aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile8
-rw-r--r--actionmailer/lib/action_mailer.rb5
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb1
-rw-r--r--actionpack/CHANGELOG.md30
-rw-r--r--actionpack/lib/action_controller.rb6
-rw-r--r--actionpack/lib/action_controller/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch.rb9
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb5
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb5
-rw-r--r--actionpack/lib/action_view.rb6
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb84
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb1
-rw-r--r--actionpack/lib/action_view/railtie.rb2
-rw-r--r--actionpack/test/controller/routing_test.rb22
-rw-r--r--actionpack/test/dispatch/mount_test.rb9
-rw-r--r--actionpack/test/dispatch/response_test.rb4
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb12
-rw-r--r--activemodel/CHANGELOG.md11
-rw-r--r--activemodel/lib/active_model.rb16
-rw-r--r--activemodel/lib/active_model/errors.rb8
-rw-r--r--activemodel/lib/active_model/railtie.rb8
-rw-r--r--activemodel/lib/active_model/validations/validates.rb7
-rw-r--r--activemodel/test/cases/validations_test.rb9
-rw-r--r--activerecord/CHANGELOG.md29
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/examples/performance.rb69
-rw-r--r--activerecord/lib/active_record.rb11
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb212
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb123
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb4
-rw-r--r--activerecord/lib/active_record/core.rb10
-rw-r--r--activerecord/lib/active_record/counter_cache.rb2
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb10
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb18
-rw-r--r--activerecord/lib/active_record/model_schema.rb15
-rw-r--r--activerecord/lib/active_record/persistence.rb6
-rw-r--r--activerecord/lib/active_record/railtie.rb3
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb7
-rw-r--r--activerecord/lib/active_record/relation.rb1
-rw-r--r--activerecord/lib/active_record/relation/merger.rb10
-rw-r--r--activerecord/lib/active_record/result.rb4
-rw-r--r--activerecord/lib/active_record/store.rb2
-rw-r--r--activerecord/lib/active_record/transactions.rb18
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb7
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb7
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb6
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb1
-rw-r--r--activerecord/test/cases/base_test.rb7
-rw-r--r--activerecord/test/cases/counter_cache_test.rb11
-rw-r--r--activerecord/test/cases/deprecated_dynamic_methods_test.rb2
-rw-r--r--activerecord/test/cases/dirty_test.rb15
-rw-r--r--activerecord/test/cases/locking_test.rb19
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb2
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb8
-rw-r--r--activerecord/test/cases/relation_test.rb5
-rw-r--r--activerecord/test/cases/relations_test.rb19
-rw-r--r--activerecord/test/cases/serialization_test.rb7
-rw-r--r--activerecord/test/cases/store_test.rb5
-rw-r--r--activerecord/test/cases/transactions_test.rb205
-rw-r--r--activerecord/test/fixtures/friendships.yml4
-rw-r--r--activerecord/test/fixtures/people.yml3
-rw-r--r--activerecord/test/fixtures/topics.yml2
-rw-r--r--activerecord/test/models/friendship.rb4
-rw-r--r--activerecord/test/models/person.rb2
-rw-r--r--activerecord/test/schema/schema.rb6
-rw-r--r--activesupport/CHANGELOG.md10
-rw-r--r--activesupport/lib/active_support.rb3
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb53
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb7
-rw-r--r--activesupport/lib/active_support/dependencies.rb32
-rw-r--r--activesupport/lib/active_support/dependencies/autoload.rb60
-rw-r--r--activesupport/lib/active_support/railtie.rb2
-rw-r--r--activesupport/test/abstract_unit.rb6
-rw-r--r--activesupport/test/autoload_test.rb (renamed from activesupport/test/autoload.rb)12
-rw-r--r--activesupport/test/caching_test.rb14
-rw-r--r--guides/source/4_0_release_notes.textile2
-rw-r--r--guides/source/active_record_validations_callbacks.textile10
-rw-r--r--guides/source/configuring.textile22
-rw-r--r--guides/source/upgrading_ruby_on_rails.textile6
-rw-r--r--railties/CHANGELOG.md2
-rw-r--r--railties/lib/rails/application.rb7
-rw-r--r--railties/lib/rails/application/bootstrap.rb18
-rw-r--r--railties/lib/rails/application/configuration.rb20
-rw-r--r--railties/lib/rails/application/finisher.rb6
-rw-r--r--railties/lib/rails/commands/server.rb9
-rw-r--r--railties/lib/rails/engine.rb5
-rw-r--r--railties/lib/rails/generators/app_base.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environment.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb8
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb2
-rw-r--r--railties/lib/rails/railtie.rb3
-rw-r--r--railties/lib/rails/railtie/configuration.rb10
-rw-r--r--railties/test/application/configuration_test.rb36
-rw-r--r--railties/test/application/middleware/remote_ip_test.rb14
-rw-r--r--railties/test/application/middleware_test.rb4
-rw-r--r--railties/test/application/rake_test.rb23
-rw-r--r--railties/test/isolation/abstract_unit.rb3
110 files changed, 983 insertions, 716 deletions
diff --git a/Gemfile b/Gemfile
index 0ce4541074..30b8f9aa08 100644
--- a/Gemfile
+++ b/Gemfile
@@ -20,9 +20,9 @@ else
end
if ENV['AR_DEPRECATED_FINDERS']
- gem 'active_record_deprecated_finders', path: ENV['AR_DEPRECATED_FINDERS']
+ gem 'activerecord-deprecated_finders', path: ENV['AR_DEPRECATED_FINDERS']
else
- gem 'active_record_deprecated_finders', github: 'rails/active_record_deprecated_finders'
+ gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders'
end
# This needs to be with require false to avoid
@@ -40,7 +40,7 @@ group :doc do
end
# AS
-gem 'memcache-client', '>= 1.8.5'
+gem 'dalli'
# Add your own local bundler stuff
local_gemfile = File.dirname(__FILE__) + "/.Gemfile"
@@ -96,3 +96,5 @@ end
# A gem necessary for ActiveRecord tests with IBM DB
gem 'ibm_db' if ENV['IBM_DB']
+
+gem 'benchmark-ips'
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 32e6183ff6..cfbe2f1cbd 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -35,7 +35,10 @@ require 'active_support/lazy_load_hooks'
module ActionMailer
extend ::ActiveSupport::Autoload
- autoload :Collector
+ eager_autoload do
+ autoload :Collector
+ end
+
autoload :Base
autoload :DeliveryMethods
autoload :MailHelper
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 5c03a29f0f..8679096735 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -5,6 +5,7 @@ require "abstract_controller/railties/routes_helpers"
module ActionMailer
class Railtie < Rails::Railtie
config.action_mailer = ActiveSupport::OrderedOptions.new
+ config.eager_load_namespaces << ActionMailer
initializer "action_mailer.logger" do
ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger }
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 516fcbe62f..1445cf43f6 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,17 +1,22 @@
## Rails 4.0.0 (unreleased) ##
+* Fix select_tag when option_tags is nil.
+ Fixes #7404.
+
+ *Sandeep Ravichandran*
+
* Add Request#formats=(extensions) that lets you set multiple formats directly in a prioritized order *DHH*
Example of using this for custom iphone views with an HTML fallback:
- class ApplicationController < ActionController::Base
- before_filter :adjust_format_for_iphone_with_html_fallback
-
- private
- def adjust_format_for_iphone_with_html_fallback
- request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
- end
- end
+ class ApplicationController < ActionController::Base
+ before_filter :adjust_format_for_iphone_with_html_fallback
+
+ private
+ def adjust_format_for_iphone_with_html_fallback
+ request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
+ end
+ end
* Add Routing Concerns to declare common routes that can be reused inside
@@ -51,8 +56,9 @@
*Richard Schneeman*
-* Add 'X-Frame-Options' => 'SAMEORIGIN' and
- 'X-XSS-Protection' => '1; mode=block'
+* Add 'X-Frame-Options' => 'SAMEORIGIN'
+ 'X-XSS-Protection' => '1; mode=block' and
+ 'X-Content-Type-Options' => 'nosniff'
as default headers.
*Egor Homakov*
@@ -1000,11 +1006,11 @@
Before:
- translate('foo_html', :something => '<script>') # => "...<script>..."
+ translate('foo_html', :something => '<script>') # => "...<script>..."
After:
- translate('foo_html', :something => '<script>') # => "...&lt;script&gt;..."
+ translate('foo_html', :something => '<script>') # => "...&lt;script&gt;..."
*Sergey Nartimov*
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index ceb90f8cee..31df9d605c 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -48,6 +48,12 @@ module ActionController
eager_autoload do
autoload :RecordIdentifier
end
+
+ def self.eager_load!
+ super
+ ActionController::Caching.eager_load!
+ HTML.eager_load!
+ end
end
# All of these simply register additional autoloads
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 851a2c4aee..3ecc105e22 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -9,6 +9,8 @@ module ActionController
class Railtie < Rails::Railtie #:nodoc:
config.action_controller = ActiveSupport::OrderedOptions.new
+ config.eager_load_namespaces << ActionController
+
initializer "action_controller.assets_config", :group => :all do |app|
app.config.action_controller.assets_dir ||= app.config.paths["public"].first
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index cc81970ddc..57b4678add 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -26,7 +26,6 @@ require 'active_support/rails'
require 'active_support/core_ext/module/attribute_accessors'
require 'action_pack'
-require 'active_model'
require 'rack'
module Rack
@@ -39,9 +38,11 @@ module ActionDispatch
class IllegalStateError < StandardError
end
- autoload_under 'http' do
- autoload :Request
- autoload :Response
+ eager_autoload do
+ autoload_under 'http' do
+ autoload :Request
+ autoload :Response
+ end
end
autoload_under 'middleware' do
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 0dcf1fc4fe..ccc0435a39 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -21,9 +21,12 @@ module ActionDispatch
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
- 'X-XSS-Protection' => '1; mode=block'
+ 'X-XSS-Protection' => '1; mode=block',
+ 'X-Content-Type-Options' => 'nosniff'
}
+ config.eager_load_namespaces << ActionDispatch
+
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index ea5028a7c0..f64cff8394 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -444,9 +444,10 @@ module ActionDispatch
raise "A rack application must be specified" unless path
- options[:as] ||= app_name(app)
+ options[:as] ||= app_name(app)
+ options[:via] ||= :all
- match(path, options.merge(:to => app, :anchor => false, :format => false, :via => :all))
+ match(path, options.merge(:to => app, :anchor => false, :format => false))
define_generate_prefix(app, options[:as])
self
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 4bd72c5520..9d11c284f5 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -38,7 +38,6 @@ module ActionView
autoload :PathSet
autoload :Template
-
autoload_under "renderer" do
autoload :Renderer
autoload :AbstractRenderer
@@ -77,6 +76,11 @@ module ActionView
autoload :TestCase
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
+
+ def self.eager_load!
+ super
+ ActionView::Template.eager_load!
+ end
end
require 'active_support/core_ext/string/output_safety'
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 68b0195700..ceb56824fa 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -209,18 +209,18 @@ module ActionView
# * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
#
# ==== Examples
- # auto_discovery_link_tag # =>
- # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
- # auto_discovery_link_tag(:atom) # =>
- # <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
- # auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
- # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
- # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
- # <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
- # auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"}) # =>
- # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
- # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"}) # =>
- # <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
+ # auto_discovery_link_tag
+ # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
+ # auto_discovery_link_tag(:atom)
+ # # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
+ # auto_discovery_link_tag(:rss, {:action => "feed"})
+ # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
+ # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"})
+ # # => <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
+ # auto_discovery_link_tag(:rss, {:controller => "news", :action => "feed"})
+ # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
+ # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"})
+ # # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
tag(
"link",
@@ -360,18 +360,18 @@ module ActionView
# width="30" and height="45". <tt>:size</tt> will be ignored if the
# value is not in the correct format.
#
- # image_tag("icon") # =>
- # <img src="/assets/icon" alt="Icon" />
- # image_tag("icon.png") # =>
- # <img src="/assets/icon.png" alt="Icon" />
- # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
- # <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
- # image_tag("/icons/icon.gif", :size => "16x16") # =>
- # <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
- # image_tag("/icons/icon.gif", :height => '32', :width => '32') # =>
- # <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
- # image_tag("/icons/icon.gif", :class => "menu_icon") # =>
- # <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
+ # image_tag("icon")
+ # # => <img src="/assets/icon" alt="Icon" />
+ # image_tag("icon.png")
+ # # => <img src="/assets/icon.png" alt="Icon" />
+ # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry")
+ # # => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
+ # image_tag("/icons/icon.gif", :size => "16x16")
+ # # => <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
+ # image_tag("/icons/icon.gif", :height => '32', :width => '32')
+ # # => <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
+ # image_tag("/icons/icon.gif", :class => "menu_icon")
+ # # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
def image_tag(source, options={})
options = options.symbolize_keys
@@ -408,24 +408,24 @@ module ActionView
# width="30" and height="45". <tt>:size</tt> will be ignored if the
# value is not in the correct format.
#
- # video_tag("trailer") # =>
- # <video src="/videos/trailer" />
- # video_tag("trailer.ogg") # =>
- # <video src="/videos/trailer.ogg" />
- # video_tag("trailer.ogg", :controls => true, :autobuffer => true) # =>
- # <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
- # video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png") # =>
- # <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
- # video_tag("/trailers/hd.avi", :size => "16x16") # =>
- # <video src="/trailers/hd.avi" width="16" height="16" />
- # video_tag("/trailers/hd.avi", :height => '32', :width => '32') # =>
- # <video height="32" src="/trailers/hd.avi" width="32" />
- # video_tag("trailer.ogg", "trailer.flv") # =>
- # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
- # video_tag(["trailer.ogg", "trailer.flv"]) # =>
- # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
- # video_tag(["trailer.ogg", "trailer.flv"], :size => "160x120") # =>
- # <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+ # video_tag("trailer")
+ # # => <video src="/videos/trailer" />
+ # video_tag("trailer.ogg")
+ # # => <video src="/videos/trailer.ogg" />
+ # video_tag("trailer.ogg", :controls => true, :autobuffer => true)
+ # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
+ # video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png")
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
+ # video_tag("/trailers/hd.avi", :size => "16x16")
+ # # => <video src="/trailers/hd.avi" width="16" height="16" />
+ # video_tag("/trailers/hd.avi", :height => '32', :width => '32')
+ # # => <video height="32" src="/trailers/hd.avi" width="32" />
+ # video_tag("trailer.ogg", "trailer.flv")
+ # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+ # video_tag(["trailer.ogg", "trailer.flv"])
+ # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+ # video_tag(["trailer.ogg", "trailer.flv"], :size => "160x120")
+ # # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
def video_tag(*sources)
multiple_sources_tag('video', sources) do |options|
options[:poster] = path_to_image(options[:poster]) if options[:poster]
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 5dc5bb8a98..b79577bcd3 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -720,15 +720,15 @@ module ActionView
# label(:post, :title)
# # => <label for="post_title">Title</label>
#
- # You can localize your labels based on model and attribute names.
- # For example you can define the following in your locale (e.g. en.yml)
+ # You can localize your labels based on model and attribute names.
+ # For example you can define the following in your locale (e.g. en.yml)
#
# helpers:
# label:
# post:
# body: "Write your entire text here"
#
- # Which then will result in
+ # Which then will result in
#
# label(:post, :body)
# # => <label for="post_body">Write your entire text here</label>
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index a9b91d1db3..ace457df2e 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -118,6 +118,7 @@ module ActionView
# # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
# # <option>Paris</option><option>Rome</option></select>
def select_tag(name, option_tags = nil, options = {})
+ option_tags ||= ""
html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
if options.delete(:include_blank)
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 9f5e3be454..2d36deaa78 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -9,6 +9,8 @@ module ActionView
config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) }
config.action_view.embed_authenticity_token_in_remote_forms = false
+ config.eager_load_namespaces << ActionView
+
initializer "action_view.embed_authenticity_token_in_remote_forms" do |app|
ActiveSupport.on_load(:action_view) do
ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms =
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 6cc1370105..57ab325683 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -197,7 +197,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_regexp_precidence
- @rs.draw do
+ rs.draw do
get '/whois/:domain', :constraints => {
:domain => /\w+\.[\w\.]+/ },
:to => lambda { |env| [200, {}, %w{regexp}] }
@@ -216,7 +216,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
}
- @rs.draw do
+ rs.draw do
get '/', :constraints => subdomain.new,
:to => lambda { |env| [200, {}, %w{default}] }
get '/', :constraints => { :subdomain => 'clients' },
@@ -228,7 +228,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_lambda_constraints
- @rs.draw do
+ rs.draw do
get '/', :constraints => lambda { |req|
req.subdomain.present? and req.subdomain != "clients" },
:to => lambda { |env| [200, {}, %w{default}] }
@@ -266,22 +266,22 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_draw_with_block_arity_one_raises
assert_raise(RuntimeError) do
- @rs.draw { |map| map.match '/:controller(/:action(/:id))' }
+ rs.draw { |map| map.match '/:controller(/:action(/:id))' }
end
end
def test_specific_controller_action_failure
- @rs.draw do
+ rs.draw do
mount lambda {} => "/foo"
end
assert_raises(ActionController::RoutingError) do
- url_for(@rs, :controller => "omg", :action => "lol")
+ url_for(rs, :controller => "omg", :action => "lol")
end
end
def test_default_setup
- @rs.draw { get '/:controller(/:action(/:id))' }
+ rs.draw { get '/:controller(/:action(/:id))' }
assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list"))
assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10"))
@@ -298,8 +298,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_ignores_leading_slash
- @rs.clear!
- @rs.draw { get '/:controller(/:action(/:id))'}
+ rs.clear!
+ rs.draw { get '/:controller(/:action(/:id))'}
test_default_setup
end
@@ -470,7 +470,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_changing_controller
- @rs.draw { get ':controller/:action/:id' }
+ rs.draw { get ':controller/:action/:id' }
assert_equal '/admin/stuff/show/10',
url_for(rs, {:controller => 'stuff', :action => 'show', :id => 10},
@@ -583,7 +583,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_action_expiry
- @rs.draw { get ':controller(/:action(/:id))' }
+ rs.draw { get ':controller(/:action(/:id))' }
assert_equal '/content', url_for(rs, { :controller => 'content' }, { :controller => 'content', :action => 'show' })
end
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index 536e35ab2e..3b008fdff0 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -22,6 +22,7 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
mount SprocketsApp => "/shorthand"
mount FakeEngine, :at => "/fakeengine"
+ mount FakeEngine, :at => "/getfake", :via => :get
scope "/its_a" do
mount SprocketsApp, :at => "/sprocket"
@@ -52,6 +53,14 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
assert_equal "/shorthand -- /omg", response.body
end
+ def test_mounting_works_with_via
+ get "/getfake"
+ assert_equal "OK", response.body
+
+ post "/getfake"
+ assert_response :not_found
+ end
+
def test_with_fake_engine_does_not_call_invalid_method
get "/fakeengine"
assert_equal "OK", response.body
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 71609d7340..4d699bd739 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -177,9 +177,10 @@ class ResponseTest < ActiveSupport::TestCase
end
end
- test "read x_frame_options and x_xss_protection" do
+ test "read x_frame_options, x_content_type_options and x_xss_protection" do
ActionDispatch::Response.default_headers = {
'X-Frame-Options' => 'DENY',
+ 'X-Content-Type-Options' => 'nosniff',
'X-XSS-Protection' => '1;'
}
resp = ActionDispatch::Response.new.tap { |response|
@@ -188,6 +189,7 @@ class ResponseTest < ActiveSupport::TestCase
resp.to_a
assert_equal('DENY', resp.headers['X-Frame-Options'])
+ assert_equal('nosniff', resp.headers['X-Content-Type-Options'])
assert_equal('1;', resp.headers['X-XSS-Protection'])
end
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 81ba92f2e6..3c66a29754 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -225,6 +225,18 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
+ def test_select_tag_with_nil_option_tags_and_include_blank
+ actual = select_tag "places", nil, :include_blank => true
+ expected = %(<select id="places" name="places"><option value=""></option></select>)
+ assert_dom_equal expected, actual
+ end
+
+ def test_select_tag_with_nil_option_tags_and_prompt
+ actual = select_tag "places", nil, :prompt => "string"
+ expected = %(<select id="places" name="places"><option value="">string</option></select>)
+ assert_dom_equal expected, actual
+ end
+
def test_text_area_tag_size_string
actual = text_area_tag "body", "hello world", "size" => "20x40"
expected = %(<textarea cols="20" id="body" name="body" rows="40">\nhello world</textarea>)
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 12e0956d3c..a8f470397b 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,6 +1,15 @@
## Rails 4.0.0 (unreleased) ##
-* Changed `ActiveModel::Serializers::Xml::Serializer#add_associations` to by default propagate `:skip_types, :dasherize, :camelize` keys to included associations. It can be overriden on each association by explicitly specifying the option on one or more associations *Anthony Alberto*
+* `AM::Validation#validates` ability to pass custom exception to `:strict` option.
+
+ *Bogdan Gusiev*
+
+* Changed `ActiveModel::Serializers::Xml::Serializer#add_associations` to by default
+ propagate `:skip_types, :dasherize, :camelize` keys to included associations.
+ It can be overriden on each association by explicitly specifying the option on one
+ or more associations
+
+ *Anthony Alberto*
* Changed `AM::Serializers::JSON.include_root_in_json' default value to false.
Now, AM Serializers and AR objects have the same default behaviour. Fixes #6578.
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index ec2d734647..d1cc19ec6b 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -34,7 +34,6 @@ module ActiveModel
autoload :Conversion
autoload :Dirty
autoload :EachValidator, 'active_model/validator'
- autoload :Errors
autoload :Lint
autoload :MassAssignmentSecurity
autoload :Model
@@ -49,11 +48,22 @@ module ActiveModel
autoload :Validations
autoload :Validator
+ eager_autoload do
+ autoload :Errors
+ end
+
module Serializers
extend ActiveSupport::Autoload
- autoload :JSON
- autoload :Xml
+ eager_autoload do
+ autoload :JSON
+ autoload :Xml
+ end
+ end
+
+ def eager_load!
+ super
+ ActiveModel::Serializer.eager_load!
end
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 1026b0f4d3..b3b9ba8e56 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -283,15 +283,19 @@ module ActiveModel
#
# If the <tt>:strict</tt> option is set to true will raise
# ActiveModel::StrictValidationFailed instead of adding the error.
+ # <tt>:strict</tt> option can also be set to any other exception.
#
# person.errors.add(:name, nil, strict: true)
# # => ActiveModel::StrictValidationFailed: name is invalid
+ # person.errors.add(:name, nil, strict: NameIsInvalid)
+ # # => NameIsInvalid: name is invalid
#
# person.errors.messages # => {}
def add(attribute, message = nil, options = {})
message = normalize_message(attribute, message, options)
- if options[:strict]
- raise ActiveModel::StrictValidationFailed, full_message(attribute, message)
+ if exception = options[:strict]
+ exception = ActiveModel::StrictValidationFailed if exception == true
+ raise exception, full_message(attribute, message)
end
self[attribute] << message
diff --git a/activemodel/lib/active_model/railtie.rb b/activemodel/lib/active_model/railtie.rb
index 63ffe5db63..f239758b35 100644
--- a/activemodel/lib/active_model/railtie.rb
+++ b/activemodel/lib/active_model/railtie.rb
@@ -1,2 +1,8 @@
require "active_model"
-require "rails" \ No newline at end of file
+require "rails"
+
+module ActiveModel
+ class Railtie < Rails::Railtie
+ config.eager_load_namespaces << ActiveModel
+ end
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 5892ad29d1..eb6e604851 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -84,12 +84,15 @@ module ActiveModel
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a +true+ or
# +false+ value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ # * <tt>:strict</tt> - if the <tt>:strict</tt> option is set to true
+ # will raise ActiveModel::StrictValidationFailed instead of adding the error.
+ # <tt>:strict</tt> option can also be set to any other exception.
#
# Example:
#
# validates :password, presence: true, confirmation: true, if: :password_required?
+ # validates :token, uniqueness: true, strict: TokenGenerationException
+ #
#
# Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+
# and +:strict+ can be given to one specific validator, as a hash:
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 8ea9745fbf..a9d32808da 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -11,6 +11,8 @@ require 'active_support/xml_mini'
class ValidationsTest < ActiveModel::TestCase
+ class CustomStrictValidationException < StandardError; end
+
def setup
Topic._validators.clear
end
@@ -323,6 +325,13 @@ class ValidationsTest < ActiveModel::TestCase
end
end
+ def test_strict_validation_custom_exception
+ Topic.validates_presence_of :title, :strict => CustomStrictValidationException
+ assert_raises CustomStrictValidationException do
+ Topic.new.valid?
+ end
+ end
+
def test_validates_with_bang
Topic.validates! :title, :presence => true
assert_raises ActiveModel::StrictValidationFailed do
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index faf1bbf232..c5ef39b9d2 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,30 @@
## Rails 4.0.0 (unreleased) ##
+* Fix `reset_counters` when there are multiple `belongs_to` association with the
+ same foreign key and one of them have a counter cache.
+ Fixes #5200.
+
+ *Dave Desrochers*
+
+* `serialized_attributes` and `_attr_readonly` become class method only. Instance reader methods are deprecated.
+
+ *kennyj*
+
+* Round usec when comparing timestamp attributes in the dirty tracking.
+ Fixes #6975.
+
+ *kennyj*
+
+* Use inversed parent for first and last child of has_many association.
+
+ *Ravil Bayramgalin*
+
+* Fix Column.microseconds and Column.fast_string_to_date to avoid converting
+ timestamp seconds to a float, since it occasionally results in inaccuracies
+ with microsecond-precision times. Fixes #7352.
+
+ *Ari Pollak*
+
* Raise `ArgumentError` if list of attributes to change is empty in `update_all`.
*Roman Shatsov*
@@ -294,7 +319,7 @@
`where(...).first_or_create!`
The implementation of the deprecated dynamic finders has been moved
- to the `active_record_deprecated_finders` gem. See below for details.
+ to the `activerecord-deprecated_finders` gem. See below for details.
*Jon Leighton*
@@ -325,7 +350,7 @@
* `:extend` becomes `:extending`
The code to implement the deprecated features has been moved out to
- the `active_record_deprecated_finders` gem. This gem is a dependency
+ the `activerecord-deprecated_finders` gem. This gem is a dependency
of Active Record in Rails 4.0. It will no longer be a dependency
from Rails 4.1, but if your app relies on the deprecated features
then you can add it to your own Gemfile. It will be maintained by
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index dca7f13fd2..53791d96ef 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency('activemodel', version)
s.add_dependency('arel', '~> 3.0.2')
- s.add_dependency('active_record_deprecated_finders', '0.0.1')
+ s.add_dependency('activerecord-deprecated_finders', '0.0.1')
end
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index 31f3e02bb8..cd9825b50c 100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -1,7 +1,9 @@
-TIMES = (ENV['N'] || 10000).to_i
-
require File.expand_path('../../../load_paths', __FILE__)
require "active_record"
+require 'benchmark/ips'
+
+TIME = (ENV['BENCHMARK_TIME'] || 20).to_i
+RECORDS = (ENV['BENCHMARK_RECORDS'] || TIME*1000).to_i
conn = { :adapter => 'sqlite3', :database => ':memory:' }
@@ -72,8 +74,8 @@ end
notes = ActiveRecord::Faker::LOREM.join ' '
today = Date.today
-puts 'Inserting 10,000 users and exhibits...'
-10_000.times do
+puts "Inserting #{RECORDS} users and exhibits..."
+RECORDS.times do
user = User.create(
:created_at => today,
:name => ActiveRecord::Faker.name,
@@ -88,9 +90,7 @@ puts 'Inserting 10,000 users and exhibits...'
)
end
-require 'benchmark'
-
-Benchmark.bm(46) do |x|
+Benchmark.ips(TIME) do |x|
ar_obj = Exhibit.find(1)
attrs = { :name => 'sam' }
attrs_first = { :name => 'sam' }
@@ -101,77 +101,72 @@ Benchmark.bm(46) do |x|
:created_at => Date.today
}
- x.report("Model#id (x#{(TIMES * 100).ceil})") do
- (TIMES * 100).ceil.times { ar_obj.id }
+ x.report("Model#id") do
+ ar_obj.id
end
x.report 'Model.new (instantiation)' do
- TIMES.times { Exhibit.new }
+ Exhibit.new
end
x.report 'Model.new (setting attributes)' do
- TIMES.times { Exhibit.new(attrs) }
+ Exhibit.new(attrs)
end
x.report 'Model.first' do
- TIMES.times { Exhibit.first.look }
+ Exhibit.first.look
end
- x.report 'Model.named_scope' do
- TIMES.times { Exhibit.limit(10).with_name.with_notes }
+ x.report("Model.all limit(100)") do
+ Exhibit.look Exhibit.limit(100)
end
- x.report("Model.all limit(100) (x#{(TIMES / 10).ceil})") do
- (TIMES / 10).ceil.times { Exhibit.look Exhibit.limit(100) }
+ x.report "Model.all limit(100) with relationship" do
+ Exhibit.feel Exhibit.limit(100).includes(:user)
end
- x.report "Model.all limit(100) with relationship (x#{(TIMES / 10).ceil})" do
- (TIMES / 10).ceil.times { Exhibit.feel Exhibit.limit(100).includes(:user) }
+ x.report "Model.all limit(10,000)" do
+ Exhibit.look Exhibit.limit(10000)
end
- x.report "Model.all limit(10,000) x(#{(TIMES / 1000).ceil})" do
- (TIMES / 1000).ceil.times { Exhibit.look Exhibit.limit(10000) }
+ x.report 'Model.named_scope' do
+ Exhibit.limit(10).with_name.with_notes
end
x.report 'Model.create' do
- TIMES.times { Exhibit.create(exhibit) }
+ Exhibit.create(exhibit)
end
x.report 'Resource#attributes=' do
- TIMES.times {
- exhibit = Exhibit.new(attrs_first)
- exhibit.attributes = attrs_second
- }
+ e = Exhibit.new(attrs_first)
+ e.attributes = attrs_second
end
x.report 'Resource#update' do
- TIMES.times { Exhibit.first.update_attributes(:name => 'bob') }
+ Exhibit.first.update_attributes(:name => 'bob')
end
x.report 'Resource#destroy' do
- TIMES.times { Exhibit.first.destroy }
+ Exhibit.first.destroy
end
x.report 'Model.transaction' do
- TIMES.times { Exhibit.transaction { Exhibit.new } }
+ Exhibit.transaction { Exhibit.new }
end
x.report 'Model.find(id)' do
- id = Exhibit.first.id
- TIMES.times { Exhibit.find(id) }
+ User.find(1)
end
x.report 'Model.find_by_sql' do
- TIMES.times {
- Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first
- }
+ Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first
end
- x.report "Model.log x(#{TIMES * 10})" do
- (TIMES * 10).times { Exhibit.connection.send(:log, "hello", "world") {} }
+ x.report "Model.log" do
+ Exhibit.connection.send(:log, "hello", "world") {}
end
- x.report "AR.execute(query) (#{TIMES / 2})" do
- (TIMES / 2).times { ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}") }
+ x.report "AR.execute(query)" do
+ ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}")
end
end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 5a51aaaced..fa94f6a941 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -25,7 +25,7 @@ require 'active_support'
require 'active_support/rails'
require 'active_model'
require 'arel'
-require 'active_record_deprecated_finders'
+require 'active_record/deprecated_finders'
require 'active_record/version'
@@ -160,6 +160,15 @@ module ActiveRecord
autoload :TestCase
autoload :TestFixtures, 'active_record/fixtures'
+
+ def self.eager_load!
+ super
+ ActiveRecord::Locking.eager_load!
+ ActiveRecord::Scoping.eager_load!
+ ActiveRecord::Associations.eager_load!
+ ActiveRecord::AttributeMethods.eager_load!
+ ActiveRecord::ConnectionAdapters.eager_load!
+ end
end
ActiveSupport.on_load(:active_record) do
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index a84eda1d3b..b15df4f308 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -574,7 +574,7 @@ module ActiveRecord
args.shift if args.first.is_a?(Hash) && args.first.empty?
collection = fetch_first_or_last_using_find?(args) ? scope : load_target
- collection.send(type, *args)
+ collection.send(type, *args).tap {|it| set_inverse_instance it }
end
end
end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 6992840040..d9989274c8 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -84,11 +84,11 @@ module ActiveRecord
def assign_attributes(new_attributes, options = {})
return if new_attributes.blank?
- attributes = new_attributes.stringify_keys
- multi_parameter_attributes = []
+ attributes = new_attributes.stringify_keys
+ multi_parameter_attributes = []
nested_parameter_attributes = []
- previous_options = @mass_assignment_options
- @mass_assignment_options = options
+ previous_options = @mass_assignment_options
+ @mass_assignment_options = options
unless options[:without_protection]
attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
@@ -97,23 +97,15 @@ module ActiveRecord
attributes.each do |k, v|
if k.include?("(")
multi_parameter_attributes << [ k, v ]
- elsif respond_to?("#{k}=")
- if v.is_a?(Hash)
- nested_parameter_attributes << [ k, v ]
- else
- send("#{k}=", v)
- end
+ elsif v.is_a?(Hash)
+ nested_parameter_attributes << [ k, v ]
else
- raise(UnknownAttributeError, "unknown attribute: #{k}")
+ _assign_attribute(k, v)
end
end
- # assign any deferred nested attributes after the base attributes have been set
- nested_parameter_attributes.each do |k,v|
- send("#{k}=", v)
- end
-
- assign_multiparameter_attributes(multi_parameter_attributes)
+ assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
+ assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
ensure
@mass_assignment_options = previous_options
end
@@ -130,6 +122,21 @@ module ActiveRecord
private
+ def _assign_attribute(k, v)
+ public_send("#{k}=", v)
+ rescue NoMethodError
+ if respond_to?("#{k}=")
+ raise
+ else
+ raise UnknownAttributeError, "unknown attribute: #{k}"
+ end
+ end
+
+ # Assign any deferred nested attributes after the base attributes have been set.
+ def assign_nested_parameter_attributes(pairs)
+ pairs.each { |k, v| _assign_attribute(k, v) }
+ end
+
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
@@ -143,19 +150,11 @@ module ActiveRecord
)
end
- def instantiate_time_object(name, values)
- if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
- Time.zone.local(*values)
- else
- Time.time_with_datetime_fallback(self.class.default_timezone, *values)
- end
- end
-
def execute_callstack_for_multiparameter_attributes(callstack)
errors = []
callstack.each do |name, values_with_empty_parameters|
begin
- send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
+ send("#{name}=", MultiparameterAttribute.new(self, name, values_with_empty_parameters).read_value)
rescue => ex
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
end
@@ -166,74 +165,12 @@ module ActiveRecord
end
end
- def read_value_from_parameter(name, values_hash_from_param)
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
- if values_hash_from_param.values.all?{|v|v.nil?}
- nil
- elsif klass == Time
- read_time_parameter_value(name, values_hash_from_param)
- elsif klass == Date
- read_date_parameter_value(name, values_hash_from_param)
- else
- read_other_parameter_value(klass, name, values_hash_from_param)
- end
- end
-
- def read_time_parameter_value(name, values_hash_from_param)
- # If column is a :time (and not :date or :timestamp) there is no need to validate if
- # there are year/month/day fields
- if column_for_attribute(name).type == :time
- # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
- {1 => 1970, 2 => 1, 3 => 1}.each do |key,value|
- values_hash_from_param[key] ||= value
- end
- else
- # else column is a timestamp, so if Date bits were not provided, error
- if missing_parameter = [1,2,3].detect{ |position| !values_hash_from_param.has_key?(position) }
- raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter}i)")
- end
-
- # If Date bits were provided but blank, then return nil
- return nil if (1..3).any? { |position| values_hash_from_param[position].blank? }
- end
-
- max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
- set_values = (1..max_position).collect{ |position| values_hash_from_param[position] }
- # If Time bits are not there, then default to 0
- (3..5).each { |i| set_values[i] = set_values[i].blank? ? 0 : set_values[i] }
- instantiate_time_object(name, set_values)
- end
-
- def read_date_parameter_value(name, values_hash_from_param)
- return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
- set_values = [values_hash_from_param[1], values_hash_from_param[2], values_hash_from_param[3]]
- begin
- Date.new(*set_values)
- rescue ArgumentError # if Date.new raises an exception on an invalid date
- instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
- end
- end
-
- def read_other_parameter_value(klass, name, values_hash_from_param)
- max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
- values = (1..max_position).collect do |position|
- raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
- values_hash_from_param[position]
- end
- klass.new(*values)
- end
-
- def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
- [values_hash_from_param.keys.max,upper_cap].min
- end
-
def extract_callstack_for_multiparameter_attributes(pairs)
attributes = { }
- pairs.each do |pair|
- multiparameter_name, value = pair
+ pairs.each do |(multiparameter_name, value)|
attribute_name = multiparameter_name.split("(").first
- attributes[attribute_name] = {} unless attributes.include?(attribute_name)
+ attributes[attribute_name] ||= {}
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
@@ -250,5 +187,100 @@ module ActiveRecord
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
end
+ class MultiparameterAttribute #:nodoc:
+ attr_reader :object, :name, :values, :column
+
+ def initialize(object, name, values)
+ @object = object
+ @name = name
+ @values = values
+ end
+
+ def read_value
+ return if values.values.compact.empty?
+
+ @column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name)
+ klass = column.klass
+
+ if klass == Time
+ read_time
+ elsif klass == Date
+ read_date
+ else
+ read_other(klass)
+ end
+ end
+
+ private
+
+ def instantiate_time_object(set_values)
+ if object.class.send(:create_time_zone_conversion_attribute?, name, column)
+ Time.zone.local(*set_values)
+ else
+ Time.time_with_datetime_fallback(object.class.default_timezone, *set_values)
+ end
+ end
+
+ def read_time
+ # If column is a :time (and not :date or :timestamp) there is no need to validate if
+ # there are year/month/day fields
+ if column.type == :time
+ # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
+ { 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
+ values[key] ||= value
+ end
+ else
+ # else column is a timestamp, so if Date bits were not provided, error
+ validate_missing_parameters!([1,2,3])
+
+ # If Date bits were provided but blank, then return nil
+ return if blank_date_parameter?
+ end
+
+ max_position = extract_max_param(6)
+ set_values = values.values_at(*(1..max_position))
+ # If Time bits are not there, then default to 0
+ (3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
+ instantiate_time_object(set_values)
+ end
+
+ def read_date
+ return if blank_date_parameter?
+ set_values = values.values_at(1,2,3)
+ begin
+ Date.new(*set_values)
+ rescue ArgumentError # if Date.new raises an exception on an invalid date
+ instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
+ end
+ end
+
+ def read_other(klass)
+ max_position = extract_max_param
+ positions = (1..max_position)
+ validate_missing_parameters!(positions)
+
+ set_values = values.values_at(*positions)
+ klass.new(*set_values)
+ end
+
+ # Checks whether some blank date parameter exists. Note that this is different
+ # than the validate_missing_parameters! method, since it just checks for blank
+ # positions instead of missing ones, and does not raise in case one blank position
+ # exists. The caller is responsible to handle the case of this returning true.
+ def blank_date_parameter?
+ (1..3).any? { |position| values[position].blank? }
+ end
+
+ # If some position is not provided, it errors out a missing parameter exception.
+ def validate_missing_parameters!(positions)
+ if missing_parameter = positions.detect { |position| !values.key?(position) }
+ raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
+ end
+ end
+
+ def extract_max_param(upper_cap = 100)
+ [values.keys.max, upper_cap].min
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 49ab3ab808..bdda5bc009 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -6,10 +6,46 @@ module ActiveRecord
included do
# Returns a hash of all the attributes that have been specified for serialization as
# keys and their class restriction as values.
- class_attribute :serialized_attributes, instance_writer: false
+ class_attribute :serialized_attributes, instance_accessor: false
self.serialized_attributes = {}
end
+ module ClassMethods
+ # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
+ # then specify the name of that attribute using this method and it will be handled automatically.
+ # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
+ # class on retrieval or SerializationTypeMismatch will be raised.
+ #
+ # ==== Parameters
+ #
+ # * +attr_name+ - The field name that should be serialized.
+ # * +class_name+ - Optional, class name that the object type should be equal to.
+ #
+ # ==== Example
+ # # Serialize a preferences attribute
+ # class User < ActiveRecord::Base
+ # serialize :preferences
+ # end
+ def serialize(attr_name, class_name = Object)
+ include Behavior
+
+ coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
+ class_name
+ else
+ Coders::YAMLColumn.new(class_name)
+ end
+
+ # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
+ # has its own hash of own serialized attributes
+ self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
+ end
+ end
+
+ def serialized_attributes
+ ActiveSupport::Deprecation.warn("Instance level serialized_attributes method is deprecated, please use class level method.")
+ defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
+ end
+
class Type # :nodoc:
def initialize(column)
@column = column
@@ -44,71 +80,50 @@ module ActiveRecord
end
end
- module ClassMethods
- # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
- # then specify the name of that attribute using this method and it will be handled automatically.
- # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
- # class on retrieval or SerializationTypeMismatch will be raised.
- #
- # ==== Parameters
- #
- # * +attr_name+ - The field name that should be serialized.
- # * +class_name+ - Optional, class name that the object type should be equal to.
- #
- # ==== Example
- # # Serialize a preferences attribute
- # class User < ActiveRecord::Base
- # serialize :preferences
- # end
- def serialize(attr_name, class_name = Object)
- coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
- class_name
- else
- Coders::YAMLColumn.new(class_name)
- end
+ # This is only added to the model when serialize is called, which
+ # ensures we do not make things slower when serialization is not used.
+ module Behavior #:nodoc:
+ extend ActiveSupport::Concern
- # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
- # has its own hash of own serialized attributes
- self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
- end
-
- def initialize_attributes(attributes, options = {}) #:nodoc:
- serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
- super(attributes, options)
+ module ClassMethods
+ def initialize_attributes(attributes, options = {})
+ serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
+ super(attributes, options)
- serialized_attributes.each do |key, coder|
- if attributes.key?(key)
- attributes[key] = Attribute.new(coder, attributes[key], serialized)
+ serialized_attributes.each do |key, coder|
+ if attributes.key?(key)
+ attributes[key] = Attribute.new(coder, attributes[key], serialized)
+ end
end
+
+ attributes
end
- attributes
- end
+ private
- private
+ def attribute_cast_code(attr_name)
+ if serialized_attributes.include?(attr_name)
+ "v.unserialized_value"
+ else
+ super
+ end
+ end
+ end
- def attribute_cast_code(attr_name)
- if serialized_attributes.include?(attr_name)
- "v.unserialized_value"
+ def type_cast_attribute_for_write(column, value)
+ if column && coder = self.class.serialized_attributes[column.name]
+ Attribute.new(coder, value, :unserialized)
else
super
end
end
- end
-
- def type_cast_attribute_for_write(column, value)
- if column && coder = self.class.serialized_attributes[column.name]
- Attribute.new(coder, value, :unserialized)
- else
- super
- end
- end
- def read_attribute_before_type_cast(attr_name)
- if serialized_attributes.include?(attr_name)
- super.unserialized_value
- else
- super
+ def read_attribute_before_type_cast(attr_name)
+ if self.class.serialized_attributes.include?(attr_name)
+ super.unserialized_value
+ else
+ super
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index fa5b2ef336..d1e9d2de0e 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -59,11 +59,14 @@ module ActiveRecord
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
- time = time.in_time_zone rescue nil if time
- changed = read_attribute(:#{attr_name}) != time
- write_attribute(:#{attr_name}, original_time)
- #{attr_name}_will_change! if changed
- @attributes_cache["#{attr_name}"] = time
+ zoned_time = time && time.in_time_zone rescue nil
+ rounded_time = round_usec(zoned_time)
+ rounded_value = round_usec(read_attribute("#{attr_name}"))
+ if (rounded_value != rounded_time) || (!rounded_value && original_time)
+ write_attribute("#{attr_name}", original_time)
+ #{attr_name}_will_change!
+ @attributes_cache["#{attr_name}"] = zoned_time
+ end
end
EOV
generated_attribute_methods.module_eval(method_body, __FILE__, line)
@@ -79,6 +82,12 @@ module ActiveRecord
[:datetime, :timestamp].include?(column.type)
end
end
+
+ private
+ def round_usec(value)
+ return unless value
+ value.change(:usec => 0)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index b0b51f540c..02459763f7 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -1,6 +1,12 @@
module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseStatements
+ def initialize
+ super
+ @_current_transaction_records = []
+ @transaction_joinable = nil
+ end
+
# Converts an arel AST to SQL
def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
@@ -167,7 +173,7 @@ module ActiveRecord
def transaction(options = {})
options.assert_valid_keys :requires_new, :joinable
- last_transaction_joinable = defined?(@transaction_joinable) ? @transaction_joinable : nil
+ last_transaction_joinable = @transaction_joinable
if options.has_key?(:joinable)
@transaction_joinable = options[:joinable]
else
@@ -176,22 +182,19 @@ module ActiveRecord
requires_new = options[:requires_new] || !last_transaction_joinable
transaction_open = false
- @_current_transaction_records ||= []
begin
- if block_given?
- if requires_new || open_transactions == 0
- if open_transactions == 0
- begin_db_transaction
- elsif requires_new
- create_savepoint
- end
- increment_open_transactions
- transaction_open = true
- @_current_transaction_records.push([])
+ if requires_new || open_transactions == 0
+ if open_transactions == 0
+ begin_db_transaction
+ elsif requires_new
+ create_savepoint
end
- yield
+ increment_open_transactions
+ transaction_open = true
+ @_current_transaction_records.push([])
end
+ yield
rescue Exception => database_transaction_rollback
if transaction_open && !outside_transaction?
transaction_open = false
@@ -225,7 +228,7 @@ module ActiveRecord
@_current_transaction_records.last.concat(save_point_records)
end
end
- rescue Exception => database_transaction_rollback
+ rescue Exception
if open_transactions == 0
rollback_db_transaction
rollback_transaction_records(true)
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index b9045cf1e7..1445bb3b2f 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -208,7 +208,7 @@ module ActiveRecord
# '0.123456' -> 123456
# '1.123456' -> 123456
def microseconds(time)
- ((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
+ time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
end
def new_date(year, mon, mday)
@@ -233,7 +233,7 @@ module ActiveRecord
# Doesn't handle time zones.
def fast_string_to_time(string)
if string =~ Format::ISO_DATETIME
- microsec = ($7.to_f * 1_000_000).to_i
+ microsec = ($7.to_r * 1_000_000).to_i
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 3b0353358a..6bf7af081f 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -298,13 +298,6 @@ module ActiveRecord
@connection.insert_id
end
- class Result < ActiveRecord::Result
- def initialize(columns, rows, column_types)
- super(columns, rows)
- @column_types = column_types
- end
- end
-
module Fields
class Type
def type; end
@@ -437,7 +430,7 @@ module ActiveRecord
}
end
}
- result_set = Result.new(types.keys, result.to_a, types)
+ result_set = ActiveRecord::Result.new(types.keys, result.to_a, types)
result.free
else
result_set = ActiveRecord::Result.new([], [])
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 8e9ce80697..40cd65cce9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -803,13 +803,6 @@ module ActiveRecord
Arel::Nodes::BindParam.new "$#{index + 1}"
end
- class Result < ActiveRecord::Result
- def initialize(columns, rows, column_types)
- super(columns, rows)
- @column_types = column_types
- end
- end
-
def exec_query(sql, name = 'SQL', binds = [])
log(sql, name, binds) do
result = binds.empty? ? exec_no_cache(sql, binds) :
@@ -825,7 +818,7 @@ module ActiveRecord
}
end
- ret = Result.new(result.fields, result.values, types)
+ ret = ActiveRecord::Result.new(result.fields, result.values, types)
result.clear
return ret
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 57aa47ab61..4fe0013f0f 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -380,9 +380,9 @@ module ActiveRecord
case field["dflt_value"]
when /^null$/i
field["dflt_value"] = nil
- when /^'(.*)'$/
+ when /^'(.*)'$/m
field["dflt_value"] = $1.gsub("''", "'")
- when /^"(.*)"$/
+ when /^"(.*)"$/m
field["dflt_value"] = $1.gsub('""', '"')
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 0fddfdf0cb..aad21b8e37 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/hash/indifferent_access'
-require 'active_support/core_ext/object/deep_dup'
+require 'active_support/core_ext/object/duplicable'
require 'thread'
module ActiveRecord
@@ -173,7 +173,10 @@ module ActiveRecord
# # Instantiates a single new object bypassing mass-assignment security
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
def initialize(attributes = nil, options = {})
- @attributes = self.class.initialize_attributes(self.class.column_defaults.deep_dup)
+ defaults = self.class.column_defaults.dup
+ defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
+
+ @attributes = self.class.initialize_attributes(defaults)
@columns_hash = self.class.column_types.dup
init_internals
@@ -185,7 +188,7 @@ module ActiveRecord
assign_attributes(attributes, options) if attributes
yield self if block_given?
- run_callbacks :initialize if _initialize_callbacks.any?
+ run_callbacks :initialize unless _initialize_callbacks.empty?
end
# Initialize an empty model object from +coder+. +coder+ must contain
@@ -390,6 +393,7 @@ module ActiveRecord
@marked_for_destruction = false
@new_record = true
@mass_assignment_options = nil
+ @_start_transaction_state = {}
end
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index b27a19f89a..c877079b25 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -25,7 +25,7 @@ module ActiveRecord
foreign_key = has_many_association.foreign_key.to_s
child_class = has_many_association.klass
belongs_to = child_class.reflect_on_all_associations(:belongs_to)
- reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
counter_name = reflection.counter_cache_column
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index 843587c32e..3bac31c6aa 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module DynamicMatchers #:nodoc:
# This code in this file seems to have a lot of indirection, but the indirection
- # is there to provide extension points for the active_record_deprecated_finders
- # gem. When we stop supporting active_record_deprecated_finders (from Rails 5),
+ # is there to provide extension points for the activerecord-deprecated_finders
+ # gem. When we stop supporting activerecord-deprecated_finders (from Rails 5),
# then we can remove the indirection.
def respond_to?(name, include_private = false)
@@ -74,17 +74,17 @@ module ActiveRecord
end
module Finder
- # Extended in active_record_deprecated_finders
+ # Extended in activerecord-deprecated_finders
def body
result
end
- # Extended in active_record_deprecated_finders
+ # Extended in activerecord-deprecated_finders
def result
"#{finder}(#{attributes_hash})"
end
- # Extended in active_record_deprecated_finders
+ # Extended in activerecord-deprecated_finders
def signature
attribute_names.join(', ')
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index e0344f3c56..e96ed00f9c 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -168,16 +168,16 @@ module ActiveRecord
super
end
- # If the locking column has no default value set,
- # start the lock version at zero. Note we can't use
- # <tt>locking_enabled?</tt> at this point as
- # <tt>@attributes</tt> may not have been initialized yet.
- def initialize_attributes(attributes, options = {}) #:nodoc:
- if attributes.key?(locking_column) && lock_optimistically
- attributes[locking_column] ||= 0
- end
+ def column_defaults
+ @column_defaults ||= begin
+ defaults = super
+
+ if defaults.key?(locking_column) && lock_optimistically
+ defaults[locking_column] ||= 0
+ end
- attributes
+ defaults
+ end
end
end
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index def48c03bf..99de16cd33 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -225,7 +225,7 @@ module ActiveRecord
def decorate_columns(columns_hash) # :nodoc:
return if columns_hash.empty?
- serialized_attributes.keys.each do |key|
+ serialized_attributes.each_key do |key|
columns_hash[key] = AttributeMethods::Serialization::Type.new(columns_hash[key])
end
@@ -259,13 +259,12 @@ module ActiveRecord
# and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
# is available.
def column_methods_hash #:nodoc:
- @dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
+ @dynamic_methods_hash ||= column_names.each_with_object(Hash.new(false)) do |attr, methods|
attr_name = attr.to_s
methods[attr.to_sym] = attr_name
methods["#{attr}=".to_sym] = attr_name
methods["#{attr}?".to_sym] = attr_name
methods["#{attr}_before_type_cast".to_sym] = attr_name
- methods
end
end
@@ -312,13 +311,19 @@ module ActiveRecord
@relation = nil
end
+ # This is a hook for use by modules that need to do extra stuff to
+ # attributes when they are initialized. (e.g. attribute
+ # serialization)
+ def initialize_attributes(attributes, options = {}) #:nodoc:
+ attributes
+ end
+
private
# Guesses the table name, but does not decorate it with prefix and suffix information.
def undecorated_table_name(class_name = base_class.name)
table_name = class_name.to_s.demodulize.underscore
- table_name = table_name.pluralize if pluralize_table_names
- table_name
+ pluralize_table_names ? table_name.pluralize : table_name
end
# Computes and returns a table name according to default conventions.
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 593fed5a85..6b4b9bd103 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -218,7 +218,7 @@ module ActiveRecord
raise ActiveRecordError, "can not update on a new record object" unless persisted?
attributes.each_key do |key|
- raise ActiveRecordError, "#{key.to_s} is marked as readonly" if self.class.readonly_attributes.include?(key.to_s)
+ raise ActiveRecordError, "#{key} is marked as readonly" if self.class.readonly_attributes.include?(key.to_s)
end
attributes.each do |k,v|
@@ -391,9 +391,5 @@ module ActiveRecord
@new_record = false
id
end
-
- def verify_readonly_attribute(name)
- raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
- end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 672d9a4246..ecf8547e67 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -29,8 +29,11 @@ module ActiveRecord
'ActiveRecord::RecordNotSaved' => :unprocessable_entity
)
+
config.active_record.use_schema_cache_dump = true
+ config.eager_load_namespaces << ActiveRecord
+
rake_tasks do
require "active_record/base"
load "active_record/railties/databases.rake"
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 1d8c566e40..b3c20c4aff 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -4,7 +4,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :_attr_readonly, instance_writer: false
+ class_attribute :_attr_readonly, instance_accessor: false
self._attr_readonly = []
end
@@ -20,5 +20,10 @@ module ActiveRecord
self._attr_readonly
end
end
+
+ def _attr_readonly
+ ActiveSupport::Deprecation.warn("Instance level _attr_readonly method is deprecated, please use class level method.")
+ defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly
+ end
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 1abbc58314..2d0457636e 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -18,6 +18,7 @@ module ActiveRecord
attr_reader :table, :klass, :loaded
attr_accessor :default_scoped
+ alias :model :klass
alias :loaded? :loaded
alias :default_scoped? :default_scoped
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 71aaedee1e..e5b50673da 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -97,15 +97,13 @@ module ActiveRecord
merged_wheres = relation.where_values + values[:where]
unless relation.where_values.empty?
- # Remove duplicates, last one wins.
- seen = Hash.new { |h,table| h[table] = {} }
+ # Remove equalities with duplicated left-hand. Last one wins.
+ seen = {}
merged_wheres = merged_wheres.reverse.reject { |w|
nuke = false
if w.respond_to?(:operator) && w.operator == :==
- name = w.left.name
- table = w.left.relation.name
- nuke = seen[table][name]
- seen[table][name] = true
+ nuke = seen[w.left]
+ seen[w.left] = true
end
nuke
}.reverse
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index fd276ccf5d..2414a4bbd7 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -10,11 +10,11 @@ module ActiveRecord
attr_reader :columns, :rows, :column_types
- def initialize(columns, rows)
+ def initialize(columns, rows, column_types = {})
@columns = columns
@rows = rows
@hash_rows = nil
- @column_types = {}
+ @column_types = column_types
end
def each
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 5151f349b7..b4013ecc1e 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -41,7 +41,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :stored_attributes, instance_writer: false
+ class_attribute :stored_attributes, instance_accessor: false
self.stored_attributes = {}
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 5c3399e2aa..9cec791faf 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -293,12 +293,12 @@ module ActiveRecord
begin
status = yield
rescue ActiveRecord::Rollback
- if defined?(@_start_transaction_state)
+ if defined?(@_start_transaction_state)
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
end
status = nil
end
-
+
raise ActiveRecord::Rollback unless status
end
status
@@ -308,7 +308,6 @@ module ActiveRecord
# Save the new record state and id of a record so it can be restored later if a transaction fails.
def remember_transaction_record_state #:nodoc:
- @_start_transaction_state ||= {}
@_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
@_start_transaction_state[:new_record] = @new_record
@_start_transaction_state[:destroyed] = @destroyed
@@ -317,18 +316,16 @@ module ActiveRecord
# Clear the new record state and id of a record.
def clear_transaction_record_state #:nodoc:
- if defined?(@_start_transaction_state)
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
- remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1
- end
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
+ @_start_transaction_state.clear if @_start_transaction_state[:level] < 1
end
# Restore the new record state and id of a record that was previously saved by a call to save_record_state.
def restore_transaction_record_state(force = false) #:nodoc:
- if defined?(@_start_transaction_state)
+ unless @_start_transaction_state.empty?
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
if @_start_transaction_state[:level] < 1 || force
- restore_state = remove_instance_variable(:@_start_transaction_state)
+ restore_state = @_start_transaction_state
was_frozen = @attributes.frozen?
@attributes = @attributes.dup if was_frozen
@new_record = restore_state[:new_record]
@@ -340,13 +337,14 @@ module ActiveRecord
@attributes_cache.delete(self.class.primary_key)
end
@attributes.freeze if was_frozen
+ @_start_transaction_state.clear
end
end
end
# Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
def transaction_record_state(state) #:nodoc:
- @_start_transaction_state[state] if defined?(@_start_transaction_state)
+ @_start_transaction_state[state]
end
# Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index c3f82bc63d..4bccd2cc59 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -128,11 +128,12 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert_equal [["STRICT_ALL_TABLES"]], result.rows
end
- def test_mysql_strict_mode_disabled
+ def test_mysql_strict_mode_disabled_dont_override_global_sql_mode
run_without_connection do |orig_connection|
ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false}))
- result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal [['']], result.rows
+ global_sql_mode = ActiveRecord::Model.connection.exec_query "SELECT @@GLOBAL.sql_mode"
+ session_sql_mode = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal global_sql_mode.rows, session_sql_mode.rows
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 276c499276..c63e4fe5b6 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -44,11 +44,12 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert_equal [["STRICT_ALL_TABLES"]], result.rows
end
- def test_mysql_strict_mode_disabled
+ def test_mysql_strict_mode_disabled_dont_override_global_sql_mode
run_without_connection do |orig_connection|
ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false}))
- result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal [['']], result.rows
+ global_sql_mode = ActiveRecord::Model.connection.exec_query "SELECT @@GLOBAL.sql_mode"
+ session_sql_mode = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal global_sql_mode.rows, session_sql_mode.rows
end
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 8cb8a5a861..aad48e7ce9 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -259,6 +259,12 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal m.name, i.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
end
+ def test_parent_instance_should_be_shared_with_first_and_last_child
+ man = Man.first
+ assert man.interests.first.man.equal? man
+ assert man.interests.last.man.equal? man
+ end
+
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests }
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 807971d678..4bc68acd13 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -34,7 +34,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert t.attribute_present?("written_on")
assert !t.attribute_present?("content")
assert !t.attribute_present?("author_name")
-
end
def test_attribute_present_with_booleans
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 062f196a12..63981a68a9 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -231,6 +231,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal 11, Topic.find(1).written_on.sec
assert_equal 223300, Topic.find(1).written_on.usec
assert_equal 9900, Topic.find(2).written_on.usec
+ assert_equal 129346, Topic.find(3).written_on.usec
end
end
@@ -603,6 +604,12 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "changed", post.body
end
+ def test_attr_readonly_is_class_level_setting
+ post = ReadonlyTitlePost.new
+ assert_raise(NoMethodError) { post._attr_readonly = [:title] }
+ assert_deprecated { post._attr_readonly }
+ end
+
def test_non_valid_identifier_column_name
weird = Weird.create('a$b' => 'value')
weird.reload
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index cd3d19e783..ee443741ca 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -8,9 +8,11 @@ require 'models/category'
require 'models/categorization'
require 'models/dog'
require 'models/dog_lover'
+require 'models/person'
+require 'models/friendship'
class CounterCacheTest < ActiveRecord::TestCase
- fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers
+ fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships
class ::SpecialTopic < ::Topic
has_many :special_replies, :foreign_key => 'parent_id'
@@ -109,4 +111,11 @@ class CounterCacheTest < ActiveRecord::TestCase
Topic.update_counters([t1.id, t2.id], :replies_count => 2)
end
end
+
+ test "reset the right counter if two have the same foreign key" do
+ michael = people(:michael)
+ assert_nothing_raised(ActiveRecord::StatementInvalid) do
+ Person.reset_counters(michael.id, :followers)
+ end
+ end
end
diff --git a/activerecord/test/cases/deprecated_dynamic_methods_test.rb b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
index fe307bc49b..392f5f4cd5 100644
--- a/activerecord/test/cases/deprecated_dynamic_methods_test.rb
+++ b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
@@ -1,4 +1,4 @@
-# This file should be deleted when active_record_deprecated_finders is removed as
+# This file should be deleted when activerecord-deprecated_finders is removed as
# a dependency.
#
# It is kept for now as there is some fairly nuanced behaviour in the dynamic
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 248f4efe3e..92677b9926 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -525,6 +525,21 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_setting_time_attributes_with_time_zone_field_to_same_time_should_not_be_marked_as_a_change
+ in_time_zone 'Paris' do
+ target = Class.new(ActiveRecord::Base)
+ target.table_name = 'pirates'
+
+ created_on = Time.now
+
+ pirate = target.create(:created_on => created_on)
+ pirate.reload # Here mysql truncate the usec value to 0
+
+ pirate.created_on = created_on
+ assert !pirate.created_on_changed?
+ end
+ end
+
private
def with_partial_updates(klass, on = true)
old = klass.partial_updates?
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index afb0bd6fd9..2392516395 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -3,6 +3,7 @@ require "cases/helper"
require 'models/person'
require 'models/job'
require 'models/reader'
+require 'models/ship'
require 'models/legacy_thing'
require 'models/reference'
require 'models/string_key_object'
@@ -18,8 +19,8 @@ class LockWithCustomColumnWithoutDefault < ActiveRecord::Base
self.locking_column = :custom_lock_version
end
-class ReadonlyFirstNamePerson < Person
- attr_readonly :first_name
+class ReadonlyNameShip < Ship
+ attr_readonly :name
end
class OptimisticLockingTest < ActiveRecord::TestCase
@@ -200,15 +201,15 @@ class OptimisticLockingTest < ActiveRecord::TestCase
end
def test_readonly_attributes
- assert_equal Set.new([ 'first_name' ]), ReadonlyFirstNamePerson.readonly_attributes
+ assert_equal Set.new([ 'name' ]), ReadonlyNameShip.readonly_attributes
- p = ReadonlyFirstNamePerson.create(:first_name => "unchangeable name")
- p.reload
- assert_equal "unchangeable name", p.first_name
+ s = ReadonlyNameShip.create(:name => "unchangeable name")
+ s.reload
+ assert_equal "unchangeable name", s.name
- p.update_attributes(:first_name => "changed name")
- p.reload
- assert_equal "unchangeable name", p.first_name
+ s.update_attributes(:name => "changed name")
+ s.reload
+ assert_equal "unchangeable name", s.name
end
def test_quote_table_name
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 73a01906b9..a36b2c2506 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -313,7 +313,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
end
-# This class should be deleted when we removed active_record_deprecated_finders as a
+# This class should be deleted when we remove activerecord-deprecated_finders as a
# dependency.
class MassAssignmentSecurityDeprecatedFindersTest < ActiveRecord::TestCase
include MassAssignmentTestHelpers
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 9584d5dd06..b88db384a0 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -7,6 +7,14 @@ module ActiveRecord
self.use_transactional_fixtures = false
+ def test_add_column_newline_default
+ string = "foo\nbar"
+ add_column 'test_models', 'command', :string, :default => string
+ TestModel.reset_column_information
+
+ assert_equal string, TestModel.new.command
+ end
+
def test_add_remove_single_field_using_string_arguments
refute TestModel.column_methods_hash.key?(:last_name)
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 5fb54b1ca1..6399111be6 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -19,6 +19,11 @@ module ActiveRecord
assert !relation.loaded, 'relation is not loaded'
end
+ def test_responds_to_model_and_returns_klass
+ relation = Relation.new :a, :b
+ assert_equal :a, relation.model
+ end
+
def test_initialize_single_values
relation = Relation.new :a, :b
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 84027ea5ae..684538940a 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -668,6 +668,25 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [developers(:poor_jamis)], dev_with_count.to_a
end
+ def test_relation_merging_with_arel_equalities_keeps_last_equality
+ devs = Developer.where(Developer.arel_table[:salary].eq(80000)).merge(
+ Developer.where(Developer.arel_table[:salary].eq(9000))
+ )
+ assert_equal [developers(:poor_jamis)], devs.to_a
+ end
+
+ def test_relation_merging_with_arel_equalities_keeps_last_equality_with_non_attribute_left_hand
+ salary_attr = Developer.arel_table[:salary]
+ devs = Developer.where(
+ Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(80000)
+ ).merge(
+ Developer.where(
+ Arel::Nodes::NamedFunction.new('abs', [salary_attr]).eq(9000)
+ )
+ )
+ assert_equal [developers(:poor_jamis)], devs.to_a
+ end
+
def test_relation_merging_with_eager_load
relations = []
relations << Post.order('comments.id DESC').merge(Post.eager_load(:last_comment)).merge(Post.all)
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index ce167509c1..10d8ccc711 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -53,9 +53,8 @@ class SerializationTest < ActiveRecord::TestCase
end
def test_serialized_attributes_are_class_level_settings
- assert_raise NoMethodError do
- topic = Topic.new
- topic.serialized_attributes = []
- end
+ topic = Topic.new
+ assert_raise(NoMethodError) { topic.serialized_attributes = [] }
+ assert_deprecated { topic.serialized_attributes }
end
end
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 3e60b62fd5..fb0d116c08 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -122,9 +122,8 @@ class StoreTest < ActiveRecord::TestCase
end
test "stores_attributes are class level settings" do
- assert_raise NoMethodError do
- @john.stored_attributes = {}
- end
+ assert_raise(NoMethodError) { @john.stored_attributes = Hash.new }
+ assert_raise(NoMethodError) { @john.stored_attributes }
end
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index d5597a68ad..0d0de455b3 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -91,18 +91,14 @@ class TransactionTest < ActiveRecord::TestCase
end
def test_raising_exception_in_callback_rollbacks_in_save
- add_exception_raising_after_save_callback_to_topic
-
- begin
- @first.approved = true
- @first.save
- flunk
- rescue => e
- assert_equal "Make the transaction rollback", e.message
- assert !Topic.find(1).approved?
- ensure
- remove_exception_raising_after_save_callback_to_topic
+ def @first.after_save_for_transaction
+ raise 'Make the transaction rollback'
end
+
+ @first.approved = true
+ e = assert_raises(RuntimeError) { @first.save }
+ assert_equal "Make the transaction rollback", e.message
+ assert !Topic.find(1).approved?
end
def test_update_attributes_should_rollback_on_failure
@@ -125,100 +121,83 @@ class TransactionTest < ActiveRecord::TestCase
end
def test_cancellation_from_before_destroy_rollbacks_in_destroy
- add_cancelling_before_destroy_with_db_side_effect_to_topic
- begin
- nbooks_before_destroy = Book.count
- status = @first.destroy
- assert !status
- assert_nothing_raised(ActiveRecord::RecordNotFound) { @first.reload }
- assert_equal nbooks_before_destroy, Book.count
- ensure
- remove_cancelling_before_destroy_with_db_side_effect_to_topic
- end
+ add_cancelling_before_destroy_with_db_side_effect_to_topic @first
+ nbooks_before_destroy = Book.count
+ status = @first.destroy
+ assert !status
+ @first.reload
+ assert_equal nbooks_before_destroy, Book.count
end
- def test_cancellation_from_before_filters_rollbacks_in_save
- %w(validation save).each do |filter|
- send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic")
- begin
- nbooks_before_save = Book.count
- original_author_name = @first.author_name
- @first.author_name += '_this_should_not_end_up_in_the_db'
- status = @first.save
- assert !status
- assert_equal original_author_name, @first.reload.author_name
- assert_equal nbooks_before_save, Book.count
- ensure
- send("remove_cancelling_before_#{filter}_with_db_side_effect_to_topic")
- end
+ %w(validation save).each do |filter|
+ define_method("test_cancellation_from_before_filters_rollbacks_in_#{filter}") do
+ send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic", @first)
+ nbooks_before_save = Book.count
+ original_author_name = @first.author_name
+ @first.author_name += '_this_should_not_end_up_in_the_db'
+ status = @first.save
+ assert !status
+ assert_equal original_author_name, @first.reload.author_name
+ assert_equal nbooks_before_save, Book.count
end
- end
- def test_cancellation_from_before_filters_rollbacks_in_save!
- %w(validation save).each do |filter|
- send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic")
+ define_method("test_cancellation_from_before_filters_rollbacks_in_#{filter}!") do
+ send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic", @first)
+ nbooks_before_save = Book.count
+ original_author_name = @first.author_name
+ @first.author_name += '_this_should_not_end_up_in_the_db'
+
begin
- nbooks_before_save = Book.count
- original_author_name = @first.author_name
- @first.author_name += '_this_should_not_end_up_in_the_db'
@first.save!
- flunk
- rescue
- assert_equal original_author_name, @first.reload.author_name
- assert_equal nbooks_before_save, Book.count
- ensure
- send("remove_cancelling_before_#{filter}_with_db_side_effect_to_topic")
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved
end
+
+ assert_equal original_author_name, @first.reload.author_name
+ assert_equal nbooks_before_save, Book.count
end
end
def test_callback_rollback_in_create
- new_topic = Topic.new(
- :title => "A new topic",
- :author_name => "Ben",
- :author_email_address => "ben@example.com",
- :written_on => "2003-07-16t15:28:11.2233+01:00",
- :last_read => "2004-04-15",
- :bonus_time => "2005-01-30t15:28:00.00+01:00",
- :content => "Have a nice day",
- :approved => false)
+ topic = Class.new(Topic) {
+ def after_create_for_transaction
+ raise 'Make the transaction rollback'
+ end
+ }
+
+ new_topic = topic.new(:title => "A new topic",
+ :author_name => "Ben",
+ :author_email_address => "ben@example.com",
+ :written_on => "2003-07-16t15:28:11.2233+01:00",
+ :last_read => "2004-04-15",
+ :bonus_time => "2005-01-30t15:28:00.00+01:00",
+ :content => "Have a nice day",
+ :approved => false)
+
new_record_snapshot = !new_topic.persisted?
id_present = new_topic.has_attribute?(Topic.primary_key)
id_snapshot = new_topic.id
# Make sure the second save gets the after_create callback called.
2.times do
- begin
- add_exception_raising_after_create_callback_to_topic
- new_topic.approved = true
- new_topic.save
- flunk
- rescue => e
- assert_equal "Make the transaction rollback", e.message
- assert_equal new_record_snapshot, !new_topic.persisted?, "The topic should have its old persisted value"
- assert_equal id_snapshot, new_topic.id, "The topic should have its old id"
- assert_equal id_present, new_topic.has_attribute?(Topic.primary_key)
- ensure
- remove_exception_raising_after_create_callback_to_topic
- end
+ new_topic.approved = true
+ e = assert_raises(RuntimeError) { new_topic.save }
+ assert_equal "Make the transaction rollback", e.message
+ assert_equal new_record_snapshot, !new_topic.persisted?, "The topic should have its old persisted value"
+ assert_equal id_snapshot, new_topic.id, "The topic should have its old id"
+ assert_equal id_present, new_topic.has_attribute?(Topic.primary_key)
end
end
def test_callback_rollback_in_create_with_record_invalid_exception
- begin
- Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1
- remove_method(:after_create_for_transaction)
- def after_create_for_transaction
- raise ActiveRecord::RecordInvalid.new(Author.new)
- end
- eoruby
+ topic = Class.new(Topic) {
+ def after_create_for_transaction
+ raise ActiveRecord::RecordInvalid.new(Author.new)
+ end
+ }
- new_topic = Topic.create(:title => "A new topic")
- assert !new_topic.persisted?, "The topic should not be persisted"
- assert_nil new_topic.id, "The topic should not have an ID"
- ensure
- remove_exception_raising_after_create_callback_to_topic
- end
+ new_topic = topic.create(:title => "A new topic")
+ assert !new_topic.persisted?, "The topic should not be persisted"
+ assert_nil new_topic.id, "The topic should not have an ID"
end
def test_nested_explicit_transactions
@@ -478,62 +457,16 @@ class TransactionTest < ActiveRecord::TestCase
end
private
- def define_callback_method(callback_method)
- define_method(callback_method) do
- self.history << [callback_method, :method]
- end
- end
- def add_exception_raising_after_save_callback_to_topic
- Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1
- remove_method(:after_save_for_transaction)
- def after_save_for_transaction
- raise 'Make the transaction rollback'
- end
- eoruby
- end
-
- def remove_exception_raising_after_save_callback_to_topic
- Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1
- remove_method :after_save_for_transaction
- def after_save_for_transaction; end
- eoruby
- end
-
- def add_exception_raising_after_create_callback_to_topic
- Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1
- remove_method(:after_create_for_transaction)
- def after_create_for_transaction
- raise 'Make the transaction rollback'
- end
- eoruby
- end
-
- def remove_exception_raising_after_create_callback_to_topic
- Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1
- remove_method :after_create_for_transaction
- def after_create_for_transaction; end
- eoruby
- end
-
- %w(validation save destroy).each do |filter|
- define_method("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") do
- Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1
- remove_method :before_#{filter}_for_transaction
- def before_#{filter}_for_transaction
- Book.create
- false
- end
- eoruby
- end
-
- define_method("remove_cancelling_before_#{filter}_with_db_side_effect_to_topic") do
- Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1
- remove_method :before_#{filter}_for_transaction
- def before_#{filter}_for_transaction; end
- eoruby
+ %w(validation save destroy).each do |filter|
+ define_method("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") do |topic|
+ meta = class << topic; self; end
+ meta.send("define_method", "before_#{filter}_for_transaction") do
+ Book.create
+ false
end
end
+ end
end
class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase
diff --git a/activerecord/test/fixtures/friendships.yml b/activerecord/test/fixtures/friendships.yml
new file mode 100644
index 0000000000..1ee09175bf
--- /dev/null
+++ b/activerecord/test/fixtures/friendships.yml
@@ -0,0 +1,4 @@
+Connection 1:
+ id: 1
+ person_id: 1
+ friend_id: 2 \ No newline at end of file
diff --git a/activerecord/test/fixtures/people.yml b/activerecord/test/fixtures/people.yml
index 123673a2af..e640a38f1f 100644
--- a/activerecord/test/fixtures/people.yml
+++ b/activerecord/test/fixtures/people.yml
@@ -4,15 +4,18 @@ michael:
primary_contact_id: 2
number1_fan_id: 3
gender: M
+ followers_count: 1
david:
id: 2
first_name: David
primary_contact_id: 3
number1_fan_id: 1
gender: M
+ followers_count: 1
susan:
id: 3
first_name: Susan
primary_contact_id: 2
number1_fan_id: 1
gender: F
+ followers_count: 1
diff --git a/activerecord/test/fixtures/topics.yml b/activerecord/test/fixtures/topics.yml
index 93f48aedc4..2b042bd135 100644
--- a/activerecord/test/fixtures/topics.yml
+++ b/activerecord/test/fixtures/topics.yml
@@ -25,7 +25,7 @@ third:
id: 3
title: The Third Topic of the day
author_name: Carl
- written_on: 2005-07-15t15:28:00.0099+01:00
+ written_on: 2012-08-12t20:24:22.129346+00:00
content: I'm a troll
approved: true
replies_count: 1
diff --git a/activerecord/test/models/friendship.rb b/activerecord/test/models/friendship.rb
new file mode 100644
index 0000000000..6b4f7acc38
--- /dev/null
+++ b/activerecord/test/models/friendship.rb
@@ -0,0 +1,4 @@
+class Friendship < ActiveRecord::Base
+ belongs_to :friend, class_name: 'Person'
+ belongs_to :follower, foreign_key: 'friend_id', class_name: 'Person', counter_cache: :followers_count
+end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index e204508986..6e6ff29f77 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -8,6 +8,8 @@ class Person < ActiveRecord::Base
has_many :posts_with_no_comments, -> { includes(:comments).where('comments.id is null').references(:comments) },
:through => :readers, :source => :post
+ has_many :followers, foreign_key: 'friend_id', class_name: 'Friendship'
+
has_many :references
has_many :bad_references
has_many :fixed_bad_references, -> { where :favourite => true }, :class_name => 'BadReference'
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 6c919a2b02..7c45ca27c0 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -270,6 +270,11 @@ ActiveRecord::Schema.define do
t.string :name
end
+ create_table :friendships, :force => true do |t|
+ t.integer :friend_id
+ t.integer :person_id
+ end
+
create_table :goofy_string_id, :force => true, :id => false do |t|
t.string :id, :null => false
t.string :info
@@ -476,6 +481,7 @@ ActiveRecord::Schema.define do
t.references :number1_fan
t.integer :lock_version, :null => false, :default => 0
t.string :comments
+ t.integer :followers_count, :default => 0
t.references :best_friend
t.references :best_friend_of
t.timestamps
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index b0b21a1563..4f57efb35d 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,15 @@
## Rails 4.0.0 (unreleased) ##
+* Remove `j` alias for `ERB::Util#json_escape`.
+ The `j` alias is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`
+ and both modules are included in the view context that would confuse the developers.
+
+ *Akira Matsuda*
+
+* Replace deprecated `memcache-client` gem with `dalli` in ActiveSupport::Cache::MemCacheStore
+
+ *Guillermo Iguaran*
+
* Add default values to all `ActiveSupport::NumberHelper` methods, to avoid
errors with empty locales or missing values.
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 56d6676961..41d77ab6c1 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -37,7 +37,6 @@ module ActiveSupport
autoload :LogSubscriber
autoload :Notifications
- # TODO: Narrow this list down
eager_autoload do
autoload :BacktraceCleaner
autoload :BasicObject
@@ -55,12 +54,12 @@ module ActiveSupport
autoload :OptionMerger
autoload :OrderedHash
autoload :OrderedOptions
- autoload :Rescuable
autoload :StringInquirer
autoload :TaggedLogging
autoload :XmlMini
end
+ autoload :Rescuable
autoload :SafeBuffer, "active_support/core_ext/string/output_safety"
autoload :TestCase
end
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 2e1ccb72d8..5aa78cc9f3 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -1,7 +1,7 @@
begin
- require 'memcache'
+ require 'dalli'
rescue LoadError => e
- $stderr.puts "You don't have memcache-client installed in your application. Please add it to your Gemfile and run bundle install"
+ $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end
@@ -22,21 +22,13 @@ module ActiveSupport
# MemCacheStore implements the Strategy::LocalCache strategy which implements
# an in-memory cache inside of a block.
class MemCacheStore < Store
- module Response # :nodoc:
- STORED = "STORED\r\n"
- NOT_STORED = "NOT_STORED\r\n"
- EXISTS = "EXISTS\r\n"
- NOT_FOUND = "NOT_FOUND\r\n"
- DELETED = "DELETED\r\n"
- end
-
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
def self.build_mem_cache(*addresses)
addresses = addresses.flatten
options = addresses.extract_options!
addresses = ["localhost:11211"] if addresses.empty?
- MemCache.new(addresses, options)
+ Dalli::Client.new(addresses, options)
end
# Creates a new MemCacheStore object, with the given memcached server
@@ -90,11 +82,11 @@ module ActiveSupport
# to zero.
def increment(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
- response = instrument(:increment, name, :amount => amount) do
+ instrument(:increment, name, :amount => amount) do
@data.incr(escape_key(namespaced_key(name, options)), amount)
end
- response == Response::NOT_FOUND ? nil : response.to_i
- rescue MemCache::MemCacheError
+ rescue Dalli::DalliError
+ logger.error("DalliError (#{e}): #{e.message}") if logger
nil
end
@@ -104,11 +96,11 @@ module ActiveSupport
# to zero.
def decrement(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
- response = instrument(:decrement, name, :amount => amount) do
+ instrument(:decrement, name, :amount => amount) do
@data.decr(escape_key(namespaced_key(name, options)), amount)
end
- response == Response::NOT_FOUND ? nil : response.to_i
- rescue MemCache::MemCacheError
+ rescue Dalli::DalliError
+ logger.error("DalliError (#{e}): #{e.message}") if logger
nil
end
@@ -116,6 +108,9 @@ module ActiveSupport
# be used with care when shared cache is being used.
def clear(options = nil)
@data.flush_all
+ rescue Dalli::DalliError => e
+ logger.error("DalliError (#{e}): #{e.message}") if logger
+ nil
end
# Get the statistics from the memcached servers.
@@ -126,9 +121,9 @@ module ActiveSupport
protected
# Read an entry from the cache.
def read_entry(key, options) # :nodoc:
- deserialize_entry(@data.get(escape_key(key), true))
- rescue MemCache::MemCacheError => e
- logger.error("MemCacheError (#{e}): #{e.message}") if logger
+ deserialize_entry(@data.get(escape_key(key), options))
+ rescue Dalli::DalliError => e
+ logger.error("DalliError (#{e}): #{e.message}") if logger
nil
end
@@ -137,23 +132,17 @@ module ActiveSupport
method = options && options[:unless_exist] ? :add : :set
value = options[:raw] ? entry.value.to_s : entry
expires_in = options[:expires_in].to_i
- if expires_in > 0 && !options[:raw]
- # Set the memcache expire a few minutes in the future to support race condition ttls on read
- expires_in += 5.minutes
- end
- response = @data.send(method, escape_key(key), value, expires_in, options[:raw])
- response == Response::STORED
- rescue MemCache::MemCacheError => e
- logger.error("MemCacheError (#{e}): #{e.message}") if logger
+ @data.send(method, escape_key(key), value, expires_in, options)
+ rescue Dalli::DalliError => e
+ logger.error("DalliError (#{e}): #{e.message}") if logger
false
end
# Delete an entry from the cache.
def delete_entry(key, options) # :nodoc:
- response = @data.delete(escape_key(key))
- response == Response::DELETED
- rescue MemCache::MemCacheError => e
- logger.error("MemCacheError (#{e}): #{e.message}") if logger
+ @data.delete(escape_key(key))
+ rescue Dalli::DalliError => e
+ logger.error("DalliError (#{e}): #{e.message}") if logger
false
end
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index c17d695967..dad4b29d46 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -60,18 +60,11 @@ class ERB
# json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}')
# # => {name:john,created_at:2010-04-28T01:39:31Z,id:1}
#
- # This method is also aliased as +j+, and available as a helper
- # in Rails templates:
- #
- # <%=j @person.to_json %>
- #
def json_escape(s)
result = s.to_s.gsub(JSON_ESCAPE_REGEXP) { |special| JSON_ESCAPE[special] }
s.html_safe? ? result.html_safe : result
end
- alias j json_escape
- module_function :j
module_function :json_escape
end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index c9071a73d8..77cab6f08d 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -168,16 +168,30 @@ module ActiveSupport #:nodoc:
end
end
- def const_missing(const_name, nesting = nil)
+ def const_missing(const_name)
klass_name = name.presence || "Object"
- unless nesting
- # We'll assume that the nesting of Foo::Bar is ["Foo::Bar", "Foo"]
- # even though it might not be, such as in the case of
- # class Foo::Bar; Baz; end
- nesting = []
- klass_name.to_s.scan(/::|$/) { nesting.unshift $` }
- end
+ # Since Ruby does not pass the nesting at the point the unknown
+ # constant triggered the callback we cannot fully emulate constant
+ # name lookup and need to make a trade-off: we are going to assume
+ # that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even
+ # though it might not be. Counterexamples are
+ #
+ # class Foo::Bar
+ # Module.nesting # => [Foo::Bar]
+ # end
+ #
+ # or
+ #
+ # module M::N
+ # module S::T
+ # Module.nesting # => [S::T, M::N]
+ # end
+ # end
+ #
+ # for example.
+ nesting = []
+ klass_name.to_s.scan(/::|$/) { nesting.unshift $` }
# If there are multiple levels of nesting to search under, the top
# level is the one we want to report as the lookup fail.
@@ -287,13 +301,11 @@ module ActiveSupport #:nodoc:
Object.class_eval { include Loadable }
Module.class_eval { include ModuleConstMissing }
Exception.class_eval { include Blamable }
- true
end
def unhook!
ModuleConstMissing.exclude_from(Module)
Loadable.exclude_from(Object)
- true
end
def load?
diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb
index 4045db3232..df490ae298 100644
--- a/activesupport/lib/active_support/dependencies/autoload.rb
+++ b/activesupport/lib/active_support/dependencies/autoload.rb
@@ -1,52 +1,78 @@
require "active_support/inflector/methods"
module ActiveSupport
+ # Autoload and eager load conveniences for your library.
+ #
+ # This module allows you to define autoloads based on
+ # Rails conventions (i.e. no need to define the path
+ # it is automatically guessed based on the filename)
+ # and also define a set of constants that needs to be
+ # eager loaded:
+ #
+ # module MyLib
+ # extend ActiveSupport::Autoload
+ #
+ # autoload :Model
+ #
+ # eager_autoload do
+ # autoload :Cache
+ # end
+ # end
+ #
+ # Then your library can be eager loaded by simply calling:
+ #
+ # MyLib.eager_load!
+ #
module Autoload
- @@autoloads = {}
- @@under_path = nil
- @@at_path = nil
- @@eager_autoload = false
+ def self.extended(base)
+ base.class_eval do
+ @_autoloads = {}
+ @_under_path = nil
+ @_at_path = nil
+ @_eager_autoload = false
+ end
+ end
- def autoload(const_name, path = @@at_path)
+ def autoload(const_name, path = @_at_path)
unless path
- full = [name, @@under_path, const_name.to_s, path].compact.join("::")
+ full = [name, @_under_path, const_name.to_s, path].compact.join("::")
path = Inflector.underscore(full)
end
- if @@eager_autoload
- @@autoloads[const_name] = path
+ if @_eager_autoload
+ @_autoloads[const_name] = path
end
super const_name, path
end
def autoload_under(path)
- @@under_path, old_path = path, @@under_path
+ @_under_path, old_path = path, @_under_path
yield
ensure
- @@under_path = old_path
+ @_under_path = old_path
end
def autoload_at(path)
- @@at_path, old_path = path, @@at_path
+ @_at_path, old_path = path, @_at_path
yield
ensure
- @@at_path = old_path
+ @_at_path = old_path
end
def eager_autoload
- old_eager, @@eager_autoload = @@eager_autoload, true
+ old_eager, @_eager_autoload = @_eager_autoload, true
yield
ensure
- @@eager_autoload = old_eager
+ @_eager_autoload = old_eager
end
- def self.eager_autoload!
- @@autoloads.values.each { |file| require file }
+ def eager_load!
+ @_autoloads.values.each { |file| require file }
end
def autoloads
- @@autoloads
+ @_autoloads
end
end
end
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 30ac881090..aa8d408da9 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -5,6 +5,8 @@ module ActiveSupport
class Railtie < Rails::Railtie
config.active_support = ActiveSupport::OrderedOptions.new
+ config.eager_load_namespaces << ActiveSupport
+
initializer "active_support.deprecation_behavior" do |app|
if deprecation = app.config.active_support.deprecation
ActiveSupport::Deprecation.behavior = deprecation
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 25ed962c23..34c9e920bc 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -22,11 +22,11 @@ ENV['NO_RELOAD'] = '1'
require 'active_support'
def uses_memcached(test_name)
- require 'memcache'
+ require 'dalli'
begin
- MemCache.new('localhost:11211').stats
+ Dalli::Client.new('localhost:11211').stats
yield
- rescue MemCache::MemCacheError
+ rescue Dalli::DalliError
$stderr.puts "Skipping #{test_name} tests. Start memcached and try again."
end
end
diff --git a/activesupport/test/autoload.rb b/activesupport/test/autoload_test.rb
index 5d8026a9ca..7d02d835a8 100644
--- a/activesupport/test/autoload.rb
+++ b/activesupport/test/autoload_test.rb
@@ -28,15 +28,6 @@ class TestAutoloadModule < ActiveSupport::TestCase
assert_nothing_raised { ::Fixtures::Autoload::SomeClass }
end
- test ":eager constants can be triggered via ActiveSupport::Autoload.eager_autoload!" do
- module ::Fixtures::Autoload
- autoload :SomeClass, "fixtures/autoload/some_class"
- end
- ActiveSupport::Autoload.eager_autoload!
- assert $LOADED_FEATURES.include?("fixtures/autoload/some_class.rb")
- assert_nothing_raised { ::Fixtures::Autoload::SomeClass }
- end
-
test "the location of autoloaded constants defaults to :name.underscore" do
module ::Fixtures::Autoload
autoload :SomeClass
@@ -51,8 +42,7 @@ class TestAutoloadModule < ActiveSupport::TestCase
autoload :SomeClass
end
- ActiveSupport::Autoload.eager_autoload!
- assert $LOADED_FEATURES.include?("fixtures/autoload/some_class.rb")
+ ::Fixtures::Autoload.eager_load!
assert_nothing_raised { ::Fixtures::Autoload::SomeClass }
end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index a75db47be8..5056d8deb8 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -83,20 +83,20 @@ class CacheStoreSettingTest < ActiveSupport::TestCase
end
def test_mem_cache_fragment_cache_store
- MemCache.expects(:new).with(%w[localhost], {})
+ Dalli::Client.expects(:new).with(%w[localhost], {})
store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
end
def test_mem_cache_fragment_cache_store_with_given_mem_cache
- mem_cache = MemCache.new
- MemCache.expects(:new).never
+ mem_cache = Dalli::Client.new
+ Dalli::Client.expects(:new).never
store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
end
def test_mem_cache_fragment_cache_store_with_given_mem_cache_like_object
- MemCache.expects(:new).never
+ Dalli::Client.expects(:new).never
memcache = Object.new
def memcache.get() true end
store = ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
@@ -104,13 +104,13 @@ class CacheStoreSettingTest < ActiveSupport::TestCase
end
def test_mem_cache_fragment_cache_store_with_multiple_servers
- MemCache.expects(:new).with(%w[localhost 192.168.1.1], {})
+ Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], {})
store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1'
assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
end
def test_mem_cache_fragment_cache_store_with_options
- MemCache.expects(:new).with(%w[localhost 192.168.1.1], { :timeout => 10 })
+ Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], { :timeout => 10 })
store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10
assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
assert_equal 'foo', store.options[:namespace]
@@ -447,6 +447,7 @@ module CacheIncrementDecrementBehavior
assert_equal 2, @cache.read('foo').to_i
assert_equal 3, @cache.increment('foo')
assert_equal 3, @cache.read('foo').to_i
+ assert_nil @cache.increment('bar')
end
def test_decrement
@@ -456,6 +457,7 @@ module CacheIncrementDecrementBehavior
assert_equal 2, @cache.read('foo').to_i
assert_equal 1, @cache.decrement('foo')
assert_equal 1, @cache.read('foo').to_i
+ assert_nil @cache.decrement('bar')
end
end
diff --git a/guides/source/4_0_release_notes.textile b/guides/source/4_0_release_notes.textile
index 0de8a0a4b7..2f21f8cc71 100644
--- a/guides/source/4_0_release_notes.textile
+++ b/guides/source/4_0_release_notes.textile
@@ -82,8 +82,6 @@ will generate the model with <tt>belongs_to :supplier, polymorphic: true</tt> as
* Load all environments available in <tt>config.paths["config/environments"]</tt>.
-* The application generator generates <tt>public/humans.txt</tt> with some basic data.
-
* Add <tt>config.queue_consumer</tt> to allow the default consumer to be configurable.
* Add <tt>Rails.queue</tt> as an interface with a default implementation that consumes jobs in a separate thread.
diff --git a/guides/source/active_record_validations_callbacks.textile b/guides/source/active_record_validations_callbacks.textile
index cf7293bd9e..b866337e3f 100644
--- a/guides/source/active_record_validations_callbacks.textile
+++ b/guides/source/active_record_validations_callbacks.textile
@@ -532,6 +532,16 @@ end
Person.new.valid? => ActiveModel::StrictValidationFailed: Name can't be blank
</ruby>
+There is also an ability to pass custom exception to +:strict+ option
+
+<ruby>
+class Person < ActiveRecord::Base
+ validates :token, :presence => true, :uniqueness => true, :strict => TokenGenerationException
+end
+
+Person.new.valid? => TokenGenerationException: Token can't be blank
+</ruby>
+
h3. Conditional Validation
Sometimes it will make sense to validate an object just when a given predicate is satisfied. You can do that by using the +:if+ and +:unless+ options, which can take a symbol, a string, a +Proc+ or an +Array+. You may use the +:if+ option when you want to specify when the validation *should* happen. If you want to specify when the validation *should not* happen, then you may use the +:unless+ option.
diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile
index 5ed3ad4a6b..27eaf1cbc5 100644
--- a/guides/source/configuring.textile
+++ b/guides/source/configuring.textile
@@ -50,8 +50,6 @@ config.after_initialize do
end
</ruby>
-* +config.allow_concurrency+ should be true to allow concurrent (threadsafe) action processing. False by default. You probably don't want to call this one directly, though, because a series of other adjustments need to be made for threadsafe mode to work properly. Can also be enabled with +threadsafe!+.
-
* +config.asset_host+ sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints builtin in browsers using different domain aliases. Shorter version of +config.action_controller.asset_host+.
* +config.asset_path+ lets you decorate asset paths. This can be a callable, a string, or be +nil+ which is the default. For example, the normal path for +blog.js+ would be +/javascripts/blog.js+, let that absolute path be +path+. If +config.asset_path+ is a callable, Rails calls it when generating asset paths passing +path+ as argument. If +config.asset_path+ is a string, it is expected to be a +sprintf+ format string with a +%s+ where +path+ will get inserted. In either case, Rails outputs the decorated path. Shorter version of +config.action_controller.asset_path+.
@@ -89,6 +87,10 @@ end
* +config.dependency_loading+ is a flag that allows you to disable constant autoloading setting it to false. It only has effect if +config.cache_classes+ is true, which it is by default in production mode. This flag is set to false by +config.threadsafe!+.
+* +config.eager_load+ when true, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks and any other registered namespace.
+
+* +config.eager_load_namespaces+ registers namespaces that are eager loaded when +config.eager_load+ is true. All namespaces in the list must respond to the +eager_load!+ method.
+
* +config.eager_load_paths+ accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. Defaults to every folder in the +app+ directory of the application.
* +config.encoding+ sets up the application-wide encoding. Defaults to UTF-8.
@@ -109,8 +111,6 @@ end
* +config.middleware+ allows you to configure the application's middleware. This is covered in depth in the "Configuring Middleware":#configuring-middleware section below.
-* +config.preload_frameworks+ enables or disables preloading all frameworks at startup. Enabled by +config.threadsafe!+. Defaults to +nil+, so is disabled.
-
* +config.queue+ configures a different queue implementation for the application. Defaults to +Rails::Queueing::Queue+. Note that, if the default queue is changed, the default +queue_consumer+ is not going to be initialized, it is up to the new queue implementation to handle starting and shutting down its own consumer(s).
* +config.queue_consumer+ configures a different consumer implementation for the default queue. Defaults to +Rails::Queueing::ThreadedConsumer+.
@@ -129,10 +129,6 @@ config.session_store :my_custom_store
This custom store must be defined as +ActionDispatch::Session::MyCustomStore+. In addition to symbols, they can also be objects implementing a certain API, like +ActiveRecord::SessionStore+, in which case no special namespace is required.
-* +config.threadsafe!+ enables +allow_concurrency+, +cache_classes+, +dependency_loading+ and +preload_frameworks+ to make the application threadsafe.
-
-WARNING: Threadsafe operation is incompatible with the normal workings of development mode Rails. In particular, automatic dependency loading and class reloading are automatically disabled when you call +config.threadsafe!+.
-
* +config.time_zone+ sets the default time zone for the application and enables time zone awareness for Active Record.
* +config.whiny_nils+ enables or disables warnings when a certain set of methods are invoked on +nil+ and it does not respond to them. Defaults to true in development and test environments.
@@ -203,7 +199,7 @@ Every Rails application comes with a standard set of middleware which it uses in
* +ActionDispatch::SSL+ forces every request to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to +true+. Options passed to this can be configured by using +config.ssl_options+.
* +ActionDispatch::Static+ is used to serve static assets. Disabled if +config.serve_static_assets+ is +true+.
-* +Rack::Lock+ wraps the app in mutex so it can only be called by a single thread at a time. Only enabled if +config.action_controller.allow_concurrency+ is set to +false+, which it is by default.
+* +Rack::Lock+ wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when +config.cache_classes_+ is +false+.
* +ActiveSupport::Cache::Strategy::LocalCache+ serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread.
* +Rack::Runtime+ sets an +X-Runtime+ header, containing the time (in seconds) taken to execute the request.
* +Rails::Rack::Logger+ notifies the logs that the request has began. After request is complete, flushes all the logs.
@@ -341,7 +337,7 @@ h4. Configuring Action Dispatch
* +config.action_dispatch.default_headers+ is a hash with HTTP headers that are set by default in each response. By default, this is defined as:
<ruby>
-config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block' }
+config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block', 'X-Content-Type-Options' => 'nosniff' }
</ruby>
* +config.action_dispatch.tld_length+ sets the TLD (top-level domain) length for the application. Defaults to +1+.
@@ -656,8 +652,6 @@ Serves as a placeholder so that +:load_environment_config+ can be defined to run
*+load_active_support+* Requires +active_support/dependencies+ which sets up the basis for Active Support. Optionally requires +active_support/all+ if +config.active_support.bare+ is un-truthful, which is the default.
-*+preload_frameworks+* Loads all autoload dependencies of Rails automatically if +config.preload_frameworks+ is +true+ or "truthful". By default this configuration option is disabled. In Rails, when internal classes are referenced for the first time they are autoloaded. +:preload_frameworks+ loads all of this at once on initialization.
-
*+initialize_logger+* Initializes the logger (an +ActiveSupport::BufferedLogger+ object) for the application and makes it accessible at +Rails.logger+, provided that no initializer inserted before this point has defined +Rails.logger+.
*+initialize_cache+* If +Rails.cache+ isn't set yet, initializes the cache by referencing the value in +config.cache_store+ and stores the outcome as +Rails.cache+. If this object responds to the +middleware+ method, its middleware is inserted before +Rack::Runtime+ in the middleware stack.
@@ -752,13 +746,13 @@ The error occurred while evaluating nil.each
*+build_middleware_stack+* Builds the middleware stack for the application, returning an object which has a +call+ method which takes a Rack environment object for the request.
-*+eager_load!+* If +config.cache_classes+ is true, runs the +config.before_eager_load+ hooks and then calls +eager_load!+ which will load all the Ruby files from +config.eager_load_paths+.
+*+eager_load!+* If +config.eager_load+ is true, runs the +config.before_eager_load+ hooks and then calls +eager_load!+ which will load all +config.eager_load_namespaces+.
*+finisher_hook+* Provides a hook for after the initialization of process of the application is complete, as well as running all the +config.after_initialize+ blocks for the application, railties and engines.
*+set_routes_reloader+* Configures Action Dispatch to reload the routes file using +ActionDispatch::Callbacks.to_prepare+.
-*+disable_dependency_loading+* Disables the automatic dependency loading if the +config.cache_classes+ is set to true and +config.dependency_loading+ is set to false.
+*+disable_dependency_loading+* Disables the automatic dependency loading if the +config.eager_load+ is set to true.
h3. Database pooling
diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile
index 5024bc4c37..cdf7306264 100644
--- a/guides/source/upgrading_ruby_on_rails.textile
+++ b/guides/source/upgrading_ruby_on_rails.textile
@@ -44,6 +44,8 @@ The <tt>delete</tt> method in collection associations can now receive <tt>Fixnum
Rails 4.0 has changed how orders get stacked in +ActiveRecord::Relation+. In previous versions of rails new order was applied after previous defined order. But this is no long true. Check "ActiveRecord Query guide":active_record_querying.html#ordering for more information.
+Rails 4.0 has changed <tt>serialized_attributes</tt> and <tt>_attr_readonly</tt> to class methods only. Now you shouldn't use instance methods, it's deprecated. You must change them, e.g. <tt>self.serialized_attributes</tt> to <tt>self.class.serialized_attributes</tt>.
+
h4(#active_model4_0). Active Model
Rails 4.0 has changed how errors attach with the <tt>ActiveModel::Validations::ConfirmationValidator</tt>. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>.
@@ -54,6 +56,10 @@ Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and
Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, e.g. <tt>get Rack::Utils.escape('こんにちは'), :controller => 'welcome', :action => 'index'</tt> to <tt>get 'こんにちは', :controller => 'welcome', :action => 'index'</tt>.
+h4(#active_support4_0). Active Support
+
+Rails 4.0 Removed the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`.
+
h4(#helpers_order). Helpers Loading Order
The loading order of helpers from more than one directory has changed in Rails 4.0. Previously, helpers from all directories were gathered and then sorted alphabetically. After upgrade to Rails 4.0 helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use <tt>helpers_path</tt> parameter, this change will only impact the way of loading helpers from engines. If you rely on the fact that particular helper from engine loads before or after another helper from application or another engine, you should check if correct methods are available after upgrade. If you would like to change order in which engines are loaded, you can use <tt>config.railties_order=</tt> method.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 851f41249a..fea18b5f47 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -5,6 +5,8 @@
default_url_options[:script_name] to set proper application's mount point by
yourself. *Piotr Sarnacki*
+* `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded *José Valim*
+
* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance
rails g migration AddReferencesToProducts user:references supplier:references{polymorphic}
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 3531728421..ae872f2bb0 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -16,7 +16,7 @@ module Rails
#
# Besides providing the same configuration as Rails::Engine and Rails::Railtie,
# the application object has several specific configurations, for example
- # "allow_concurrency", "cache_classes", "consider_all_requests_local", "filter_parameters",
+ # "cache_classes", "consider_all_requests_local", "filter_parameters",
# "logger" and so forth.
#
# Check Rails::Application::Configuration to see them all.
@@ -216,8 +216,9 @@ module Rails
railties.each { |r| r.run_tasks_blocks(app) }
super
require "rails/tasks"
+ config = self.config
task :environment do
- $rails_rake_task = true
+ config.eager_load = false
require_environment!
end
end
@@ -296,7 +297,7 @@ module Rails
middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control
end
- middleware.use ::Rack::Lock unless config.allow_concurrency
+ middleware.use ::Rack::Lock unless config.cache_classes
middleware.use ::Rack::Runtime
middleware.use ::Rack::MethodOverride
middleware.use ::ActionDispatch::RequestId
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index e567df7162..a1bc95550b 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -13,11 +13,18 @@ module Rails
require "active_support/all" unless config.active_support.bare
end
- # Preload all frameworks specified by the Configuration#frameworks.
- # Used by Passenger to ensure everything's loaded before forking and
- # to avoid autoload race conditions in JRuby.
- initializer :preload_frameworks, :group => :all do
- ActiveSupport::Autoload.eager_autoload! if config.preload_frameworks
+ initializer :set_eager_load, :group => :all do
+ if config.eager_load.nil?
+ warn <<-INFO
+config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly:
+
+ * development - set it to false
+ * test - set it to false (unless you use a tool that preloads your test environment)
+ * production - set it to true
+
+INFO
+ config.eager_load = config.cache_classes
+ end
end
# Initialize the logger early in the stack in case we need to log some deprecation.
@@ -60,7 +67,6 @@ module Rails
end
# Sets the dependency loading mechanism.
- # TODO: Remove files from the $" and always use require.
initializer :initialize_dependency_mechanism, :group => :all do
ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 2b36cc9d0d..7f05b2e7e1 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -5,11 +5,11 @@ require 'rails/engine/configuration'
module Rails
class Application
class Configuration < ::Rails::Engine::Configuration
- attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets, :autoflush_log,
+ attr_accessor :asset_host, :asset_path, :assets, :autoflush_log,
:cache_classes, :cache_store, :consider_all_requests_local, :console,
- :dependency_loading, :exceptions_app, :file_watcher, :filter_parameters,
+ :eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
- :preload_frameworks, :railties_order, :relative_url_root, :secret_token,
+ :railties_order, :relative_url_root, :secret_token,
:serve_static_assets, :ssl_options, :static_cache_control, :session_options,
:time_zone, :reload_classes_only_on_change,
:queue, :queue_consumer
@@ -20,11 +20,9 @@ module Rails
def initialize(*)
super
self.encoding = "utf-8"
- @allow_concurrency = false
@consider_all_requests_local = false
@filter_parameters = []
@helpers_paths = []
- @dependency_loading = true
@serve_static_assets = true
@static_cache_control = nil
@force_ssl = false
@@ -45,6 +43,7 @@ module Rails
@log_formatter = ActiveSupport::Logger::SimpleFormatter.new
@queue = Rails::Queueing::Queue
@queue_consumer = Rails::Queueing::ThreadedConsumer
+ @eager_load = nil
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false
@@ -91,15 +90,12 @@ module Rails
end
end
- # Enable threaded mode. Allows concurrent requests to controller actions and
- # multiple database connections. Also disables automatic dependency loading
- # after boot, and disables reloading code on every request, as these are
- # fundamentally incompatible with thread safety.
def threadsafe!
- @preload_frameworks = true
+ ActiveSupport::Deprecation.warn "config.threadsafe! is deprecated. Rails applications " \
+ "behave by default as thread safe in production as long as config.cache_classes and " \
+ "config.eager_load are set to true"
@cache_classes = true
- @dependency_loading = false
- @allow_concurrency = true
+ @eager_load = true
self
end
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 60aa40b92f..1952a0fc3a 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -50,9 +50,9 @@ module Rails
end
initializer :eager_load! do
- if config.cache_classes && !(defined?($rails_rake_task) && $rails_rake_task)
+ if config.eager_load
ActiveSupport.run_load_hooks(:before_eager_load, self)
- eager_load!
+ config.eager_load_namespaces.each(&:eager_load!)
end
end
@@ -91,7 +91,7 @@ module Rails
# Disable dependency loading during request cycle
initializer :disable_dependency_loading do
- if config.cache_classes && !config.dependency_loading
+ if config.eager_load
ActiveSupport::Dependencies.unhook!
end
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 9ef64da3ef..a684129353 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -87,6 +87,15 @@ module Rails
middlewares = []
middlewares << [Rails::Rack::Debugger] if options[:debugger]
middlewares << [::Rack::ContentLength]
+
+ # FIXME: add Rack::Lock in the case people are using webrick.
+ # This is to remain backwards compatible for those who are
+ # running webrick in production. We should consider removing this
+ # in development.
+ if server.name == 'Rack::Handler::WEBrick'
+ middlewares << [::Rack::Lock]
+ end
+
Hash.new(middlewares)
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 40f35ae5a6..3a5caf9f62 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -339,11 +339,16 @@ module Rails
class << self
attr_accessor :called_from, :isolated
+
alias :isolated? :isolated
alias :engine_name :railtie_name
+ delegate :eager_load!, to: :instance
+
def inherited(base)
unless base.abstract_railtie?
+ Rails::Railtie::Configuration.eager_load_namespaces << base
+
base.called_from = begin
# Remove the line number from backtraces making sure we don't leave anything behind
call_stack = caller.map { |p| p.sub(/:\d+.*/, '') }
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 5838c9fc38..383bea9e54 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -140,14 +140,14 @@ module Rails
gem 'rails', path: '#{Rails::Generators::RAILS_DEV_PATH}'
gem 'journey', github: 'rails/journey'
gem 'arel', github: 'rails/arel'
- gem 'active_record_deprecated_finders', github: 'rails/active_record_deprecated_finders'
+ gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders'
GEMFILE
elsif options.edge?
<<-GEMFILE.strip_heredoc
gem 'rails', github: 'rails/rails'
gem 'journey', github: 'rails/journey'
gem 'arel', github: 'rails/arel'
- gem 'active_record_deprecated_finders', github: 'rails/active_record_deprecated_finders'
+ gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders'
GEMFILE
else
<<-GEMFILE.strip_heredoc
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index b2b760ee7b..1ac0248bcf 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -60,7 +60,7 @@ module <%= app_const_base %>
config.assets.version = '1.0'
<% end -%>
- # Enable app-wide asynchronous ActionMailer
+ # Enable app-wide asynchronous ActionMailer.
# config.action_mailer.async = true
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
index 1684986a59..e080ebd74e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb
@@ -1,5 +1,5 @@
-# Load the rails application
+# Load the rails application.
require File.expand_path('../application', __FILE__)
-# Initialize the rails application
+# Initialize the rails application.
<%= app_const %>.initialize!
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index fee9eb9456..122e7e2b34 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -6,6 +6,9 @@
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
+ # Do not eager load code on boot.
+ config.eager_load = false
+
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
@@ -39,6 +42,6 @@
config.assets.debug = true
<%- end -%>
- # In development, use an in-memory queue for queueing
+ # In development, use an in-memory queue for queueing.
config.queue = Rails::Queueing::Queue
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 2ffae66a57..a627636089 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -4,6 +4,12 @@
# Code is not reloaded between requests.
config.cache_classes = true
+ # Eager load code on boot. This eager loads most of Rails and
+ # your application in memory, allowing both thread web servers
+ # and those relying on copy on write to perform better.
+ # Rake tasks automatically ignore this option for performance.
+ config.eager_load = true
+
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
@@ -71,10 +77,10 @@
# Disable automatic flushing of the log to improve performance.
# config.autoflush_log = false
- # Use default logging formatter so that PID and timestamp are not suppressed
+ # Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
# Default the production mode queue to an in-memory queue. You will probably
- # want to replace this with an out-of-process queueing solution
+ # want to replace this with an out-of-process queueing solution.
config.queue = Rails::Queueing::Queue
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index b27b88a3c6..8ab27eb6e1 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -7,6 +7,11 @@
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
+ # Do not eager load code on boot. This avoids loading your whole application
+ # just for the purpose of running a single test. If you are using a tool that
+ # preloads Rails for running tests, you may have to set it to true.
+ config.eager_load = false
+
# Configure static asset server for tests with Cache-Control for performance.
config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"
@@ -34,6 +39,6 @@
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
- # Use the testing queue
+ # Use the testing queue.
config.queue = Rails::Queueing::TestQueue
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
index e02397aaf9..21a5de4cba 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
@@ -2,8 +2,11 @@
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
+
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
+# You can generate a safe secret key with the command `rake secret`.
+
# Make sure your secret_token is kept private
# if you're sharing your code publicly.
<%= app_const %>.config.secret_token = '<%= app_secret %>'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
index ade0c4f78c..ff676280cb 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt
@@ -4,5 +4,5 @@
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
-# (create the session table with "rails generate session_migration")
+# (create the session table with "rails generate session_migration").
# <%= app_const %>.config.session_store :active_record_store
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
index 85acac6725..f6b1ef1feb 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -8,11 +8,11 @@
# Sample of regular route:
# get 'products/:id' => 'catalog#view'
- # Keep in mind you can assign values other than :controller and :action
+ # Keep in mind you can assign values other than :controller and :action.
# Sample of named route:
# get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
- # This route can be invoked with purchase_url(id: product.id)
+ # This route can be invoked with purchase_url(id: product.id).
# Sample resource route (maps HTTP verbs to controller actions automatically):
# resources :products
@@ -35,7 +35,7 @@
# resource :seller
# end
- # Sample resource route with more complex sub-resources
+ # Sample resource route with more complex sub-resources:
# resources :products do
# resources :comments
# resources :sales do
@@ -51,5 +51,5 @@
# end
- # See how all your routes lay out with "rake routes"
+ # See how all your routes lay out with "rake routes".
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index ab0e440bc4..4f937ad65a 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -155,7 +155,7 @@ task :default => :test
:desc => "Create dummy application at given path"
class_option :full, :type => :boolean, :default => false,
- :desc => "Generate rails engine with integration tests"
+ :desc => "Generate a rails engine with bundled Rails application for testing"
class_option :mountable, :type => :boolean, :default => false,
:desc => "Generate mountable isolated application"
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 1cb99463cc..fba685c769 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -178,9 +178,6 @@ module Rails
@config ||= Railtie::Configuration.new
end
- def eager_load!
- end
-
def railtie_namespace
@railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) }
end
diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb
index 1c6b3769a5..9dc8843887 100644
--- a/railties/lib/rails/railtie/configuration.rb
+++ b/railties/lib/rails/railtie/configuration.rb
@@ -7,6 +7,16 @@ module Rails
@@options ||= {}
end
+ # Expose the eager_load_namespaces at "module" level for convenience.
+ def self.eager_load_namespaces #:nodoc:
+ @@eager_load_namespaces ||= []
+ end
+
+ # All namespaces that are eager loaded
+ def eager_load_namespaces
+ @@eager_load_namespaces ||= []
+ end
+
# Add files that should be watched for change.
def watchable_files
@@watchable_files ||= []
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index c813defe93..ebdbbaee8b 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -139,22 +139,19 @@ module ApplicationTests
assert_instance_of Pathname, Rails.root
end
- test "marking the application as threadsafe sets the correct config variables" do
+ test "initialize an eager loaded, cache classes app" do
add_to_config <<-RUBY
- config.threadsafe!
+ config.eager_load = true
+ config.cache_classes = true
RUBY
require "#{app_path}/config/application"
- assert AppTemplate::Application.config.allow_concurrency
+ assert AppTemplate::Application.initialize!
end
- test "initialize a threadsafe app" do
- add_to_config <<-RUBY
- config.threadsafe!
- RUBY
-
+ test "application is always added to eager_load namespaces" do
require "#{app_path}/config/application"
- assert AppTemplate::Application.initialize!
+ assert AppTemplate::Application, AppTemplate::Application.config.eager_load_namespaces
end
test "asset_path defaults to nil for application" do
@@ -162,10 +159,11 @@ module ApplicationTests
assert_equal nil, AppTemplate::Application.config.asset_path
end
- test "the application can be marked as threadsafe when there are no frameworks" do
+ test "the application can be eager loaded even when there are no frameworks" do
FileUtils.rm_rf("#{app_path}/config/environments")
add_to_config <<-RUBY
- config.threadsafe!
+ config.eager_load = true
+ config.cache_classes = true
RUBY
use_frameworks []
@@ -175,22 +173,6 @@ module ApplicationTests
end
end
- test "frameworks are not preloaded by default" do
- require "#{app_path}/config/environment"
-
- assert ActionController.autoload?(:Caching)
- end
-
- test "frameworks are preloaded with config.preload_frameworks is set" do
- add_to_config <<-RUBY
- config.preload_frameworks = true
- RUBY
-
- require "#{app_path}/config/environment"
-
- assert !ActionView.autoload?(:AssetPaths)
- end
-
test "filter_parameters should be able to set via config.filter_parameters" do
add_to_config <<-RUBY
config.filter_parameters += [ :foo, 'bar', lambda { |key, value|
diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb
index 066f0c1c84..9d97bae9ae 100644
--- a/railties/test/application/middleware/remote_ip_test.rb
+++ b/railties/test/application/middleware/remote_ip_test.rb
@@ -4,20 +4,6 @@ module ApplicationTests
class RemoteIpTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
- def setup
- build_app
- boot_rails
- FileUtils.rm_rf "#{app_path}/config/environments"
- end
-
- def teardown
- teardown_app
- end
-
- def app
- @app ||= Rails.application
- end
-
def remote_ip(env = {})
remote_ip = nil
env = Rack::MockRequest.env_for("/").merge(env).merge!(
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 9c9ed0cd6b..34b460d0a7 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -87,8 +87,8 @@ module ApplicationTests
assert !middleware.include?("ActiveRecord::QueryCache")
end
- test "removes lock if allow concurrency is set" do
- add_to_config "config.allow_concurrency = true"
+ test "removes lock if cache classes is set" do
+ add_to_config "config.cache_classes = true"
boot!
assert !middleware.include?("Rack::Lock")
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index a450f90dbf..b05fe3aed5 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -55,6 +55,29 @@ module ApplicationTests
assert_match "Doing something...", output
end
+ def test_does_not_explode_when_accessing_a_model_with_eager_load
+ add_to_config <<-RUBY
+ config.eager_load = true
+
+ rake_tasks do
+ task :do_nothing => :environment do
+ Hello.new.world
+ end
+ end
+ RUBY
+
+ app_file "app/models/hello.rb", <<-RUBY
+ class Hello
+ def world
+ puts "Hello world"
+ end
+ end
+ RUBY
+
+ output = Dir.chdir(app_path){ `rake do_nothing` }
+ assert_match "Hello world", output
+ end
+
def test_code_statistics_sanity
assert_match "Code LOC: 5 Test LOC: 0 Code to Test Ratio: 1:0.0",
Dir.chdir(app_path){ `rake stats` }
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 8f04692aef..37839a1c54 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -12,7 +12,6 @@ require 'bundler/setup'
require 'minitest/autorun'
require 'active_support/test_case'
-# TODO: Remove setting this magic constant
RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..")
# These files do not require any others and are needed
@@ -118,6 +117,7 @@ module TestHelpers
end
add_to_config <<-RUBY
+ config.eager_load = false
config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
config.session_store :cookie_store, :key => "_myapp_session"
config.active_support.deprecation = :log
@@ -136,6 +136,7 @@ module TestHelpers
require "action_controller/railtie"
app = Class.new(Rails::Application)
+ app.config.eager_load = false
app.config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
app.config.session_store :cookie_store, :key => "_myapp_session"
app.config.active_support.deprecation = :log