aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile3
-rw-r--r--actionpack/CHANGELOG2
-rw-r--r--actionpack/lib/action_dispatch.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb31
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb65
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb8
-rw-r--r--actionpack/lib/action_view/base.rb8
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb5
-rw-r--r--actionpack/lib/action_view/railtie.rb10
-rw-r--r--actionpack/lib/action_view/template/resolver.rb11
-rw-r--r--actionpack/test/activerecord/render_partial_with_record_identification_test.rb6
-rw-r--r--actionpack/test/dispatch/callbacks_test.rb72
-rw-r--r--actionpack/test/dispatch/reloader_test.rb124
-rw-r--r--actionpack/test/template/number_helper_test.rb5
-rw-r--r--activemodel/CHANGELOG5
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/dirty.rb1
-rw-r--r--activemodel/lib/active_model/secure_password.rb58
-rw-r--r--activemodel/lib/active_model/validations.rb12
-rw-r--r--activemodel/test/cases/secure_password_test.rb32
-rw-r--r--activemodel/test/cases/validations_test.rb10
-rw-r--r--activemodel/test/models/user.rb8
-rw-r--r--activerecord/CHANGELOG41
-rw-r--r--activerecord/lib/active_record/association_preload.rb1
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb16
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb26
-rw-r--r--activerecord/lib/active_record/base.rb5
-rw-r--r--activerecord/lib/active_record/railtie.rb10
-rw-r--r--activerecord/lib/active_record/reflection.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb2
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb20
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/migration.rb4
-rw-r--r--activerecord/test/cases/associations/eager_test.rb9
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb16
-rw-r--r--activerecord/test/cases/named_scope_test.rb42
-rw-r--r--activerecord/test/cases/reflection_test.rb5
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb10
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb6
-rw-r--r--activerecord/test/models/categorization.rb3
-rw-r--r--activerecord/test/models/post.rb10
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb2
-rw-r--r--railties/guides/source/configuring.textile2
-rw-r--r--railties/guides/source/getting_started.textile4
-rw-r--r--railties/lib/rails/application.rb3
-rw-r--r--railties/lib/rails/application/bootstrap.rb8
-rw-r--r--railties/lib/rails/application/finisher.rb8
-rw-r--r--railties/lib/rails/console/app.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/boot.rb13
-rwxr-xr-xrailties/lib/rails/generators/rails/plugin_new/templates/Rakefile9
-rw-r--r--railties/lib/rails/railtie/configurable.rb10
-rw-r--r--railties/lib/rails/tasks/engine.rake69
-rw-r--r--railties/test/application/configuration_test.rb38
-rw-r--r--railties/test/application/console_test.rb8
-rw-r--r--railties/test/application/middleware_test.rb7
-rw-r--r--railties/test/generators/app_generator_test.rb5
-rw-r--r--railties/test/generators/scaffold_generator_test.rb7
-rw-r--r--railties/test/railties/engine_test.rb126
-rw-r--r--railties/test/railties/railtie_test.rb6
62 files changed, 764 insertions, 289 deletions
diff --git a/Gemfile b/Gemfile
index 4e13331626..adfaf7c0e4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,5 +1,7 @@
source 'http://rubygems.org'
+gemspec
+
if ENV['AREL']
gem "arel", :path => ENV['AREL']
else
@@ -7,7 +9,6 @@ else
end
gem "rack", :git => "git://github.com/rack/rack.git"
-gem "rails", :path => File.dirname(__FILE__)
gem "rake", ">= 0.8.7"
gem "mocha", ">= 0.9.8"
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index acd9bd5b63..93b29bcc3a 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.1.0 (unreleased)*
+* brought back config.action_view.cache_template_loading, which allows to decide whether templates should be cached or not [Piotr Sarnacki]
+
* url_for and named url helpers now accept :subdomain and :domain as options [Josh Kalderimis]
* The redirect route method now also accepts a hash of options which will only change the parts of the url in question, or an object which responds to call, allowing for redirects to be reused (check the documentation for examples). [Josh Kalderimis]
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 50faf666e6..50d0d191c1 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -53,6 +53,7 @@ module ActionDispatch
autoload :Flash
autoload :Head
autoload :ParamsParser
+ autoload :Reloader
autoload :RemoteIp
autoload :Rescue
autoload :ShowExceptions
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 0bb950d1cc..5776a7bb27 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -1,32 +1,14 @@
module ActionDispatch
# Provide callbacks to be executed before and after the request dispatch.
- #
- # It also provides a to_prepare callback, which is performed in all requests
- # in development by only once in production and notification callback for async
- # operations.
- #
class Callbacks
include ActiveSupport::Callbacks
define_callbacks :call, :rescuable => true
- define_callbacks :prepare, :scope => :name
- # Add a preparation callback. Preparation callbacks are run before every
- # request in development mode, and before the first request in production mode.
- #
- # If a symbol with a block is given, the symbol is used as an identifier.
- # That allows to_prepare to be called again with the same identifier to
- # replace the existing callback. Passing an identifier is a suggested
- # practice if the code adding a preparation block may be reloaded.
def self.to_prepare(*args, &block)
- first_arg = args.first
- if first_arg.is_a?(Symbol) && block_given?
- remove_method :"__#{first_arg}" if method_defined?(:"__#{first_arg}")
- define_method :"__#{first_arg}", &block
- set_callback(:prepare, :"__#{first_arg}")
- else
- set_callback(:prepare, *args, &block)
- end
+ ActiveSupport::Deprecation.warn "ActionDispatch::Callbacks.to_prepare is deprecated. " <<
+ "Please use ActionDispatch::Reloader.to_prepare instead."
+ ActionDispatch::Reloader.to_prepare(*args, &block)
end
def self.before(*args, &block)
@@ -37,14 +19,13 @@ module ActionDispatch
set_callback(:call, :after, *args, &block)
end
- def initialize(app, prepare_each_request = false)
- @app, @prepare_each_request = app, prepare_each_request
- _run_prepare_callbacks
+ def initialize(app, unused = nil)
+ ActiveSupport::Deprecation.warn "Passing a second argument to ActionDispatch::Callbacks.new is deprecated." unless unused.nil?
+ @app = app
end
def call(env)
_run_call_callbacks do
- _run_prepare_callbacks if @prepare_each_request
@app.call(env)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
new file mode 100644
index 0000000000..579b5d8a02
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -0,0 +1,65 @@
+module ActionDispatch
+ # ActionDispatch::Reloader provides to_prepare and to_cleanup callbacks.
+ # These are analogs of ActionDispatch::Callback's before and after
+ # callbacks, with the difference that to_cleanup is not called until the
+ # request is fully complete -- that is, after #close has been called on
+ # the request body. This is important for streaming responses such as the
+ # following:
+ #
+ # self.response_body = lambda { |response, output|
+ # # code here which refers to application models
+ # }
+ #
+ # Cleanup callbacks will not be called until after the response_body lambda
+ # is evaluated, ensuring that it can refer to application models and other
+ # classes before they are unloaded.
+ #
+ # By default, ActionDispatch::Reloader is included in the middleware stack
+ # only in the development environment.
+ #
+ class Reloader
+ include ActiveSupport::Callbacks
+
+ define_callbacks :prepare, :scope => :name
+ define_callbacks :cleanup, :scope => :name
+
+ # Add a preparation callback. Preparation callbacks are run before each
+ # request.
+ def self.to_prepare(*args, &block)
+ set_callback(:prepare, *args, &block)
+ end
+
+ # Add a cleanup callback. Cleanup callbacks are run after each request is
+ # complete (after #close is called on the response body).
+ def self.to_cleanup(*args, &block)
+ set_callback(:cleanup, *args, &block)
+ end
+
+ def self.prepare!
+ new(nil).send(:_run_prepare_callbacks)
+ end
+
+ def self.cleanup!
+ new(nil).send(:_run_cleanup_callbacks)
+ end
+
+ def initialize(app)
+ @app = app
+ end
+
+ module CleanupOnClose
+ def close
+ super if defined?(super)
+ ensure
+ ActionDispatch::Reloader.cleanup!
+ end
+ end
+
+ def call(env)
+ _run_prepare_callbacks
+ response = @app.call(env)
+ response[2].extend(CleanupOnClose)
+ response
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 49e237f8db..82c4fadb50 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -99,11 +99,9 @@ module ActionDispatch
record = extract_record(record_or_hash_or_array)
record = record.to_model if record.respond_to?(:to_model)
- args = case record_or_hash_or_array
- when Hash; [ record_or_hash_or_array ]
- when Array; record_or_hash_or_array.dup
- else [ record_or_hash_or_array ]
- end
+ args = Array === record_or_hash_or_array ?
+ record_or_hash_or_array.dup :
+ [ record_or_hash_or_array ]
inflection = if options[:action] && options[:action].to_s == "new"
args.pop
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 15944138f7..92ff3380b0 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -172,6 +172,14 @@ module ActionView #:nodoc:
class << self
delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
delegate :logger, :to => 'ActionController::Base', :allow_nil => true
+
+ def cache_template_loading
+ ActionView::Resolver.caching?
+ end
+
+ def cache_template_loading=(value)
+ ActionView::Resolver.caching = value
+ end
end
attr_accessor :_template
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index a9400c347f..26f8dce3c3 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -270,12 +270,13 @@ module ActionView
digits, rounded_number = 1, 0
else
digits = (Math.log10(number.abs) + 1).floor
- rounded_number = BigDecimal.new((number / 10 ** (digits - precision)).to_s).round.to_f * 10 ** (digits - precision)
+ rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
+ digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
end
precision -= digits
precision = precision > 0 ? precision : 0 #don't let it be negative
else
- rounded_number = BigDecimal.new((number * (10 ** precision)).to_s).round.to_f / 10 ** precision
+ rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
end
formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
if strip_insignificant_zeros
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 71cd1a788a..501ec07b09 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -35,5 +35,13 @@ module ActionView
end
end
end
+
+ initializer "action_view.caching" do |app|
+ ActiveSupport.on_load(:action_view) do
+ if app.config.action_view.cache_template_loading.nil?
+ ActionView::Resolver.caching = app.config.cache_classes
+ end
+ end
+ end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index a17454da28..0dccc99d14 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -5,6 +5,13 @@ require "action_view/template"
module ActionView
# = Action View Resolver
class Resolver
+ cattr_accessor :caching
+ self.caching = true
+
+ class << self
+ alias :caching? :caching
+ end
+
def initialize
@cached = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
@@ -23,9 +30,7 @@ module ActionView
private
- def caching?
- @caching ||= !defined?(Rails.application) || Rails.application.config.cache_classes
- end
+ delegate :caching?, :to => "self.class"
# This is what child classes implement. No defaults are needed
# because Resolver guarantees that the arguments are present and
diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
index 43c534c111..99f09286ff 100644
--- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
+++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
@@ -11,7 +11,7 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
render :partial => @topic.replies
end
- def render_with_named_scope
+ def render_with_scope
render :partial => Reply.base
end
@@ -62,8 +62,8 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
assert_equal 'Birdman is better!', @response.body
end
- def test_rendering_partial_with_named_scope
- get :render_with_named_scope
+ def test_rendering_partial_with_scope
+ get :render_with_scope
assert_template 'replies/_reply'
assert_equal 'Birdman is better!Nuh uh!', @response.body
end
diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb
index d3aa55a1ba..5becb621de 100644
--- a/actionpack/test/dispatch/callbacks_test.rb
+++ b/actionpack/test/dispatch/callbacks_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class DispatcherTest < Test::Unit::TestCase
+class DispatcherTest < ActiveSupport::TestCase
class Foo
cattr_accessor :a, :b
end
@@ -13,65 +13,9 @@ class DispatcherTest < Test::Unit::TestCase
def setup
Foo.a, Foo.b = 0, 0
- ActionDispatch::Callbacks.reset_callbacks(:prepare)
ActionDispatch::Callbacks.reset_callbacks(:call)
end
- def test_prepare_callbacks_with_cache_classes
- a = b = c = nil
- ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 }
- ActionDispatch::Callbacks.to_prepare { |*args| b = c = 2 }
- ActionDispatch::Callbacks.to_prepare { |*args| c = 3 }
-
- # Ensure to_prepare callbacks are not run when defined
- assert_nil a || b || c
-
- # Run callbacks
- dispatch
-
- assert_equal 1, a
- assert_equal 2, b
- assert_equal 3, c
-
- # Make sure they are only run once
- a = b = c = nil
- dispatch
- assert_nil a || b || c
- end
-
- def test_prepare_callbacks_without_cache_classes
- a = b = c = nil
- ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 }
- ActionDispatch::Callbacks.to_prepare { |*args| b = c = 2 }
- ActionDispatch::Callbacks.to_prepare { |*args| c = 3 }
-
- # Ensure to_prepare callbacks are not run when defined
- assert_nil a || b || c
-
- # Run callbacks
- dispatch(false)
-
- assert_equal 1, a
- assert_equal 2, b
- assert_equal 3, c
-
- # Make sure they are run again
- a = b = c = nil
- dispatch(false)
- assert_equal 1, a
- assert_equal 2, b
- assert_equal 3, c
- end
-
- def test_to_prepare_with_identifier_replaces
- ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a, Foo.b = 1, 1 }
- ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a = 2 }
-
- dispatch
- assert_equal 2, Foo.a
- assert_equal 0, Foo.b
- end
-
def test_before_and_after_callbacks
ActionDispatch::Callbacks.before { |*args| Foo.a += 1; Foo.b += 1 }
ActionDispatch::Callbacks.after { |*args| Foo.a += 1; Foo.b += 1 }
@@ -85,10 +29,20 @@ class DispatcherTest < Test::Unit::TestCase
assert_equal 4, Foo.b
end
+ def test_to_prepare_deprecation
+ prepared = false
+ assert_deprecated do
+ ActionDispatch::Callbacks.to_prepare { prepared = true }
+ end
+
+ ActionDispatch::Reloader.prepare!
+ assert prepared
+ end
+
private
- def dispatch(cache_classes = true, &block)
- @dispatcher ||= ActionDispatch::Callbacks.new(block || DummyApp.new, !cache_classes)
+ def dispatch(&block)
+ @dispatcher ||= ActionDispatch::Callbacks.new(block || DummyApp.new)
@dispatcher.call({'rack.input' => StringIO.new('')})
end
diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb
new file mode 100644
index 0000000000..995b19030c
--- /dev/null
+++ b/actionpack/test/dispatch/reloader_test.rb
@@ -0,0 +1,124 @@
+require 'abstract_unit'
+
+class ReloaderTest < Test::Unit::TestCase
+ Reloader = ActionDispatch::Reloader
+
+ def test_prepare_callbacks
+ a = b = c = nil
+ Reloader.to_prepare { |*args| a = b = c = 1 }
+ Reloader.to_prepare { |*args| b = c = 2 }
+ Reloader.to_prepare { |*args| c = 3 }
+
+ # Ensure to_prepare callbacks are not run when defined
+ assert_nil a || b || c
+
+ # Run callbacks
+ call_and_return_body
+
+ assert_equal 1, a
+ assert_equal 2, b
+ assert_equal 3, c
+ end
+
+ class MyBody < Array
+ def initialize(&block)
+ @on_close = block
+ end
+
+ def foo
+ "foo"
+ end
+
+ def bar
+ "bar"
+ end
+
+ def close
+ @on_close.call if @on_close
+ end
+ end
+
+ def test_returned_body_object_always_responds_to_close
+ body = call_and_return_body
+ assert_respond_to body, :close
+ end
+
+ def test_returned_body_object_behaves_like_underlying_object
+ body = call_and_return_body do
+ b = MyBody.new
+ b << "hello"
+ b << "world"
+ [200, { "Content-Type" => "text/html" }, b]
+ end
+ assert_equal 2, body.size
+ assert_equal "hello", body[0]
+ assert_equal "world", body[1]
+ assert_equal "foo", body.foo
+ assert_equal "bar", body.bar
+ end
+
+ def test_it_calls_close_on_underlying_object_when_close_is_called_on_body
+ close_called = false
+ body = call_and_return_body do
+ b = MyBody.new do
+ close_called = true
+ end
+ [200, { "Content-Type" => "text/html" }, b]
+ end
+ body.close
+ assert close_called
+ end
+
+ def test_returned_body_object_responds_to_all_methods_supported_by_underlying_object
+ body = call_and_return_body do
+ [200, { "Content-Type" => "text/html" }, MyBody.new]
+ end
+ assert_respond_to body, :size
+ assert_respond_to body, :each
+ assert_respond_to body, :foo
+ assert_respond_to body, :bar
+ end
+
+ def test_cleanup_callbacks_are_called_when_body_is_closed
+ cleaned = false
+ Reloader.to_cleanup { cleaned = true }
+
+ body = call_and_return_body
+ assert !cleaned
+
+ body.close
+ assert cleaned
+ end
+
+ def test_prepare_callbacks_arent_called_when_body_is_closed
+ prepared = false
+ Reloader.to_prepare { prepared = true }
+
+ body = call_and_return_body
+ prepared = false
+
+ body.close
+ assert !prepared
+ end
+
+ def test_manual_reloading
+ prepared = cleaned = false
+ Reloader.to_prepare { prepared = true }
+ Reloader.to_cleanup { cleaned = true }
+
+ Reloader.prepare!
+ assert prepared
+ assert !cleaned
+
+ prepared = cleaned = false
+ Reloader.cleanup!
+ assert !prepared
+ assert cleaned
+ end
+
+ private
+ def call_and_return_body(&block)
+ @reloader ||= Reloader.new(block || proc {[200, {}, 'response']})
+ @reloader.call({'rack.input' => StringIO.new('')})[2]
+ end
+end
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index ab127521ad..156b7cb5ff 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -100,6 +100,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("0", number_with_precision(0, :precision => 0))
assert_equal("0.00100", number_with_precision(0.001, :precision => 5))
assert_equal("0.001", number_with_precision(0.00111, :precision => 3))
+ assert_equal("10.00", number_with_precision(9.995, :precision => 2))
+ assert_equal("11.00", number_with_precision(10.995, :precision => 2))
end
def test_number_with_precision_with_custom_delimiter_and_separator
@@ -125,6 +127,9 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "0.0001", number_with_precision(0.0001, :precision => 1, :significant => true )
assert_equal "0.000100", number_with_precision(0.0001, :precision => 3, :significant => true )
assert_equal "0.0001", number_with_precision(0.0001111, :precision => 1, :significant => true )
+ assert_equal "10.0", number_with_precision(9.995, :precision => 3, :significant => true)
+ assert_equal "9.99", number_with_precision(9.994, :precision => 3, :significant => true)
+ assert_equal "11.0", number_with_precision(10.995, :precision => 3, :significant => true)
end
def test_number_with_precision_with_strip_insignificant_zeros
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index 4e963c77b0..9dd5e03685 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,15 +1,18 @@
*Rails 3.1.0 (unreleased)*
-* No changes
+* Added ActiveModel::SecurePassword to encapsulate dead-simple password usage with BCrypt encryption and salting [DHH]
+
*Rails 3.0.2 (unreleased)*
* No changes
+
*Rails 3.0.1 (October 15, 2010)*
* No Changes, just a version bump.
+
*Rails 3.0.0 (August 29, 2010)*
* Added ActiveModel::MassAssignmentSecurity [Eric Chapweske, Josh Kalderimis]
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 1f38e70c36..64aa7ad922 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -22,4 +22,6 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('i18n', '~> 0.5.0')
+ s.add_dependency('bcrypt-ruby', '~> 2.1.2')
+
end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index be0f24ff92..dd6ee058cc 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -42,6 +42,7 @@ module ActiveModel
autoload :Naming
autoload :Observer, 'active_model/observing'
autoload :Observing
+ autoload :SecurePassword
autoload :Serialization
autoload :TestCase
autoload :Translation
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 1dfd0b6132..a479795d51 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -1,5 +1,4 @@
require 'active_model/attribute_methods'
-require 'active_support/concern'
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
new file mode 100644
index 0000000000..52941942b8
--- /dev/null
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -0,0 +1,58 @@
+require 'bcrypt'
+
+module ActiveModel
+ module SecurePassword
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Adds methods to set and authenticate against a BCrypt password.
+ # This mechanism requires you to have a password_digest attribute.
+ #
+ # Validations for presence of password, confirmation of password (using
+ # a "password_confirmation" attribute) are automatically added.
+ # You can add more validations by hand if need be.
+ #
+ # Example using Active Record (which automatically includes ActiveModel::SecurePassword):
+ #
+ # # Schema: User(name:string, password_digest:string)
+ # class User < ActiveRecord::Base
+ # has_secure_password
+ # end
+ #
+ # user = User.new(:name => "david", :password => "", :password_confirmation => "nomatch")
+ # user.save # => false, password required
+ # user.password = "mUc3m00RsqyRe"
+ # user.save # => false, confirmation doesn't match
+ # user.password_confirmation = "mUc3m00RsqyRe"
+ # user.save # => true
+ # user.authenticate("notright") # => false
+ # user.authenticate("mUc3m00RsqyRe") # => user
+ # User.find_by_name("david").try(:authenticate, "notright") # => nil
+ # User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
+ def has_secure_password
+ attr_reader :password
+ attr_accessor :password_confirmation
+
+ attr_protected(:password_digest) if respond_to?(:attr_protected)
+
+ validates_confirmation_of :password
+ validates_presence_of :password_digest
+ end
+ end
+
+ # Returns self if the password is correct, otherwise false.
+ def authenticate(unencrypted_password)
+ if BCrypt::Password.new(password_digest) == unencrypted_password
+ self
+ else
+ false
+ end
+ end
+
+ # Encrypts the password into the password_digest attribute.
+ def password=(unencrypted_password)
+ @password = unencrypted_password
+ self.password_digest = BCrypt::Password.create(unencrypted_password)
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index b044caa8d3..6cb015a144 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -104,7 +104,7 @@ module ActiveModel
# end
# end
#
- # Or with a block which is passed with the current record to be validated:
+ # With a block which is passed with the current record to be validated:
#
# class Comment
# include ActiveModel::Validations
@@ -118,6 +118,16 @@ module ActiveModel
# end
# end
#
+ # Or with a block where self points to the current record to be validated:
+ #
+ # class Comment
+ # include ActiveModel::Validations
+ #
+ # validate do
+ # errors.add(:base, "Must be friends to leave a comment") unless commenter.friend_of?(commentee)
+ # end
+ # end
+ #
def validate(*args, &block)
options = args.extract_options!
if options.key?(:on)
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
new file mode 100644
index 0000000000..79be715730
--- /dev/null
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -0,0 +1,32 @@
+require 'cases/helper'
+require 'models/user'
+
+class SecurePasswordTest < ActiveModel::TestCase
+
+ setup do
+ @user = User.new
+ end
+
+ test "password must be present" do
+ assert !@user.valid?
+ assert_equal 1, @user.errors.size
+ end
+
+ test "password must match confirmation" do
+ @user.password = "thiswillberight"
+ @user.password_confirmation = "wrong"
+
+ assert !@user.valid?
+
+ @user.password_confirmation = "thiswillberight"
+
+ assert @user.valid?
+ end
+
+ test "authenticate" do
+ @user.password = "secret"
+
+ assert !@user.authenticate("wrong")
+ assert @user.authenticate("secret")
+ end
+end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 55b477dd10..e90dc7d4e3 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -148,6 +148,14 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_validate_block
+ Topic.validate { errors.add("title", "will never be valid") }
+ t = Topic.new("title" => "Title", "content" => "whatever")
+ assert t.invalid?
+ assert t.errors[:title].any?
+ assert_equal ["will never be valid"], t.errors["title"]
+ end
+
+ def test_validate_block_with_params
Topic.validate { |topic| topic.errors.add("title", "will never be valid") }
t = Topic.new("title" => "Title", "content" => "whatever")
assert t.invalid?
@@ -187,7 +195,7 @@ class ValidationsTest < ActiveModel::TestCase
assert t.invalid?
assert_equal "can't be blank", t.errors["title"].first
Topic.validates_presence_of :title, :author_name
- Topic.validate {|topic| topic.errors.add('author_email_address', 'will never be valid')}
+ Topic.validate {errors.add('author_email_address', 'will never be valid')}
Topic.validates_length_of :title, :content, :minimum => 2
t = Topic.new :title => ''
diff --git a/activemodel/test/models/user.rb b/activemodel/test/models/user.rb
new file mode 100644
index 0000000000..e221bb8091
--- /dev/null
+++ b/activemodel/test/models/user.rb
@@ -0,0 +1,8 @@
+class User
+ include ActiveModel::Validations
+ include ActiveModel::SecurePassword
+
+ has_secure_password
+
+ attr_accessor :password_digest, :password_salt
+end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 9c2e311c75..fd571c4ca4 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,46 @@
*Rails 3.1.0 (unreleased)*
+* Added ActiveRecord::Base#has_secure_password (via ActiveModel::SecurePassword) to encapsulate dead-simple password usage with BCrypt encryption and salting [DHH]. Example:
+
+ # Schema: User(name:string, password_digest:string, password_salt:string)
+ class User < ActiveRecord::Base
+ has_secure_password
+ end
+
+ user = User.new(:name => "david", :password => "", :password_confirmation => "nomatch")
+ user.save # => false, password required
+ user.password = "mUc3m00RsqyRe"
+ user.save # => false, confirmation doesn't match
+ user.password_confirmation = "mUc3m00RsqyRe"
+ user.save # => true
+ user.authenticate("notright") # => false
+ user.authenticate("mUc3m00RsqyRe") # => user
+ User.find_by_name("david").try(:authenticate, "notright") # => nil
+ User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
+
+
+* When a model is generated add_index is added by default for belongs_to or references columns
+
+ rails g model post user:belongs_to will generate the following:
+
+ class CreatePosts < ActiveRecord::Migration
+ def up
+ create_table :posts do |t|
+ t.belongs_to :user
+
+ t.timestamps
+ end
+
+ add_index :posts, :user_id
+ end
+
+ def down
+ drop_table :posts
+ end
+ end
+
+ [Santiago Pastorino]
+
* Setting the id of a belongs_to object will update the reference to the
object. [#2989 state:resolved]
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 5eb1071ba2..f2094283a2 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -251,6 +251,7 @@ module ActiveRecord
through_record_id = through_record[reflection.through_reflection_primary_key].to_s
add_preloaded_records_to_collection(id_to_record_map[through_record_id], reflection.name, through_record.send(source))
end
+ records.each { |record| record.send(reflection.name).target.uniq! } if options[:uniq]
end
else
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index abb17a11c6..11a7a725e5 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -102,7 +102,7 @@ module ActiveRecord
def reset
reset_target!
- reset_named_scopes_cache!
+ reset_scopes_cache!
@loaded = false
end
@@ -160,7 +160,7 @@ module ActiveRecord
load_target
delete(@target)
reset_target!
- reset_named_scopes_cache!
+ reset_scopes_cache!
end
# Calculate sum using SQL, not Enumerable
@@ -253,7 +253,7 @@ module ActiveRecord
load_target
destroy(@target).tap do
reset_target!
- reset_named_scopes_cache!
+ reset_scopes_cache!
end
end
@@ -409,9 +409,9 @@ module ActiveRecord
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
super
elsif @reflection.klass.scopes[method]
- @_named_scopes_cache ||= {}
- @_named_scopes_cache[method] ||= {}
- @_named_scopes_cache[method][args] ||= with_scope(@scope) { @reflection.klass.send(method, *args) }
+ @_scopes_cache ||= {}
+ @_scopes_cache[method] ||= {}
+ @_scopes_cache[method][args] ||= with_scope(@scope) { @reflection.klass.send(method, *args) }
else
with_scope(@scope) do
if block_given?
@@ -442,8 +442,8 @@ module ActiveRecord
@target = Array.new
end
- def reset_named_scopes_cache!
- @_named_scopes_cache = {}
+ def reset_scopes_cache!
+ @_scopes_cache = {}
end
def find_target
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index 2ecd0f054a..5dc5b0c048 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -39,22 +39,22 @@ module ActiveRecord
# Build SQL conditions from attributes, qualified by table name.
def construct_conditions
table = aliased_through_table
- conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
+ conditions = construct_owner_attributes(@reflection.through_reflection).map do |attr, value|
table[attr].eq(value)
end
conditions << Arel.sql(sql_conditions) if sql_conditions
table.create_and(conditions)
end
- # Associate attributes pointing to owner, quoted.
- def construct_quoted_owner_attributes(reflection)
+ # Associate attributes pointing to owner
+ def construct_owner_attributes(reflection)
if as = reflection.options[:as]
- { "#{as}_id" => @owner.id,
+ { "#{as}_id" => @owner[reflection.active_record_primary_key],
"#{as}_type" => @owner.class.base_class.name }
elsif reflection.macro == :belongs_to
{ reflection.klass.primary_key => @owner[reflection.primary_key_name] }
else
- { reflection.primary_key_name => @owner.id }
+ { reflection.primary_key_name => @owner[reflection.active_record_primary_key] }
end
end
@@ -74,7 +74,8 @@ module ActiveRecord
conditions = []
if @reflection.source_reflection.macro == :belongs_to
- reflection_primary_key = @reflection.klass.primary_key
+ reflection_primary_key = @reflection.source_reflection.options[:primary_key] ||
+ @reflection.klass.primary_key
source_primary_key = @reflection.source_reflection.primary_key_name
if @reflection.options[:source_type]
column = @reflection.source_reflection.options[:foreign_type]
@@ -83,7 +84,8 @@ module ActiveRecord
end
else
reflection_primary_key = @reflection.source_reflection.primary_key_name
- source_primary_key = @reflection.through_reflection.klass.primary_key
+ source_primary_key = @reflection.source_reflection.options[:primary_key] ||
+ @reflection.through_reflection.klass.primary_key
if @reflection.source_reflection.options[:as]
column = "#{@reflection.source_reflection.options[:as]}_type"
conditions <<
@@ -99,16 +101,6 @@ module ActiveRecord
right.create_on(right.create_and(conditions)))
end
- # Construct attributes for associate pointing to owner.
- def construct_owner_attributes(reflection)
- if as = reflection.options[:as]
- { "#{as}_id" => @owner.id,
- "#{as}_type" => @owner.class.base_class.name }
- else
- { reflection.primary_key_name => @owner.id }
- end
- end
-
# Construct attributes for :through pointing to owner and associate.
def construct_join_attributes(associate)
# TODO: revisit this to allow it for deletion, supposing dependent option is supported
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 7b9ce21ceb..858ccebbfa 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -852,8 +852,8 @@ module ActiveRecord #:nodoc:
# limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
#
- # It is recommended to use block form of unscoped because chaining unscoped with <tt>named_scope</tt>
- # does not work. Assuming that <tt>published</tt> is a <tt>named_scope</tt> following two statements are same.
+ # It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
+ # does not work. Assuming that <tt>published</tt> is a <tt>scope</tt> following two statements are same.
#
# Post.unscoped.published
# Post.published
@@ -1851,6 +1851,7 @@ MSG
include ActiveModel::MassAssignmentSecurity
include Callbacks, ActiveModel::Observing, Timestamp
include Associations, AssociationPreload, NamedScope
+ include ActiveModel::SecurePassword
# AutosaveAssociation needs to be included before Transactions, because we want
# #save_with_autosave_associations to be wrapped inside a transaction.
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index dfe255ad7c..2accf0a48f 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -69,11 +69,9 @@ module ActiveRecord
end
initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app|
- unless app.config.cache_classes
- ActiveSupport.on_load(:active_record) do
- ActionDispatch::Callbacks.after do
- ActiveRecord::Base.clear_reloadable_connections!
- end
+ ActiveSupport.on_load(:active_record) do
+ ActionDispatch::Reloader.to_cleanup do
+ ActiveRecord::Base.clear_reloadable_connections!
end
end
end
@@ -82,7 +80,7 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) do
instantiate_observers
- ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do
+ ActionDispatch::Reloader.to_prepare do
ActiveRecord::Base.instantiate_observers
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index fe4b518826..b9caa64a0e 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -205,7 +205,11 @@ module ActiveRecord
end
def association_foreign_key
- @association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
+ @association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
+ end
+
+ def active_record_primary_key
+ @active_record_primary_key ||= options[:primary_key] || active_record.primary_key
end
def counter_cache_column
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 6660df302b..0ab55ae864 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -63,7 +63,7 @@ module ActiveRecord
end
def joins(*args)
- return self if args.blank?
+ return self if args.compact.blank?
relation = clone
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 15abf8bac7..8c4adf7116 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -226,17 +226,17 @@ module ActiveRecord #:nodoc:
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
def compute_type
- type = @serializable.class.serialized_attributes.has_key?(name) ?
- super : @serializable.class.columns_hash[name].type
+ klass = @serializable.class
+ type = if klass.serialized_attributes.key?(name)
+ super
+ elsif klass.columns_hash.key?(name)
+ klass.columns_hash[name].type
+ else
+ NilClass
+ end
- case type
- when :text
- :string
- when :time
- :datetime
- else
- type
- end
+ { :text => :string,
+ :time => :datetime }[type] || type
end
protected :compute_type
end
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
index 70e064be21..7d4e1a7404 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
@@ -8,6 +8,10 @@ class <%= migration_class_name %> < ActiveRecord::Migration
t.timestamps
<% end -%>
end
+
+<% attributes.select {|attr| attr.reference? }.each do |attribute| -%>
+ add_index :<%= table_name %>, :<%= attribute.name %>_id
+<% end -%>
end
def down
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 34a1cdeebe..d5262b1ee4 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -20,10 +20,11 @@ require 'models/project'
require 'models/member'
require 'models/membership'
require 'models/club'
+require 'models/categorization'
class EagerAssociationTest < ActiveRecord::TestCase
fixtures :posts, :comments, :authors, :author_addresses, :categories, :categories_posts,
- :companies, :accounts, :tags, :taggings, :people, :readers,
+ :companies, :accounts, :tags, :taggings, :people, :readers, :categorizations,
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books,
:developers, :projects, :developers_projects, :members, :memberships, :clubs
@@ -910,4 +911,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_queries(2) { @tagging = Tagging.preload(:taggable).find(t.id) }
assert_no_queries { assert ! @tagging.taggable }
end
+
+ def test_preloading_has_many_through_with_uniq
+ mary = Author.includes(:unique_categorized_posts).where(:id => authors(:mary).id).first
+ assert_equal 1, mary.unique_categorized_posts.length
+ assert_equal 1, mary.unique_categorized_post_ids.length
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 44ff01ddc0..77bc369ecc 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -23,7 +23,7 @@ require 'models/category'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors,
:owners, :pets, :toys, :jobs, :references, :companies,
- :subscribers, :books, :subscriptions, :developers
+ :subscribers, :books, :subscriptions, :developers, :categorizations
# Dummies to force column loads so query counts are clean.
def setup
@@ -473,4 +473,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
category = authors(:david).special_categories.create(:name => "Foo")
assert_equal 1, category.categorizations.where(:special => true).count
end
+
+ def test_joining_has_many_through_with_uniq
+ mary = Author.joins(:unique_categorized_posts).where(:id => authors(:mary).id).first
+ assert_equal 1, mary.unique_categorized_posts.length
+ assert_equal 1, mary.unique_categorized_post_ids.length
+ end
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 4581cb1acd..0a57c7883f 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -298,6 +298,22 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_equal [authors(:mary)], posts(:authorless).authors
end
+ def test_has_many_going_through_join_model_with_custom_primary_key
+ assert_equal [authors(:david)], posts(:thinking).authors_using_author_id
+ end
+
+ def test_has_many_going_through_polymorphic_join_model_with_custom_primary_key
+ assert_equal [tags(:general)], posts(:eager_other).tags_using_author_id
+ end
+
+ def test_has_many_through_with_custom_primary_key_on_belongs_to_source
+ assert_equal [authors(:david), authors(:david)], posts(:thinking).author_using_custom_pk
+ end
+
+ def test_has_many_through_with_custom_primary_key_on_has_many_source
+ assert_equal [authors(:david)], posts(:thinking).authors_using_custom_pk
+ end
+
def test_both_scoped_and_explicit_joins_should_be_respected
assert_nothing_raised do
Post.send(:with_scope, :find => {:joins => "left outer join comments on comments.id = posts.id"}) do
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 6ac3e3fc56..ed5e1e0cba 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -141,26 +141,26 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal 1, Topic.multiple_extensions.extension_one
end
- def test_has_many_associations_have_access_to_named_scopes
+ def test_has_many_associations_have_access_to_scopes
assert_not_equal Post.containing_the_letter_a, authors(:david).posts
assert !Post.containing_the_letter_a.empty?
assert_equal authors(:david).posts & Post.containing_the_letter_a, authors(:david).posts.containing_the_letter_a
end
- def test_named_scope_with_STI
+ def test_scope_with_STI
assert_equal 3,Post.containing_the_letter_a.count
assert_equal 1,SpecialPost.containing_the_letter_a.count
end
- def test_has_many_through_associations_have_access_to_named_scopes
+ def test_has_many_through_associations_have_access_to_scopes
assert_not_equal Comment.containing_the_letter_e, authors(:david).comments
assert !Comment.containing_the_letter_e.empty?
assert_equal authors(:david).comments & Comment.containing_the_letter_e, authors(:david).comments.containing_the_letter_e
end
- def test_named_scopes_honor_current_scopes_from_when_defined
+ def test_scopes_honor_current_scopes_from_when_defined
assert !Post.ranked_by_comments.limit_by(5).empty?
assert !authors(:david).posts.ranked_by_comments.limit_by(5).empty?
assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5)
@@ -236,7 +236,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
- def test_any_should_not_fire_query_if_named_scope_loaded
+ def test_any_should_not_fire_query_if_scope_loaded
topics = Topic.base
topics.collect # force load
assert_no_queries { assert topics.any? }
@@ -259,7 +259,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
- def test_many_should_not_fire_query_if_named_scope_loaded
+ def test_many_should_not_fire_query_if_scope_loaded
topics = Topic.base
topics.collect # force load
assert_no_queries { assert topics.many? }
@@ -276,27 +276,27 @@ class NamedScopeTest < ActiveRecord::TestCase
assert Topic.base.many?
end
- def test_should_build_on_top_of_named_scope
+ def test_should_build_on_top_of_scope
topic = Topic.approved.build({})
assert topic.approved
end
- def test_should_build_new_on_top_of_named_scope
+ def test_should_build_new_on_top_of_scope
topic = Topic.approved.new
assert topic.approved
end
- def test_should_create_on_top_of_named_scope
+ def test_should_create_on_top_of_scope
topic = Topic.approved.create({})
assert topic.approved
end
- def test_should_create_with_bang_on_top_of_named_scope
+ def test_should_create_with_bang_on_top_of_scope
topic = Topic.approved.create!({})
assert topic.approved
end
- def test_should_build_on_top_of_chained_named_scopes
+ def test_should_build_on_top_of_chained_scopes
topic = Topic.approved.by_lifo.build({})
assert topic.approved
assert_equal 'lifo', topic.author_name
@@ -310,7 +310,7 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_kind_of Topic, Topic.approved.sample
end
- def test_should_use_where_in_query_for_named_scope
+ def test_should_use_where_in_query_for_scope
assert_equal Developer.find_all_by_name('Jamis').to_set, Developer.find_all_by_id(Developer.jamises).to_set
end
@@ -361,7 +361,7 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.uniq
end
- def test_named_scopes_batch_finders
+ def test_scopes_batch_finders
assert_equal 3, Topic.approved.count
assert_queries(4) do
@@ -381,7 +381,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
- def test_named_scopes_with_reserved_names
+ def test_scopes_with_reserved_names
class << Topic
def public_method; end
public :public_method
@@ -400,7 +400,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
- def test_named_scopes_on_relations
+ def test_scopes_on_relations
# Topic.replied
approved_topics = Topic.scoped.approved.order('id DESC')
assert_equal topics(:fourth), approved_topics.first
@@ -409,19 +409,19 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal topics(:third), replied_approved_topics.first
end
- def test_index_on_named_scope
+ def test_index_on_scope
approved = Topic.approved.order('id ASC')
assert_equal topics(:second), approved[0]
assert approved.loaded?
end
- def test_nested_named_scopes_queries_size
+ def test_nested_scopes_queries_size
assert_queries(1) do
Topic.approved.by_lifo.replied.written_before(Time.now).all
end
end
- def test_named_scopes_are_cached_on_associations
+ def test_scopes_are_cached_on_associations
post = posts(:welcome)
assert_equal post.comments.containing_the_letter_e.object_id, post.comments.containing_the_letter_e.object_id
@@ -430,7 +430,7 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_no_queries { post.comments.containing_the_letter_e.all }
end
- def test_named_scopes_with_arguments_are_cached_on_associations
+ def test_scopes_with_arguments_are_cached_on_associations
post = posts(:welcome)
one = post.comments.limit_by(1).all
@@ -443,7 +443,7 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_no_queries { post.comments.limit_by(2).all }
end
- def test_named_scopes_are_reset_on_association_reload
+ def test_scopes_are_reset_on_association_reload
post = posts(:welcome)
[:destroy_all, :reset, :delete_all].each do |method|
@@ -453,7 +453,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
- def test_named_scoped_are_lazy_loaded_if_table_still_does_not_exist
+ def test_scoped_are_lazy_loaded_if_table_still_does_not_exist
assert_nothing_raised do
require "models/without_table"
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 389ca9eae6..912e3c47bb 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -191,6 +191,11 @@ class ReflectionTest < ActiveRecord::TestCase
assert_kind_of ThroughReflection, Subscriber.reflect_on_association(:books)
end
+ def test_active_record_primary_key
+ assert_equal "nick", Subscriber.reflect_on_association(:subscriptions).active_record_primary_key.to_s
+ assert_equal "name", Author.reflect_on_association(:essay).active_record_primary_key.to_s
+ end
+
def test_collection_association
assert Pirate.reflect_on_association(:birds).collection?
assert Pirate.reflect_on_association(:parrots).collection?
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 1678e631e5..f113a9c516 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -422,7 +422,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
- def test_named_scope_overwrites_default
+ def test_scope_overwrites_default
expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.name }
received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
assert_equal expected, received
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 1682f34a1d..20bfafbc5e 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -36,7 +36,7 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [], relation.bind_values
end
- def test_two_named_scopes_with_includes_should_not_drop_any_include
+ def test_two_scopes_with_includes_should_not_drop_any_include
car = Car.incl_engines.incl_tyres.first
assert_no_queries { car.tyres.length }
assert_no_queries { car.engines.length }
@@ -184,6 +184,10 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [2, 4, 6, 8, 10], even_ids.sort
end
+ def test_joins_with_nil_argument
+ assert_nothing_raised { DependentFirm.joins(nil).first }
+ end
+
def test_finding_with_hash_conditions_on_joined_table
firms = DependentFirm.joins(:account).where({:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}).to_a
assert_equal 1, firms.size
@@ -248,7 +252,7 @@ class RelationTest < ActiveRecord::TestCase
end
end
- def test_respond_to_class_methods_and_named_scopes
+ def test_respond_to_class_methods_and_scopes
assert DeveloperOrderedBySalary.scoped.respond_to?(:all_ordered_by_name)
assert Topic.scoped.respond_to?(:by_lifo)
end
@@ -754,7 +758,7 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 'zyke', FastCar.order('name desc').find(:first, :order => 'id').name
end
- def test_default_scope_order_with_named_scope_order
+ def test_default_scope_order_with_scope_order
assert_equal 'zyke', CoolCar.order_using_new_style.limit(1).first.name
assert_equal 'zyke', CoolCar.order_using_old_style.limit(1).first.name
assert_equal 'zyke', FastCar.order_using_new_style.limit(1).first.name
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 2003e25e35..a6074b23e7 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -262,4 +262,10 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
assert array.include? 'github'
end
+ def test_should_support_aliased_attributes
+ xml = Author.select("name as firstname").to_xml
+ array = Hash.from_xml(xml)['authors']
+ assert_equal array.size, array.select { |author| author.has_key? 'firstname' }.size
+ end
+
end
diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb
index b3fc29fa15..fdb0a11540 100644
--- a/activerecord/test/models/categorization.rb
+++ b/activerecord/test/models/categorization.rb
@@ -2,6 +2,9 @@ class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
belongs_to :author
+
+ belongs_to :author_using_custom_pk, :class_name => 'Author', :foreign_key => :author_id, :primary_key => :author_address_extra_id
+ has_many :authors_using_custom_pk, :class_name => 'Author', :foreign_key => :id, :primary_key => :category_id
end
class SpecialCategorization < ActiveRecord::Base
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 164b499bf0..974e87d2bf 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -69,6 +69,16 @@ class Post < ActiveRecord::Base
has_many :categorizations, :foreign_key => :category_id
has_many :authors, :through => :categorizations
+ has_many :categorizations_using_author_id, :primary_key => :author_id, :foreign_key => :post_id, :class_name => 'Categorization'
+ has_many :authors_using_author_id, :through => :categorizations_using_author_id, :source => :author
+
+ has_many :taggings_using_author_id, :primary_key => :author_id, :as => :taggable, :class_name => 'Tagging'
+ has_many :tags_using_author_id, :through => :taggings_using_author_id, :source => :tag
+
+ has_many :standard_categorizations, :class_name => 'Categorization', :foreign_key => :post_id
+ has_many :author_using_custom_pk, :through => :standard_categorizations
+ has_many :authors_using_custom_pk, :through => :standard_categorizations
+
has_many :readers
has_many :readers_with_person, :include => :person, :class_name => "Reader"
has_many :people, :through => :readers
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index cd658fe173..a97e9d7daf 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -8,7 +8,7 @@ module ActiveSupport
# I18n.reload!
# end
#
- # ActionDispatch::Callbacks.to_prepare do
+ # ActionDispatch::Reloader.to_prepare do
# i18n_reloader.execute_if_updated
# end
#
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index f8a5616a76..282337d373 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -19,7 +19,7 @@ module I18n
# on to_prepare callbacks. This will only happen on the config.after_initialize
# callback below.
initializer "i18n.callbacks" do
- ActionDispatch::Callbacks.to_prepare do
+ ActionDispatch::Reloader.to_prepare do
I18n::Railtie.reloader.execute_if_updated
end
end
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index 7b1ac5ece5..497c8318f0 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -63,6 +63,8 @@ end
* +config.cache_classes+ controls whether or not application classes should be reloaded on each request. Defaults to _true_ in development, _false_ in test and production. Can also be enabled with +threadsafe!+.
+* +config.action_view.cache_template_loading+ controls whether or not templates should be reloaded on each request. Defaults to whatever is set for config.cache_classes.
+
* +config.cache_store+ configures which cache store to use for Rails caching. Options include +:memory_store+, +:file_store+, +:mem_cache_store+ or the name of your own custom class. Defaults to +:file_store+.
* +config.colorize_logging+ specifies whether or not to use ANSI color codes when logging information. Defaults to _true_.
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index 902b7353c0..e371632d87 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -811,6 +811,8 @@ class CreateComments < ActiveRecord::Migration
t.timestamps
end
+
+ add_index :comments, :post_id
end
def self.down
@@ -819,7 +821,7 @@ class CreateComments < ActiveRecord::Migration
end
</ruby>
-The +t.references+ line sets up a foreign key column for the association between the two models. Go ahead and run the migration:
+The +t.references+ line sets up a foreign key column for the association between the two models. And the +add_index+ line sets up an index for this association column. Go ahead and run the migration:
<shell>
$ rake db:migrate
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index b248bc737c..149c63cd9e 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -156,7 +156,8 @@ module Rails
middleware.use ::ActionDispatch::ShowExceptions, config.consider_all_requests_local if config.action_dispatch.show_exceptions
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
- middleware.use ::ActionDispatch::Callbacks, !config.cache_classes
+ middleware.use ::ActionDispatch::Reloader unless config.cache_classes
+ middleware.use ::ActionDispatch::Callbacks
middleware.use ::ActionDispatch::Cookies
if config.session_store
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 213aa0768a..9c9d85eed6 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -51,11 +51,9 @@ module Rails
end
initializer :set_clear_dependencies_hook do
- unless config.cache_classes
- ActionDispatch::Callbacks.after do
- ActiveSupport::DescendantsTracker.clear
- ActiveSupport::Dependencies.clear
- end
+ ActionDispatch::Reloader.to_cleanup do
+ ActiveSupport::DescendantsTracker.clear
+ ActiveSupport::Dependencies.clear
end
end
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index e3342be7ee..a45b61c99c 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -21,7 +21,7 @@ module Rails
initializer :add_to_prepare_blocks do
config.to_prepare_blocks.each do |block|
- ActionDispatch::Callbacks.to_prepare(&block)
+ ActionDispatch::Reloader.to_prepare(&block)
end
end
@@ -37,6 +37,10 @@ module Rails
build_middleware_stack
end
+ initializer :run_prepare_callbacks do
+ ActionDispatch::Reloader.prepare!
+ end
+
initializer :eager_load! do
if config.cache_classes && !$rails_rake_task
ActiveSupport.run_load_hooks(:before_eager_load, self)
@@ -52,7 +56,7 @@ module Rails
initializer :set_routes_reloader do |app|
reloader = lambda { app.routes_reloader.execute_if_updated }
reloader.call
- ActionDispatch::Callbacks.to_prepare(&reloader)
+ ActionDispatch::Reloader.to_prepare(&reloader)
end
# Disable dependency loading during request cycle
diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb
index 9d9763699d..95c74baae2 100644
--- a/railties/lib/rails/console/app.rb
+++ b/railties/lib/rails/console/app.rb
@@ -26,7 +26,7 @@ end
# reloads the environment
def reload!(print=true)
puts "Reloading..." if print
- # This triggers the to_prepare callbacks
- ActionDispatch::Callbacks.new(Proc.new {}, false).call({})
+ ActionDispatch::Reloader.cleanup!
+ ActionDispatch::Reloader.prepare!
true
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
index ab6cb374de..4489e58688 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
@@ -1,13 +1,6 @@
require 'rubygems'
# Set up gems listed in the Gemfile.
-gemfile = File.expand_path('../../Gemfile', __FILE__)
-begin
- ENV['BUNDLE_GEMFILE'] = gemfile
- require 'bundler'
- Bundler.setup
-rescue Bundler::GemNotFound => e
- STDERR.puts e.message
- STDERR.puts "Try running `bundle install`."
- exit!
-end if File.exist?(gemfile)
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+
+require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
index 25292f59ad..5704e75a29 100755
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -16,9 +16,6 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
end
<% if full? && !options[:skip_active_record] -%>
-namespace :app do
- ENGINE_PATH = File.expand_path("..", __FILE__)
- load File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)
-end
-<% end -%>
-
+APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)
+load 'rails/tasks/engine.rake'
+<% end %>
diff --git a/railties/lib/rails/railtie/configurable.rb b/railties/lib/rails/railtie/configurable.rb
index b6d4ed2312..920ab67ff1 100644
--- a/railties/lib/rails/railtie/configurable.rb
+++ b/railties/lib/rails/railtie/configurable.rb
@@ -1,9 +1,7 @@
module Rails
class Railtie
module Configurable
- def self.included(base)
- base.extend ClassMethods
- end
+ extend ActiveSupport::Concern
module ClassMethods
delegate :config, :to => :instance
@@ -26,9 +24,9 @@ module Rails
protected
- def method_missing(*args, &block)
- instance.send(*args, &block)
- end
+ def method_missing(*args, &block)
+ instance.send(*args, &block)
+ end
end
end
end
diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake
new file mode 100644
index 0000000000..2f0e7be896
--- /dev/null
+++ b/railties/lib/rails/tasks/engine.rake
@@ -0,0 +1,69 @@
+task "load_app" do
+ namespace :app do
+ load APP_RAKEFILE
+ end
+
+ if !defined?(ENGINE_PATH) || !ENGINE_PATH
+ ENGINE_PATH = find_engine_path(APP_RAKEFILE)
+ end
+end
+
+def app_task(name)
+ task name => [:load_app, "app:db:#{name}"]
+end
+
+namespace :db do
+ app_task "reset"
+
+ desc "Migrate the database (options: VERSION=x, VERBOSE=false)."
+ app_task "migrate"
+ app_task "migrate:up"
+ app_task "migrate:down"
+ app_task "migrate:redo"
+ app_task "migrate:reset"
+
+ desc "Display status of migrations"
+ app_task "migrate:status"
+
+ desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)'
+ app_task "create"
+ app_task "create:all"
+
+ desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)'
+ app_task "drop"
+ app_task "drop:all"
+
+ desc "Load fixtures into the current environment's database."
+ app_task "fixtures:load"
+
+ desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)."
+ app_task "rollback"
+
+ desc "Create a db/schema.rb file that can be portably used against any DB supported by AR"
+ app_task "schema:dump"
+
+ desc "Load a schema.rb file into the database"
+ app_task "schema:load"
+
+ desc "Load the seed data from db/seeds.rb"
+ app_task "seed"
+
+ desc "Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)"
+ app_task "setup"
+
+ desc "Dump the database structure to an SQL file"
+ app_task "structure:dump"
+
+ desc "Retrieves the current schema version number"
+ app_task "version"
+end
+
+def find_engine_path(path)
+ return if path == "/"
+
+ if Rails::Engine.find(path)
+ path
+ else
+ find_engine_path(File.expand_path('..', path))
+ end
+end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index c12c4a4660..4f4b7beec4 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -280,5 +280,43 @@ module ApplicationTests
get "/"
assert_equal "/omg/images/foo.jpg", last_response.body
end
+
+ test "config.action_view.cache_template_loading with cache_classes default" do
+ add_to_config "config.cache_classes = true"
+ require "#{app_path}/config/environment"
+ require 'action_view/base'
+
+ assert ActionView::Resolver.caching?
+ end
+
+ test "config.action_view.cache_template_loading without cache_classes default" do
+ add_to_config "config.cache_classes = false"
+ require "#{app_path}/config/environment"
+ require 'action_view/base'
+
+ assert !ActionView::Resolver.caching?
+ end
+
+ test "config.action_view.cache_template_loading = false" do
+ add_to_config <<-RUBY
+ config.cache_classes = true
+ config.action_view.cache_template_loading = false
+ RUBY
+ require "#{app_path}/config/environment"
+ require 'action_view/base'
+
+ assert !ActionView::Resolver.caching?
+ end
+
+ test "config.action_view.cache_template_loading = true" do
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ config.action_view.cache_template_loading = true
+ RUBY
+ require "#{app_path}/config/environment"
+ require 'action_view/base'
+
+ assert ActionView::Resolver.caching?
+ end
end
end
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index d4159dd0fd..793e73556c 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -26,14 +26,14 @@ class ConsoleTest < Test::Unit::TestCase
assert_instance_of ActionDispatch::Integration::Session, session
end
- def test_reload_should_fire_preparation_callbacks
+ def test_reload_should_fire_preparation_and_cleanup_callbacks
load_environment
a = b = c = nil
# TODO: These should be defined on the initializer
- ActionDispatch::Callbacks.to_prepare { a = b = c = 1 }
- ActionDispatch::Callbacks.to_prepare { b = c = 2 }
- ActionDispatch::Callbacks.to_prepare { c = 3 }
+ ActionDispatch::Reloader.to_cleanup { a = b = c = 1 }
+ ActionDispatch::Reloader.to_cleanup { b = c = 2 }
+ ActionDispatch::Reloader.to_prepare { c = 3 }
# Hide Reloading... output
silence_stream(STDOUT) { reload! }
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 173ac40b12..a2217888e4 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -27,6 +27,7 @@ module ApplicationTests
"ActionDispatch::ShowExceptions",
"ActionDispatch::RemoteIp",
"Rack::Sendfile",
+ "ActionDispatch::Reloader",
"ActionDispatch::Callbacks",
"ActiveRecord::ConnectionAdapters::ConnectionManagement",
"ActiveRecord::QueryCache",
@@ -81,6 +82,12 @@ module ApplicationTests
assert !middleware.include?("ActionDispatch::ShowExceptions")
end
+ test "removes ActionDispatch::Reloader if cache_classes is true" do
+ add_to_config "config.cache_classes = true"
+ boot!
+ assert !middleware.include?("ActionDispatch::Reloader")
+ end
+
test "use middleware" do
use_frameworks []
add_to_config "config.middleware.use Rack::Config"
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 02c49ab241..0fe5cdc4a8 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -137,7 +137,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "public/javascripts/rails.js"
assert_file "public/javascripts/controls.js"
assert_file "public/javascripts/dragdrop.js"
- assert_file "public/javascripts/dragdrop.js"
+ assert_file "public/javascripts/effects.js"
assert_file "test"
end
@@ -154,6 +154,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/
assert_file "public/javascripts/application.js"
assert_file "public/javascripts/prototype.js"
+ assert_file "public/javascripts/controls.js"
+ assert_file "public/javascripts/dragdrop.js"
+ assert_file "public/javascripts/effects.js"
assert_file "public/javascripts/rails.js", /prototype/
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index f366600b16..df787f61ba 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -3,7 +3,7 @@ require 'rails/generators/rails/scaffold/scaffold_generator'
class ScaffoldGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
- arguments %w(product_line title:string price:integer)
+ arguments %w(product_line title:string product:belongs_to user:references)
setup :copy_routes
@@ -14,7 +14,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/
assert_file "test/unit/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/
assert_file "test/fixtures/product_lines.yml"
- assert_migration "db/migrate/create_product_lines.rb"
+ assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product/
+ assert_migration "db/migrate/create_product_lines.rb", /add_index :product_lines, :product_id/
+ assert_migration "db/migrate/create_product_lines.rb", /references :user/
+ assert_migration "db/migrate/create_product_lines.rb", /add_index :product_lines, :user_id/
# Route
assert_file "config/routes.rb" do |route|
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 6b64a19741..92aa025238 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -1,12 +1,15 @@
require "isolation/abstract_unit"
require "railties/shared_tests"
require 'stringio'
+require 'rack/test'
+require 'rack/file'
module RailtiesTest
class EngineTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
include SharedTests
+ include Rack::Test::Methods
def setup
build_app
@@ -87,10 +90,8 @@ module RailtiesTest
boot_rails
- env = Rack::MockRequest.env_for("/bukkits")
- response = Rails.application.call(env)
-
- assert_equal ["HELLO WORLD"], response[2]
+ get("/bukkits")
+ assert_equal "HELLO WORLD", last_response.body
end
test "it provides routes as default endpoint" do
@@ -115,9 +116,8 @@ module RailtiesTest
boot_rails
- env = Rack::MockRequest.env_for("/bukkits/foo")
- response = Rails.application.call(env)
- assert_equal ["foo"], response[2]
+ get("/bukkits/foo")
+ assert_equal "foo", last_response.body
end
test "engine can load its own plugins" do
@@ -191,13 +191,11 @@ module RailtiesTest
boot_rails
env = Rack::MockRequest.env_for("/")
- response = Bukkits::Engine.call(env)
-
+ Bukkits::Engine.call(env)
assert_equal Bukkits::Engine.routes, env['action_dispatch.routes']
env = Rack::MockRequest.env_for("/")
- response = Rails.application.call(env)
-
+ Rails.application.call(env)
assert_equal Rails.application.routes, env['action_dispatch.routes']
end
@@ -230,6 +228,12 @@ module RailtiesTest
<%= stylesheet_link_tag("foo") %>
ERB
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ mount Bukkits::Engine => "/bukkits"
+ end
+ RUBY
+
add_to_config 'config.asset_path = "/omg%s"'
boot_rails
@@ -239,9 +243,8 @@ module RailtiesTest
::Bukkits::Engine.config.asset_path = "/bukkits%s"
- env = Rack::MockRequest.env_for("/foo")
- response = Bukkits::Engine.call(env)
- stripped_body = response[2].body.split("\n").map(&:strip).join
+ get("/bukkits/foo")
+ stripped_body = last_response.body.split("\n").map(&:strip).join
expected = "/omg/bukkits/images/foo.png" +
"<script src=\"/omg/bukkits/javascripts/foo.js\" type=\"text/javascript\"></script>" +
@@ -264,14 +267,16 @@ module RailtiesTest
end
RUBY
- boot_rails
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ mount Bukkits::Engine => "/bukkits"
+ end
+ RUBY
- env = Rack::MockRequest.env_for("/foo")
- response = Bukkits::Engine.call(env)
- stripped_body = response[2].body.strip
+ boot_rails
- expected = "/bukkits/images/foo.png"
- assert_equal expected, stripped_body
+ get("/bukkits/foo")
+ assert_equal "/bukkits/images/foo.png", last_response.body.strip
end
test "engine's files are served via ActionDispatch::Static" do
@@ -291,25 +296,14 @@ module RailtiesTest
boot_rails
- env = Rack::MockRequest.env_for("/app.html")
- response = Rails.application.call(env)
- assert_equal rack_body(response[2]), rack_body(File.open(File.join(app_path, "public/app.html")))
+ get("/app.html")
+ assert_equal File.read(File.join(app_path, "public/app.html")), last_response.body
- env = Rack::MockRequest.env_for("/bukkits/bukkits.html")
- response = Rails.application.call(env)
- assert_equal rack_body(response[2]), rack_body(File.open(File.join(@plugin.path, "public/bukkits.html")))
+ get("/bukkits/bukkits.html")
+ assert_equal File.read(File.join(@plugin.path, "public/bukkits.html")), last_response.body
- env = Rack::MockRequest.env_for("/bukkits/file_from_app.html")
- response = Rails.application.call(env)
- assert_equal rack_body(response[2]), rack_body(File.open(File.join(app_path, "public/bukkits/file_from_app.html")))
- end
-
- def rack_body(obj)
- buffer = ""
- obj.each do |part|
- buffer << part
- end
- buffer
+ get("/bukkits/file_from_app.html")
+ assert_equal File.read(File.join(app_path, "public/bukkits/file_from_app.html")), last_response.body
end
test "shared engine should include application's helpers and own helpers" do
@@ -355,17 +349,14 @@ module RailtiesTest
boot_rails
- env = Rack::MockRequest.env_for("/foo")
- response = Rails.application.call(env)
- assert_equal ["Something... Something... Something..."], response[2]
+ get("/foo")
+ assert_equal "Something... Something... Something...", last_response.body
- env = Rack::MockRequest.env_for("/foo/show")
- response = Rails.application.call(env)
- assert_equal ["/foo"], response[2]
+ get("/foo/show")
+ assert_equal "/foo", last_response.body
- env = Rack::MockRequest.env_for("/foo/bar")
- response = Rails.application.call(env)
- assert_equal ["It's a bar."], response[2]
+ get("/foo/bar")
+ assert_equal "It's a bar.", last_response.body
end
test "isolated engine should include only its own routes and helpers" do
@@ -464,25 +455,20 @@ module RailtiesTest
assert ::Bukkits::MyMailer.method_defined?(:foo_path)
assert !::Bukkits::MyMailer.method_defined?(:bar_path)
- env = Rack::MockRequest.env_for("/bukkits/from_app")
- response = AppTemplate::Application.call(env)
- assert_equal ["false"], response[2]
+ get("/bukkits/from_app")
+ assert_equal "false", last_response.body
- env = Rack::MockRequest.env_for("/bukkits/foo/show")
- response = AppTemplate::Application.call(env)
- assert_equal ["/bukkits/foo"], response[2]
+ get("/bukkits/foo/show")
+ assert_equal "/bukkits/foo", last_response.body
- env = Rack::MockRequest.env_for("/bukkits/foo")
- response = AppTemplate::Application.call(env)
- assert_equal ["Helped."], response[2]
+ get("/bukkits/foo")
+ assert_equal "Helped.", last_response.body
- env = Rack::MockRequest.env_for("/bukkits/routes_helpers_in_view")
- response = AppTemplate::Application.call(env)
- assert_equal ["/bukkits/foo, /bar"], response[2]
+ get("/bukkits/routes_helpers_in_view")
+ assert_equal "/bukkits/foo, /bar", last_response.body
- env = Rack::MockRequest.env_for("/bukkits/polymorphic_path_without_namespace")
- response = AppTemplate::Application.call(env)
- assert_equal ["/bukkits/posts/1"], response[2]
+ get("/bukkits/polymorphic_path_without_namespace")
+ assert_equal "/bukkits/posts/1", last_response.body
end
test "isolated engine should avoid namespace in names if that's possible" do
@@ -541,9 +527,8 @@ module RailtiesTest
boot_rails
- env = Rack::MockRequest.env_for("/bukkits/posts/new")
- response = AppTemplate::Application.call(env)
- assert rack_body(response[2]) =~ /name="post\[title\]"/
+ get("/bukkits/posts/new")
+ assert_match /name="post\[title\]"/, last_response.body
end
test "loading seed data" do
@@ -612,16 +597,14 @@ module RailtiesTest
end
RUBY
- require 'rack/test'
- extend Rack::Test::Methods
-
boot_rails
require "#{rails_root}/config/environment"
- get "/foo"
+
+ get("/foo")
assert_equal "foo", last_response.body
- get "/bukkits/bar"
+ get("/bukkits/bar")
assert_equal "bar", last_response.body
end
@@ -785,5 +768,10 @@ module RailtiesTest
assert_match %r{bukkits/stylesheets/foo.css}, last_response.body
assert_match %r{bukkits/javascripts/foo.js}, last_response.body
end
+
+ private
+ def app
+ Rails.application
+ end
end
end
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index 6d194eecba..7ea8364ae9 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -21,10 +21,10 @@ module RailtiesTest
test "Railtie provides railtie_name" do
begin
- class ::Foo < Rails::Railtie ; end
- assert_equal "foo", ::Foo.railtie_name
+ class ::FooBarBaz < Rails::Railtie ; end
+ assert_equal "foo_bar_baz", ::FooBarBaz.railtie_name
ensure
- Object.send(:remove_const, :"Foo")
+ Object.send(:remove_const, :"FooBarBaz")
end
end