aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile18
-rw-r--r--[-rwxr-xr-x]Rakefile2
-rw-r--r--actionmailer/README.rdoc6
-rw-r--r--[-rwxr-xr-x]actionmailer/Rakefile1
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb2
-rw-r--r--actionmailer/test/abstract_unit.rb14
-rw-r--r--actionpack/CHANGELOG.md4
-rw-r--r--[-rwxr-xr-x]actionpack/Rakefile1
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb6
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb15
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb16
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb14
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb66
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb33
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb2
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb22
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb4
-rw-r--r--actionpack/test/controller/caching_test.rb33
-rw-r--r--actionpack/test/controller/routing_test.rb18
-rw-r--r--actionpack/test/dispatch/header_test.rb5
-rw-r--r--actionpack/test/dispatch/routing_test.rb19
-rw-r--r--actionpack/test/template/date_helper_test.rb35
-rw-r--r--[-rwxr-xr-x]activemodel/Rakefile0
-rw-r--r--activemodel/lib/active_model/dirty.rb1
-rw-r--r--activemodel/lib/active_model/observing.rb33
-rw-r--r--activemodel/test/cases/observing_test.rb22
-rw-r--r--activerecord/CHANGELOG.md19
-rw-r--r--[-rwxr-xr-x]activerecord/Rakefile1
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb4
-rw-r--r--activerecord/lib/active_record/base.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb2
-rw-r--r--activerecord/lib/active_record/explain.rb2
-rw-r--r--activerecord/lib/active_record/querying.rb4
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb10
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb63
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb5
-rw-r--r--activerecord/test/cases/base_test.rb31
-rw-r--r--activerecord/test/cases/calculations_test.rb4
-rw-r--r--activerecord/test/cases/column_test.rb24
-rw-r--r--activerecord/test/cases/finder_test.rb32
-rw-r--r--activerecord/test/cases/migration_test.rb4
-rw-r--r--activerecord/test/cases/relations_test.rb4
-rw-r--r--activerecord/test/models/company.rb5
-rw-r--r--activesupport/CHANGELOG.md3
-rw-r--r--[-rwxr-xr-x]activesupport/Rakefile0
-rw-r--r--activesupport/lib/active_support/cache.rb2
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb23
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb2
-rw-r--r--activesupport/lib/active_support/json/encoding.rb21
-rw-r--r--activesupport/lib/active_support/rescuable.rb1
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb1
-rw-r--r--activesupport/test/caching_test.rb7
-rw-r--r--activesupport/test/json/encoding_test.rb15
-rw-r--r--guides/code/getting_started/app/views/posts/_form.html.erb4
-rw-r--r--guides/code/getting_started/app/views/posts/show.html.erb20
-rw-r--r--guides/code/getting_started/config/routes.rb13
-rw-r--r--guides/code/getting_started/test/fixtures/tags.yml9
-rw-r--r--guides/source/action_mailer_basics.textile2
-rw-r--r--guides/source/active_record_querying.textile2
-rw-r--r--guides/source/caching_with_rails.textile2
-rw-r--r--guides/source/command_line.textile12
-rw-r--r--guides/source/configuring.textile2
-rw-r--r--guides/source/contributing_to_ruby_on_rails.textile13
-rw-r--r--guides/source/getting_started.textile492
-rw-r--r--guides/source/routing.textile2
-rw-r--r--guides/source/upgrading_ruby_on_rails.textile4
-rw-r--r--[-rwxr-xr-x]railties/Rakefile10
-rw-r--r--railties/lib/rails/backtrace_cleaner.rb2
-rw-r--r--railties/lib/rails/commands/dbconsole.rb56
-rw-r--r--railties/lib/rails/engine.rb9
-rw-r--r--railties/lib/rails/generators/app_base.rb28
-rw-r--r--[-rwxr-xr-x]railties/lib/rails/generators/rails/app/templates/Rakefile1
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec2
-rw-r--r--[-rwxr-xr-x]railties/lib/rails/generators/rails/plugin_new/templates/Rakefile1
-rw-r--r--railties/lib/rails/railtie.rb2
-rw-r--r--railties/lib/rails/railtie/configuration.rb2
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb5
-rw-r--r--railties/test/application/queue_test.rb2
-rw-r--r--railties/test/application/rake/notes_test.rb74
-rw-r--r--railties/test/backtrace_cleaner_test.rb32
-rw-r--r--railties/test/commands/dbconsole_test.rb128
-rw-r--r--railties/test/generators/app_generator_test.rb2
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb4
-rw-r--r--railties/test/generators/shared_generator_tests.rb4
-rw-r--r--railties/test/railties/engine_test.rb31
101 files changed, 937 insertions, 787 deletions
diff --git a/Gemfile b/Gemfile
index c1486b725d..005031d843 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,30 +3,30 @@ source 'https://rubygems.org'
gemspec
if ENV['AREL']
- gem 'arel', :path => ENV['AREL']
+ gem 'arel', path: ENV['AREL']
else
gem 'arel'
end
-gem 'rack-test', :git => "git://github.com/brynary/rack-test.git"
+gem 'rack-test', github: "brynary/rack-test"
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'jquery-rails'
if ENV['JOURNEY']
- gem 'journey', :path => ENV['JOURNEY']
+ gem 'journey', path: ENV['JOURNEY']
else
- gem 'journey', :git => "git://github.com/rails/journey"
+ gem 'journey', github: "rails/journey"
end
if ENV['AR_DEPRECATED_FINDERS']
gem 'active_record_deprecated_finders', path: ENV['AR_DEPRECATED_FINDERS']
else
- gem 'active_record_deprecated_finders', git: 'git://github.com/rails/active_record_deprecated_finders'
+ gem 'active_record_deprecated_finders', github: 'rails/active_record_deprecated_finders'
end
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
-gem 'uglifier', '>= 1.0.3', :require => false
+gem 'uglifier', '>= 1.0.3', require: false
gem 'rake', '>= 0.8.7'
gem 'mocha', '>= 0.11.2'
@@ -36,7 +36,7 @@ group :doc do
# to a bug, but the PR that fixes it has been there
# for some weeks unapplied. As a temporary solution
# this is our own fork with the fix.
- gem 'sdoc', :git => 'git://github.com/fxn/sdoc.git'
+ gem 'sdoc', github: 'fxn/sdoc'
gem 'RedCloth', '~> 4.2'
gem 'w3c_validators'
end
@@ -90,9 +90,9 @@ if ENV['ORACLE_ENHANCED_PATH'] || ENV['ORACLE_ENHANCED']
gem 'ruby-oci8', '>= 2.0.4'
end
if ENV['ORACLE_ENHANCED_PATH']
- gem 'activerecord-oracle_enhanced-adapter', :path => ENV['ORACLE_ENHANCED_PATH']
+ gem 'activerecord-oracle_enhanced-adapter', path: ENV['ORACLE_ENHANCED_PATH']
else
- gem 'activerecord-oracle_enhanced-adapter', :git => 'git://github.com/rsim/oracle-enhanced.git'
+ gem 'activerecord-oracle_enhanced-adapter', github: 'rsim/oracle-enhanced'
end
end
diff --git a/Rakefile b/Rakefile
index 0d218844b2..21eb60bbe1 100755..100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,5 +1,3 @@
-#!/usr/bin/env rake
-
require 'rdoc/task'
require 'sdoc'
require 'net/http'
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 755717cfba..cf10bfffdb 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -22,12 +22,12 @@ the email.
This can be as simple as:
class Notifier < ActionMailer::Base
- delivers_from 'system@loudthinking.com'
+ default from: 'system@loudthinking.com'
def welcome(recipient)
@recipient = recipient
- mail(:to => recipient,
- :subject => "[Signed up] Welcome #{recipient}")
+ mail(to: recipient,
+ subject: "[Signed up] Welcome #{recipient}")
end
end
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index 8f5aeb9603..c1c4171cdf 100755..100644
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rubygems/package_task'
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index d1467fb526..3b38dbccc7 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -65,7 +65,7 @@ module ActionMailer
when NilClass
raise "Delivery method cannot be nil"
when Symbol
- if klass = delivery_methods[method.to_sym]
+ if klass = delivery_methods[method]
mail.delivery_method(klass, send(:"#{method}_settings"))
else
raise "Invalid delivery method #{method.inspect}"
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 3a519253f9..487102b564 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -1,16 +1,4 @@
-# Pathname has a warning, so require it first while silencing
-# warnings to shut it up.
-#
-# Also, in 1.9, Bundler creates warnings due to overriding
-# Rubygems methods
-begin
- old, $VERBOSE = $VERBOSE, nil
- require 'pathname'
- require File.expand_path('../../../load_paths', __FILE__)
-ensure
- $VERBOSE = old
-end
-
+require File.expand_path('../../../load_paths', __FILE__)
require 'active_support/core_ext/kernel/reporting'
# These are the normal settings that will be set up by Railties
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 99602f14d9..96dee33f7b 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,9 @@
## Rails 4.0.0 (unreleased) ##
+* Allows `assert_redirected_to` to match against a regular expression. *Andy Lindeman*
+
+* Add backtrace to development routing error page. *Richard Schneeman*
+
* Replace `include_seconds` boolean argument with `:include_seconds => true` option
in `distance_of_time_in_words` and `time_ago_in_words` signature. *Dmitriy Kiriyenko*
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index bb1e704767..17d95bfd1d 100755..100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rubygems/package_task'
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 59ec197347..80901b8bf3 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -133,6 +133,8 @@ module ActionController #:nodoc:
end
def filter(controller)
+ cache_layout = @cache_layout.respond_to?(:call) ? @cache_layout.call(controller) : @cache_layout
+
path_options = if @cache_path.respond_to?(:call)
controller.instance_exec(controller, &@cache_path)
else
@@ -144,13 +146,13 @@ module ActionController #:nodoc:
body = controller.read_fragment(cache_path.path, @store_options)
unless body
- controller.action_has_layout = false unless @cache_layout
+ controller.action_has_layout = false unless cache_layout
yield
controller.action_has_layout = true
body = controller._save_fragment(cache_path.path, @store_options)
end
- body = controller.render_to_string(:text => body, :layout => true) unless @cache_layout
+ body = controller.render_to_string(:text => body, :layout => true) unless cache_layout
controller.response_body = body
controller.content_type = Mime[cache_path.extension || :html]
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 374645c9ce..379ff97048 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -14,7 +14,7 @@ module ActionController #:nodoc:
protected
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
# via the Rack::Sendfile middleware. The header to use is set via
- # config.action_dispatch.x_sendfile_header.
+ # +config.action_dispatch.x_sendfile_header+.
# Your server can also configure this for you by setting the X-Sendfile-Type header.
#
# Be careful to sanitize the path parameter if it is coming from a web
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 17e2db74d4..7e2316d01c 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -48,7 +48,7 @@ module ActionController
# method attribute_names.
#
# If you're going to pass the parameters to an +ActiveModel+ object (such as
- # +User.new(params[:user])+), you might consider passing the model class to
+ # <tt>User.new(params[:user])</tt>), you might consider passing the model class to
# the method instead. The +ParamsWrapper+ will actually try to determine the
# list of attribute names from the model and only wrap those attributes:
#
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 040b51e040..a3bb25f75a 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -14,17 +14,18 @@ module ActionDispatch
end
def [](header_name)
- if include?(header_name)
- super
- else
- super(env_name(header_name))
- end
+ super env_name(header_name)
+ end
+
+ def fetch(header_name, default=nil, &block)
+ super env_name(header_name), default, &block
end
private
- # Converts a HTTP header name to an environment variable name.
+ # Converts a HTTP header name to an environment variable name if it is
+ # not contained within the headers hash.
def env_name(header_name)
- @@env_cache[header_name]
+ include?(header_name) ? header_name : @@env_cache[header_name]
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 26f4c16218..0eaae80461 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -179,11 +179,11 @@ module Mime
end
end
- # input: 'text'
- # returned value: [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]
+ # For an input of <tt>'text'</tt>, returns <tt>[Mime::JSON, Mime::XML, Mime::ICS,
+ # Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]</tt>.
#
- # input: 'application'
- # returned value: [Mime::HTML, Mime::JS, Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]
+ # For an input of <tt>'application'</tt>, returns <tt>[Mime::HTML, Mime::JS,
+ # Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]</tt>.
def parse_data_with_trailing_star(input)
Mime::SET.select { |m| m =~ input }
end
@@ -192,7 +192,7 @@ module Mime
#
# Usage:
#
- # Mime::Type.unregister(:mobile)
+ # Mime::Type.unregister(:mobile)
def unregister(symbol)
symbol = symbol.to_s.upcase
mime = Mime.const_get(symbol)
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index d9b63faf5e..bcfd0b0d00 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -35,6 +35,10 @@ module ActionDispatch
@env["action_dispatch.request.path_parameters"] ||= {}
end
+ def reset_parameters #:nodoc:
+ @env.delete("action_dispatch.request.parameters")
+ end
+
private
# TODO: Validate that the characters are UTF-8. If they aren't,
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 196f655de3..2c0c31de9d 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -227,7 +227,7 @@ module ActionDispatch
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
# be raised.
#
- # This jar requires that you set a suitable secret for the verification on your app's config.secret_token.
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_token+.
#
# Example:
#
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index a0388e0e13..2f6968eb2e 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -18,10 +18,10 @@ module ActionDispatch
# classes before they are unloaded.
#
# By default, ActionDispatch::Reloader is included in the middleware stack
- # only in the development environment; specifically, when config.cache_classes
+ # only in the development environment; specifically, when +config.cache_classes+
# is false. Callbacks may be registered even when it is not included in the
- # middleware stack, but are executed only when +ActionDispatch::Reloader.prepare!+
- # or +ActionDispatch::Reloader.cleanup!+ are called manually.
+ # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
+ # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
#
class Reloader
include ActiveSupport::Callbacks
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 45a00b5056..e82132b445 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -7,6 +7,15 @@ require 'active_support/core_ext/object/blank'
module ActionDispatch
module Session
class SessionRestoreError < StandardError #:nodoc:
+ attr_reader :original_exception
+
+ def initialize(const_error)
+ @original_exception = const_error
+
+ super("Session contains objects whose class definition isn't available.\n" +
+ "Remember to require the classes for all objects kept in the session.\n" +
+ "(Original exception: #{const_error.message} [#{const_error.class}])\n")
+ end
end
module DestroyableSession
@@ -58,11 +67,8 @@ module ActionDispatch
begin
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
- rescue LoadError, NameError => const_error
- raise ActionDispatch::Session::SessionRestoreError,
- "Session contains objects whose class definition isn't available.\n" +
- "Remember to require the classes for all objects kept in the session.\n" +
- "(Original exception: #{const_error.message} [#{const_error.class}])\n"
+ rescue LoadError, NameError => e
+ raise ActionDispatch::Session::SessionRestoreError, e, e.backtrace
end
retry
else
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
index f06c07daa5..177d383e94 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
@@ -12,4 +12,6 @@
<% end %>
<p>
Try running <code>rake routes</code> for more information on available routes.
-</p> \ No newline at end of file
+</p>
+
+<%= render :template => "rescues/_trace" %>
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 1a1a054985..4ea3937057 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -35,6 +35,8 @@ module ActionDispatch
}
return true
+ ensure
+ req.reset_parameters
end
def call(env)
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 013cf93dbc..8fde667108 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -43,16 +43,14 @@ module ActionDispatch
# edit_polymorphic_path(@post) # => "/posts/1/edit"
# polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
#
- # == Using with mounted engines
+ # == Usage with mounted engines
#
- # If you use mounted engine, there is a possibility that you will need to use
- # polymorphic_url pointing at engine's routes. To do that, just pass proxy used
- # to reach engine's routes as a first argument:
+ # If you are using a mounted engine and you need to use a polymorphic_url
+ # pointing at the engine's routes, pass in the engine's route proxy as the first
+ # argument to the method. For example:
#
- # For example:
- #
- # polymorphic_url([blog, @post]) # it will call blog.post_path(@post)
- # form_for([blog, @post]) # => "/blog/posts/1
+ # polymorphic_url([blog, @post]) # calls blog.post_path(@post)
+ # form_for([blog, @post]) # => "/blog/posts/1"
#
module PolymorphicRoutes
# Constructs a call to a named RESTful route for the given record and returns the
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 444a79c50d..95c588c00a 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -1,5 +1,6 @@
require 'action_dispatch/http/request'
require 'active_support/core_ext/uri'
+require 'active_support/core_ext/array/extract_options'
require 'rack/utils'
module ActionDispatch
@@ -99,7 +100,7 @@ module ActionDispatch
# match 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
#
def redirect(*args, &block)
- options = args.last.is_a?(Hash) ? args.pop : {}
+ options = args.extract_options!
status = options.delete(:status) || 301
return OptionRedirect.new(status, options) if options.any?
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 7a7810a95c..7abd7bd008 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -99,12 +99,12 @@ module ActionDispatch
@module = Module.new do
protected
- def handle_positional_args(args, options, route)
+ def handle_positional_args(args, options, segment_keys)
inner_options = args.extract_options!
result = options.dup
if args.any?
- keys = route.segment_keys
+ keys = segment_keys
if args.size < keys.size - 1 # take format into account
keys -= self.url_options.keys if self.respond_to?(:url_options)
keys -= options.keys
@@ -161,34 +161,13 @@ module ActionDispatch
end
end
- def hash_access_name(name, only_path)
- if only_path
- :"hash_for_#{name}_path"
- else
- :"hash_for_#{name}_url"
- end
- end
-
def define_named_route_methods(name, route)
[true, false].each do |only_path|
hash = route.defaults.merge(:use_route => name, :only_path => only_path)
- define_hash_access route, name, hash
define_url_helper route, name, hash
end
end
- def define_hash_access(route, name, options)
- selector = hash_access_name(name, options[:only_path])
-
- @module.module_eval do
- redefine_method(selector) do |*args|
- self.handle_positional_args(args, options, route)
- end
- protected selector
- end
- helpers << selector
- end
-
# Create a url helper allowing ordered parameters to be associated
# with corresponding dynamic segments, so you can do:
#
@@ -204,36 +183,26 @@ module ActionDispatch
#
def define_url_helper(route, name, options)
selector = url_helper_name(name, options[:only_path])
- hash_access_method = hash_access_name(name, options[:only_path])
-
- if optimize_helper?(route)
- @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
- remove_possible_method :#{selector}
- def #{selector}(*args)
- if args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation?
- options = #{options.inspect}.merge!(url_options)
- options[:path] = "#{optimized_helper(route)}"
- ActionDispatch::Http::URL.url_for(options)
- else
- url_for(#{hash_access_method}(*args))
- end
- end
- END_EVAL
- else
- @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
- remove_possible_method :#{selector}
- def #{selector}(*args)
- url_for(#{hash_access_method}(*args))
+
+ @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
+ remove_possible_method :#{selector}
+ def #{selector}(*args)
+ if #{optimize_helper?(route)} && args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation?
+ options = #{options.inspect}.merge!(url_options)
+ options[:path] = "#{optimized_helper(route)}"
+ ActionDispatch::Http::URL.url_for(options)
+ else
+ url_for(handle_positional_args(args, #{options.inspect}, #{route.segment_keys.inspect}))
end
- END_EVAL
- end
+ end
+ END_EVAL
helpers << selector
end
# Clause check about when we need to generate an optimized helper.
def optimize_helper?(route) #:nodoc:
- route.ast.grep(Journey::Nodes::Star).empty? && route.requirements.except(:controller, :action).empty?
+ route.requirements.except(:controller, :action).empty?
end
# Generates the interpolation to be used in the optimized helper.
@@ -245,7 +214,10 @@ module ActionDispatch
end
route.required_parts.each_with_index do |part, i|
- string_route.gsub!(part.inspect, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}")
+ # Replace each route parameter
+ # e.g. :id for regular parameter or *path for globbing
+ # with ruby string interpolation code
+ string_route.gsub!(/(\*|:)#{part}/, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}")
end
string_route
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 40d38c59d6..8f6fff5d32 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -53,15 +53,18 @@ module ActionDispatch
# # assert that the redirection was to the url for @customer
# assert_redirected_to @customer
#
+ # # asserts that the redirection matches the regular expression
+ # assert_redirected_to %r(\Ahttp://example.org)
+ #
def assert_redirected_to(options = {}, message=nil)
assert_response(:redirect, message)
- return true if options == @response.location
+ return true if options === @response.location
redirect_is = normalize_argument_to_redirection(@response.location)
redirect_expected = normalize_argument_to_redirection(options)
message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
- assert_equal redirect_expected, redirect_is, message
+ assert_operator redirect_expected, :===, redirect_is, message
end
private
@@ -71,17 +74,21 @@ module ActionDispatch
end
def normalize_argument_to_redirection(fragment)
- case fragment
- when %r{^\w[A-Za-z\d+.-]*:.*}
- fragment
- when String
- @request.protocol + @request.host_with_port + fragment
- when :back
- raise RedirectBackError unless refer = @request.headers["Referer"]
- refer
- else
- @controller.url_for(fragment)
- end.delete("\0\r\n")
+ normalized = case fragment
+ when Regexp
+ fragment
+ when %r{^\w[A-Za-z\d+.-]*:.*}
+ fragment
+ when String
+ @request.protocol + @request.host_with_port + fragment
+ when :back
+ raise RedirectBackError unless refer = @request.headers["Referer"]
+ refer
+ else
+ @controller.url_for(fragment)
+ end
+
+ normalized.respond_to?(:delete) ? normalized.delete("\0\r\n") : normalized
end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
index 60d4af178a..4bcb8b9718 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
@@ -113,7 +113,7 @@ module ActionView
# == Caching multiple stylesheets into one
#
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
- # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
+ # compressed by gzip (leading to faster transfers). Caching will only happen if +config.perform_caching+
# is set to true (which is the case by default for the Rails production environment, but not for the development
# environment). Examples:
#
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 6ed265a516..6bd8e62e0d 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -29,7 +29,8 @@ module ActionView
# 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
# 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day
# 41 hrs, 59 mins, 30 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
- # 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
+ # 29 days, 23 hrs, 59 mins, 30 secs <-> 44 days, 23 hrs, 59 mins, 29 secs # => about 1 month
+ # 44 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 2 months
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
# 1 yr <-> 1 yr, 3 months # => about 1 year
# 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year
@@ -95,13 +96,18 @@ module ActionView
else locale.t :x_minutes, :count => 1
end
- when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
- when 45..89 then locale.t :about_x_hours, :count => 1
- when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
- when 1440..2519 then locale.t :x_days, :count => 1
- when 2520..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
- when 43200..86399 then locale.t :about_x_months, :count => 1
- when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
+ when 2...45 then locale.t :x_minutes, :count => distance_in_minutes
+ when 45...90 then locale.t :about_x_hours, :count => 1
+ # 90 mins up to 24 hours
+ when 90...1440 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
+ # 24 hours up to 42 hours
+ when 1440...2520 then locale.t :x_days, :count => 1
+ # 42 hours up to 30 days
+ when 2520...43200 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
+ # 30 days up to 60 days
+ when 43200...86400 then locale.t :about_x_months, :count => (distance_in_minutes.to_f / 43200.0).round
+ # 60 days up to 365 days
+ when 86400...525600 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
if from_time.acts_like?(:time) && to_time.acts_like?(:time)
fyear = from_time.year
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index b121ca9481..f5f397c9c0 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -179,6 +179,9 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
assert_redirected_to 'http://test.host/route_two'
end
assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_redirected_to %r(^http://test.host/route_two)
+ end
+ assert_raise(ActiveSupport::TestCase::Assertion) do
assert_redirected_to :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two'
end
assert_raise(ActiveSupport::TestCase::Assertion) do
@@ -212,6 +215,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
process :redirect_to_top_level_named_route
# assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return
assert_redirected_to "/action_pack_assertions/foo"
+ assert_redirected_to %r(/action_pack_assertions/foo)
end
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index e8546cb154..9efe328d62 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -236,6 +236,7 @@ class ActionCachingTestController < CachingController
caches_action :with_layout
caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } }
caches_action :layout_false, :layout => false
+ caches_action :with_layout_proc_param, :layout => Proc.new { |c| c.params[:layout] }
caches_action :record_not_found, :four_oh_four, :simple_runtime_error
caches_action :streaming
@@ -282,6 +283,7 @@ class ActionCachingTestController < CachingController
alias_method :edit, :index
alias_method :destroy, :index
alias_method :layout_false, :with_layout
+ alias_method :with_layout_proc_param, :with_layout
def expire
expire_action :controller => 'action_caching_test', :action => 'index'
@@ -403,11 +405,40 @@ class ActionCacheTest < ActionController::TestCase
get :layout_false
assert_response :success
assert_not_equal cached_time, @response.body
-
body = body_to_string(read_fragment('hostname.com/action_caching_test/layout_false'))
assert_equal cached_time, body
end
+ def test_action_cache_with_layout_and_layout_cache_false_via_proc
+ get :with_layout_proc_param, :layout => false
+ assert_response :success
+ cached_time = content_to_cache
+ assert_not_equal cached_time, @response.body
+ assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
+ reset!
+
+ get :with_layout_proc_param, :layout => false
+ assert_response :success
+ assert_not_equal cached_time, @response.body
+ body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param'))
+ assert_equal cached_time, body
+ end
+
+ def test_action_cache_with_layout_and_layout_cache_true_via_proc
+ get :with_layout_proc_param, :layout => true
+ assert_response :success
+ cached_time = content_to_cache
+ assert_not_equal cached_time, @response.body
+ assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
+ reset!
+
+ get :with_layout_proc_param, :layout => true
+ assert_response :success
+ assert_not_equal cached_time, @response.body
+ body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param'))
+ assert_equal @response.body, body
+ end
+
def test_action_cache_conditional_options
@request.env['HTTP_ACCEPT'] = 'application/json'
get :index
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 9441b46d47..bcb4e6a766 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -891,23 +891,6 @@ class RouteSetTest < ActiveSupport::TestCase
MockController.build(set.url_helpers).new
end
- def test_named_route_hash_access_method
- controller = setup_named_route_test
-
- assert_equal(
- { :controller => 'people', :action => 'show', :id => 5, :use_route => "show", :only_path => false },
- controller.send(:hash_for_show_url, :id => 5))
-
- assert_equal(
- { :controller => 'people', :action => 'index', :use_route => "index", :only_path => false },
- controller.send(:hash_for_index_url))
-
- assert_equal(
- { :controller => 'people', :action => 'show', :id => 5, :use_route => "show", :only_path => true },
- controller.send(:hash_for_show_path, :id => 5)
- )
- end
-
def test_named_route_url_method
controller = setup_named_route_test
@@ -919,7 +902,6 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "http://test.host/admin/users", controller.send(:users_url)
assert_equal '/admin/users', controller.send(:users_path)
- assert_equal '/admin/users', url_for(set, controller.send(:hash_for_users_url), { :controller => 'users', :action => 'index' })
end
def test_named_route_url_method_with_anchor
diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb
index ec6ba494dc..bc7cad8db5 100644
--- a/actionpack/test/dispatch/header_test.rb
+++ b/actionpack/test/dispatch/header_test.rb
@@ -13,4 +13,9 @@ class HeaderTest < ActiveSupport::TestCase
assert_equal "text/plain", @headers["CONTENT_TYPE"]
assert_equal "text/plain", @headers["HTTP_CONTENT_TYPE"]
end
+
+ test "fetch" do
+ assert_equal "text/plain", @headers.fetch("content-type", nil)
+ assert_equal "not found", @headers.fetch('not-found', 'not found')
+ end
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 4b8d308043..a96d2edcf9 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -2512,3 +2512,22 @@ private
%(<html><body>You are being <a href="#{ERB::Util.h(url)}">redirected</a>.</body></html>)
end
end
+
+class TestConstraintsAccessingParameters < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get "/:foo" => ok, :constraints => lambda { |r| r.params[:foo] == 'foo' }
+ get "/:bar" => ok
+ end
+ end
+
+ def app; Routes end
+
+ test "parameters are reset between constraint checks" do
+ get "/bar"
+ assert_equal nil, @request.params[:foo]
+ assert_equal "bar", @request.params[:bar]
+ end
+end
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 928c767084..ff85a675a2 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -21,7 +21,7 @@ class DateHelperTest < ActionView::TestCase
def assert_distance_of_time_in_words(from, to=nil)
to ||= from
- # 0..1 with :include_seconds => true
+ # 0..1 minute with :include_seconds => true
assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 0.seconds, :include_seconds => true)
assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 4.seconds, :include_seconds => true)
assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 5.seconds, :include_seconds => true)
@@ -35,7 +35,7 @@ class DateHelperTest < ActionView::TestCase
assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, :include_seconds => true)
assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, :include_seconds => true)
- # 0..1 with :include_seconds => false
+ # 0..1 minute with :include_seconds => false
assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds, :include_seconds => false)
assert_equal "less than a minute", distance_of_time_in_words(from, to + 4.seconds, :include_seconds => false)
assert_equal "less than a minute", distance_of_time_in_words(from, to + 5.seconds, :include_seconds => false)
@@ -48,42 +48,53 @@ class DateHelperTest < ActionView::TestCase
assert_equal "1 minute", distance_of_time_in_words(from, to + 59.seconds, :include_seconds => false)
assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, :include_seconds => false)
assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, :include_seconds => false)
- # First case 0..1
+
+ # Note that we are including a 30-second boundary around the interval we
+ # want to test. For instance, "1 minute" is actually 30s to 1m29s. The
+ # reason for doing this is simple -- in `distance_of_time_to_words`, when we
+ # take the distance between our two Time objects in seconds and convert it
+ # to minutes, we round the number. So 29s gets rounded down to 0m, 30s gets
+ # rounded up to 1m, and 1m29s gets rounded down to 1m. A similar thing
+ # happens with the other cases.
+
+ # First case 0..1 minute
assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds)
assert_equal "less than a minute", distance_of_time_in_words(from, to + 29.seconds)
assert_equal "1 minute", distance_of_time_in_words(from, to + 30.seconds)
assert_equal "1 minute", distance_of_time_in_words(from, to + 1.minutes + 29.seconds)
- # 2..44
+ # 2 minutes up to 45 minutes
assert_equal "2 minutes", distance_of_time_in_words(from, to + 1.minutes + 30.seconds)
assert_equal "44 minutes", distance_of_time_in_words(from, to + 44.minutes + 29.seconds)
- # 45..89
+ # 45 minutes up to 90 minutes
assert_equal "about 1 hour", distance_of_time_in_words(from, to + 44.minutes + 30.seconds)
assert_equal "about 1 hour", distance_of_time_in_words(from, to + 89.minutes + 29.seconds)
- # 90..1439
+ # 90 minutes up to 24 hours
assert_equal "about 2 hours", distance_of_time_in_words(from, to + 89.minutes + 30.seconds)
assert_equal "about 24 hours", distance_of_time_in_words(from, to + 23.hours + 59.minutes + 29.seconds)
- # 1440..2519
+ # 24 hours up to 42 hours
assert_equal "1 day", distance_of_time_in_words(from, to + 23.hours + 59.minutes + 30.seconds)
assert_equal "1 day", distance_of_time_in_words(from, to + 41.hours + 59.minutes + 29.seconds)
- # 2520..43199
+ # 42 hours up to 30 days
assert_equal "2 days", distance_of_time_in_words(from, to + 41.hours + 59.minutes + 30.seconds)
assert_equal "3 days", distance_of_time_in_words(from, to + 2.days + 12.hours)
assert_equal "30 days", distance_of_time_in_words(from, to + 29.days + 23.hours + 59.minutes + 29.seconds)
- # 43200..86399
+ # 30 days up to 60 days
assert_equal "about 1 month", distance_of_time_in_words(from, to + 29.days + 23.hours + 59.minutes + 30.seconds)
- assert_equal "about 1 month", distance_of_time_in_words(from, to + 59.days + 23.hours + 59.minutes + 29.seconds)
+ assert_equal "about 1 month", distance_of_time_in_words(from, to + 44.days + 23.hours + 59.minutes + 29.seconds)
+ assert_equal "about 2 months", distance_of_time_in_words(from, to + 44.days + 23.hours + 59.minutes + 30.seconds)
+ assert_equal "about 2 months", distance_of_time_in_words(from, to + 59.days + 23.hours + 59.minutes + 29.seconds)
- # 86400..525599
+ # 60 days up to 365 days
assert_equal "2 months", distance_of_time_in_words(from, to + 59.days + 23.hours + 59.minutes + 30.seconds)
assert_equal "12 months", distance_of_time_in_words(from, to + 1.years - 31.seconds)
- # > 525599
+ # >= 365 days
assert_equal "about 1 year", distance_of_time_in_words(from, to + 1.years - 30.seconds)
assert_equal "about 1 year", distance_of_time_in_words(from, to + 1.years + 3.months - 1.day)
assert_equal "over 1 year", distance_of_time_in_words(from, to + 1.years + 6.months)
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index fc5aaf9f8f..fc5aaf9f8f 100755..100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 7f7fb90d87..3d35b5bb6f 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -83,6 +83,7 @@ module ActiveModel
# in-place attributes.
#
# person.name_will_change!
+ # person.name_change # => ['Bill', 'Bill']
# person.name << 'y'
# person.name_change # => ['Bill', 'Billy']
module Dirty
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index f3781f7a68..f5ea285ccb 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -110,17 +110,24 @@ module ActiveModel
end
end
- private
- # Fires notifications to model's observers
- #
- # def save
- # notify_observers(:before_save)
- # ...
- # notify_observers(:after_save)
- # end
- def notify_observers(method)
- self.class.notify_observers(method, self)
- end
+ # Fires notifications to model's observers
+ #
+ # def save
+ # notify_observers(:before_save)
+ # ...
+ # notify_observers(:after_save)
+ # end
+ #
+ # Custom notifications can be sent in a similar fashion:
+ #
+ # notify_observers(:custom_notification, :foo)
+ #
+ # This will call +custom_notification+, passing as arguments
+ # the current object and :foo.
+ #
+ def notify_observers(method, *extra_args)
+ self.class.notify_observers(method, self, *extra_args)
+ end
end
# == Active Model Observers
@@ -230,10 +237,10 @@ module ActiveModel
# Send observed_method(object) if the method exists and
# the observer is enabled for the given object's class.
- def update(observed_method, object, &block) #:nodoc:
+ def update(observed_method, object, *extra_args, &block) #:nodoc:
return unless respond_to?(observed_method)
return if disabled_for?(object)
- send(observed_method, object, &block)
+ send(observed_method, object, *extra_args, &block)
end
# Special method sent by the observed class when it is inherited.
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index 9891be8d77..ade6026602 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -14,8 +14,8 @@ class FooObserver < ActiveModel::Observer
attr_accessor :stub
- def on_spec(record)
- stub.event_with(record) if stub
+ def on_spec(record, *args)
+ stub.event_with(record, *args) if stub
end
def around_save(record)
@@ -138,12 +138,26 @@ class ObserverTest < ActiveModel::TestCase
foo = Foo.new
FooObserver.instance.stub = stub
FooObserver.instance.stub.expects(:event_with).with(foo)
- Foo.send(:notify_observers, :on_spec, foo)
+ Foo.notify_observers(:on_spec, foo)
+ end
+
+ test "calls existing observer event from the instance" do
+ foo = Foo.new
+ FooObserver.instance.stub = stub
+ FooObserver.instance.stub.expects(:event_with).with(foo)
+ foo.notify_observers(:on_spec)
+ end
+
+ test "passes extra arguments" do
+ foo = Foo.new
+ FooObserver.instance.stub = stub
+ FooObserver.instance.stub.expects(:event_with).with(foo, :bar)
+ Foo.send(:notify_observers, :on_spec, foo, :bar)
end
test "skips nonexistent observer event" do
foo = Foo.new
- Foo.send(:notify_observers, :whatever, foo)
+ Foo.notify_observers(:whatever, foo)
end
test "update passes a block on to the observer" do
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index aa84dacf07..a661a44f1f 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,11 @@
## Rails 4.0.0 (unreleased) ##
+* Added default order to `first` to assure consistent results among
+ diferent database engines. Introduced `take` as a replacement to
+ the old behavior of `first`.
+
+ *Marcelo Silveira*
+
* Added an :index option to automatically create indexes for references
and belongs_to statements in migrations.
@@ -580,19 +586,6 @@
a URI that specifies the connection configuration. For example:
ActiveRecord::Base.establish_connection 'postgres://localhost/foo'
-* Active Record's dynamic finder will now raise the error if you passing in less number of arguments than what you call in method signature.
-
- So if you were doing this and expecting the second argument to be nil:
-
- User.find_by_username_and_group("sikachu")
-
- You'll now get `ArgumentError: wrong number of arguments (1 for 2).` You'll then have to do this:
-
- User.find_by_username_and_group("sikachu", nil)
-
- *Prem Sichanugrist*
-
-
## Rails 3.1.0 (August 30, 2011) ##
* Add a proxy_association method to association proxies, which can be called by association
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 4090293b56..7feb0b75a0 100755..100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rubygems/package_task'
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index ad029d1101..50d16b16a9 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -81,7 +81,7 @@ module ActiveRecord
def method_missing(method, *args, &block)
match = DynamicFinderMatch.match(method)
if match && match.instantiator?
- send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
+ scoped.send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
proxy_association.send :set_owner_attributes, r
proxy_association.send :add_to_target, r
yield(r) if block_given?
@@ -101,7 +101,7 @@ module ActiveRecord
end
else
- scoped.readonly(nil).send(method, *args, &block)
+ scoped.readonly(nil).public_send(method, *args, &block)
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index fc3906d395..189985b671 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -304,21 +304,22 @@ module ActiveRecord #:nodoc:
# (or a bad spelling of an existing one).
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
# specified in the association definition.
- # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
+ # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
+ # <tt>attributes=</tt> method.
+ # You can inspect the +attribute+ property of the exception object to determine which attribute
+ # triggered the error.
# * ConnectionNotEstablished - No connection has been established. Use <tt>establish_connection</tt>
# before querying.
- # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
- # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
- # nothing was found, please check its documentation for further details.
- # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
# AttributeAssignmentError
# objects that should be inspected to determine which attributes triggered the errors.
- # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
- # <tt>attributes=</tt> method.
- # You can inspect the +attribute+ property of the exception object to determine which attribute
- # triggered the error.
+ # * RecordInvalid - raised by save! and create! when the record is invalid.
+ # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
+ # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
+ # nothing was found, please check its documentation for further details.
+ # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
+ # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
#
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 3546873550..f0b6ae2b7d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -408,7 +408,7 @@ module ActiveRecord
# t.remove(:qualification)
# t.remove(:qualification, :experience)
def remove(*column_names)
- @base.remove_column(@table_name, column_names)
+ @base.remove_column(@table_name, *column_names)
end
# Removes the given index from the table.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 30a4f9aa35..e7a4f061fd 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -618,8 +618,6 @@ module ActiveRecord
end
def columns_for_remove(table_name, *column_names)
- column_names = column_names.flatten
-
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank?
column_names.map {|column_name| quote_column_name(column_name) }
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index b7e1513422..9af8e46120 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -95,7 +95,7 @@ module ActiveRecord
case type
when :string, :text then value
- when :integer then value.to_i rescue value ? 1 : 0
+ when :integer then value.to_i
when :float then value.to_f
when :decimal then klass.value_to_decimal(value)
when :datetime, :timestamp then klass.string_to_time(value)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 273c165084..68cf495025 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1233,7 +1233,10 @@ module ActiveRecord
# Construct a clean list of column names from the ORDER BY clause, removing
# any ASC/DESC modifiers
- order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
+ order_columns = orders.collect do |s|
+ s = s.to_sql unless s.is_a?(String)
+ s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
+ end
order_columns.delete_if { |c| c.blank? }
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 83f75e3505..44e407a561 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -446,7 +446,7 @@ module ActiveRecord
def remove_column(table_name, *column_names) #:nodoc:
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
- column_names.flatten.each do |column_name|
+ column_names.each do |column_name|
alter_table(table_name) do |definition|
definition.columns.delete(definition[column_name])
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 01cacf6153..313fdb3487 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -70,7 +70,7 @@ module ActiveRecord
# the threshold is set to 0.
#
# As the name of the method suggests this only applies to automatic
- # EXPLAINs, manual calls to +ActiveRecord::Relation#explain+ run.
+ # EXPLAINs, manual calls to <tt>ActiveRecord::Relation#explain</tt> run.
def silence_auto_explain
current = Thread.current
original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 95565b503a..4d8283bcff 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -3,7 +3,7 @@ require 'active_support/deprecation'
module ActiveRecord
module Querying
- delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
+ delegate :find, :take, :take!, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped
delegate :find_by, :find_by!, :to => :scoped
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
@@ -11,7 +11,7 @@ module ActiveRecord
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
:where, :preload, :eager_load, :includes, :from, :lock, :readonly,
:having, :create_with, :uniq, :references, :none, :to => :scoped
- delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :to => :scoped
+ delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 333d31d8a3..779e052e3c 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -390,6 +390,8 @@ module ActiveRecord
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
# +after_destroy+ callbacks, use the +destroy_all+ method instead.
def delete_all(conditions = nil)
+ raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
+
if conditions
where(conditions).delete_all
else
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index db894c8aa9..f388b75c05 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -139,6 +139,16 @@ module ActiveRecord
end
end
+ # Pluck all the ID's for the relation using the table's primary key
+ #
+ # Examples:
+ #
+ # Person.ids # SELECT people.id FROM people
+ # Person.joins(:companies).ids # SELECT people.id FROM PEOPLE INNER JOIN companies ON companies.person_id = people.id
+ def ids
+ pluck primary_key
+ end
+
private
def perform_calculation(operation, column_name, options = {})
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 416b55f5c5..3c9c9c4e84 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -60,6 +60,28 @@ module ActiveRecord
where(*args).first!
end
+ # Gives a record (or N records if a parameter is supplied) without any implied
+ # order. The order will depend on the database implementation.
+ # If an order is supplied it will be respected.
+ #
+ # Examples:
+ #
+ # Person.take # returns an object fetched by SELECT * FROM people
+ # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
+ # Person.where(["name LIKE '%?'", name]).take
+ def take(limit = nil)
+ limit ? limit(limit).to_a : find_take
+ end
+
+ # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>take!</tt> accepts no arguments.
+ def take!
+ take or raise RecordNotFound
+ end
+
+ # Find the first record (or first N records if a parameter is supplied).
+ # If no order is defined it will order by primary key.
+ #
# Examples:
#
# Person.first # returns the first object fetched by SELECT * FROM people
@@ -67,7 +89,15 @@ module ActiveRecord
# Person.where(["user_name = :u", { :u => user_name }]).first
# Person.order("created_on DESC").offset(5).first
def first(limit = nil)
- limit ? limit(limit).to_a : find_first
+ if limit
+ if order_values.empty? && primary_key
+ order(arel_table[primary_key].asc).limit(limit).to_a
+ else
+ limit(limit).to_a
+ end
+ else
+ find_first
+ end
end
# Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -76,6 +106,9 @@ module ActiveRecord
first or raise RecordNotFound
end
+ # Find the last record (or last N records if a parameter is supplied).
+ # If no order is defined it will order by primary key.
+ #
# Examples:
#
# Person.last # returns the last object fetched by SELECT * FROM people
@@ -83,8 +116,8 @@ module ActiveRecord
# Person.order("created_on DESC").offset(5).last
def last(limit = nil)
if limit
- if order_values.empty?
- order("#{primary_key} DESC").limit(limit).reverse
+ if order_values.empty? && primary_key
+ order(arel_table[primary_key].desc).limit(limit).reverse
else
to_a.last(limit)
end
@@ -217,8 +250,11 @@ module ActiveRecord
if match.bang? && result.blank?
raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
else
- yield(result) if block_given?
- result
+ if block_given? && result
+ yield(result)
+ else
+ result
+ end
end
end
@@ -312,11 +348,24 @@ module ActiveRecord
end
end
+ def find_take
+ if loaded?
+ @records.take(1).first
+ else
+ @take ||= limit(1).to_a.first
+ end
+ end
+
def find_first
if loaded?
@records.first
else
- @first ||= limit(1).to_a[0]
+ @first ||=
+ if order_values.empty? && primary_key
+ order(arel_table[primary_key].asc).limit(1).to_a.first
+ else
+ limit(1).to_a.first
+ end
end
end
@@ -328,7 +377,7 @@ module ActiveRecord
if offset_value || limit_value
to_a.last
else
- reverse_order.limit(1).to_a[0]
+ reverse_order.limit(1).to_a.first
end
end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index a98e5b115c..f74fe42dc2 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1703,4 +1703,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
ensure
ActiveRecord::Base.dependent_restrict_raises = option_before
end
+
+ def test_collection_association_with_private_kernel_method
+ firm = companies(:first_firm)
+ assert_equal [accounts(:signals37)], firm.accounts.open
+ end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 8a4ce5e963..35e0045e4c 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -125,20 +125,13 @@ class BasicsTest < ActiveRecord::TestCase
unless current_adapter?(:PostgreSQLAdapter,:OracleAdapter,:SQLServerAdapter)
def test_limit_with_comma
- assert_nothing_raised do
- Topic.limit("1,2").all
- end
+ assert Topic.limit("1,2").all
end
end
def test_limit_without_comma
- assert_nothing_raised do
- assert_equal 1, Topic.limit("1").all.length
- end
-
- assert_nothing_raised do
- assert_equal 1, Topic.limit(1).all.length
- end
+ assert_equal 1, Topic.limit("1").all.length
+ assert_equal 1, Topic.limit(1).all.length
end
def test_invalid_limit
@@ -175,6 +168,24 @@ class BasicsTest < ActiveRecord::TestCase
assert Topic.table_exists?
end
+ def test_finder_block
+ t = Topic.first
+ found = nil
+ Topic.find_by_id(t.id) { |f| found = f }
+ assert_equal t, found
+ end
+
+ def test_finder_block_nothing_found
+ bad_id = Topic.maximum(:id) + 1
+ assert_nil Topic.find_by_id(bad_id) { |f| raise }
+ end
+
+ def test_find_returns_block_value
+ t = Topic.first
+ x = Topic.find_by_id(t.id) { |f| "hi mom!" }
+ assert_equal "hi mom!", x
+ end
+
def test_preserving_date_objects
if current_adapter?(:SybaseAdapter)
# Sybase ctlib does not (yet?) support the date type; use datetime instead.
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 2e566ff873..e096585f62 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -465,4 +465,8 @@ class CalculationsTest < ActiveRecord::TestCase
Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
assert_equal [7], Company.joins(:contracts).pluck(:developer_id)
end
+
+ def test_plucks_with_ids
+ assert_equal Company.all.map(&:id).sort, Company.ids.sort
+ end
end
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
index ccc57cb876..4fcf8a33a4 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -24,6 +24,30 @@ module ActiveRecord
assert !column.type_cast('off')
assert !column.type_cast('OFF')
end
+
+ def test_type_cast_integer
+ column = Column.new("field", nil, "integer")
+ assert_equal 1, column.type_cast(1)
+ assert_equal 1, column.type_cast('1')
+ assert_equal 1, column.type_cast('1ignore')
+ assert_equal 0, column.type_cast('bad1')
+ assert_equal 0, column.type_cast('bad')
+ assert_equal 1, column.type_cast(1.7)
+ assert_nil column.type_cast(nil)
+ end
+
+ def test_type_cast_non_integer_to_integer
+ column = Column.new("field", nil, "integer")
+ assert_raises(NoMethodError) do
+ column.type_cast([])
+ end
+ assert_raises(NoMethodError) do
+ column.type_cast(true)
+ end
+ assert_raises(NoMethodError) do
+ column.type_cast(false)
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 29469c42ed..54801bd101 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -143,6 +143,26 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [Account], accounts.collect(&:class).uniq
end
+ def test_take
+ assert_equal topics(:first), Topic.take
+ end
+
+ def test_take_failing
+ assert_nil Topic.where("title = 'This title does not exist'").take
+ end
+
+ def test_take_bang_present
+ assert_nothing_raised do
+ assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").take!
+ end
+ end
+
+ def test_take_bang_missing
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.where("title = 'This title does not exist'").take!
+ end
+ end
+
def test_first
assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title
end
@@ -163,6 +183,12 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_first_have_primary_key_order_by_default
+ expected = topics(:first)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.first
+ end
+
def test_model_class_responds_to_first_bang
assert Topic.first!
Topic.delete_all
@@ -191,7 +217,8 @@ class FinderTest < ActiveRecord::TestCase
end
end
- def test_first_and_last_with_integer_should_use_sql_limit
+ def test_take_and_first_and_last_with_integer_should_use_sql_limit
+ assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries }
assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries }
assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries }
end
@@ -212,7 +239,8 @@ class FinderTest < ActiveRecord::TestCase
assert_no_match(/LIMIT/, query.first)
end
- def test_first_and_last_with_integer_should_return_an_array
+ def test_take_and_first_and_last_with_integer_should_return_an_array
+ assert_kind_of Array, Topic.take(5)
assert_kind_of Array, Topic.first(5)
assert_kind_of Array, Topic.last(5)
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index e71b84a3a5..f788690b0d 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -588,14 +588,14 @@ class ChangeTableMigrationsTest < ActiveRecord::TestCase
def test_remove_drops_single_column
with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, [:bar])
+ @connection.expects(:remove_column).with(:delete_me, :bar)
t.remove :bar
end
end
def test_remove_drops_multiple_columns
with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, [:bar, :baz])
+ @connection.expects(:remove_column).with(:delete_me, :bar, :baz)
t.remove :bar, :baz
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 4b8b57bac2..3ef357e297 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -688,6 +688,10 @@ class RelationTest < ActiveRecord::TestCase
assert davids.loaded?
end
+ def test_delete_all_limit_error
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all }
+ end
+
def test_select_argument_error
assert_raises(ArgumentError) { Developer.select }
end
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index fbdfaa2c29..7b993d5a2c 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -198,6 +198,11 @@ class Account < ActiveRecord::Base
@destroyed_account_ids ||= Hash.new { |h,k| h[k] = [] }
end
+ # Test private kernel method through collection proxy using has_many.
+ def self.open
+ where('firm_name = ?', '37signals')
+ end
+
before_destroy do |account|
if account.firm
Account.destroyed_account_ids[account.firm.id] << account.id
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index d32c0f3aed..82921741b8 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -32,6 +32,9 @@
* Unicode database updated to 6.1.0.
+* Adds `encode_big_decimal_as_string` option to force JSON serialization of BigDecimals as numeric instead
+ of wrapping them in strings for safety.
+
## Rails 3.2.2 (March 1, 2012) ##
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 822c9d98ae..822c9d98ae 100755..100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index b9f196d7a9..55791bfa56 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -280,7 +280,7 @@ module ActiveSupport
end
end
if entry && entry.expired?
- race_ttl = options[:race_condition_ttl].to_f
+ race_ttl = options[:race_condition_ttl].to_i
if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl
entry.expires_at = Time.now + race_ttl
write_entry(key, entry, :expires_in => race_ttl * 2)
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 8e6a3bc5a8..e7316b23b3 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -23,7 +23,7 @@ module ActiveSupport
end
def clear(options = nil)
- root_dirs = Dir.entries(cache_path).reject{|f| f.in?(EXCLUDED_DIRS)}
+ root_dirs = Dir.entries(cache_path).reject{|f| f.in?(EXCLUDED_DIRS + [".gitkeep"])}
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
end
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index fdeea4c148..b862b5ae2a 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -18,7 +18,7 @@ class Hash
end
# Replaces the hash with only the given keys.
- # Returns a hash contained the removed key/value pairs
+ # Returns a hash containing the removed key/value pairs.
# {:a => 1, :b => 2, :c => 3, :d => 4}.slice!(:a, :b) # => {:c => 3, :d => 4}
def slice!(*keys)
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 73b6a42505..9102537810 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -6,24 +6,31 @@ module ActiveSupport
# Whether to print a backtrace along with the warning.
attr_accessor :debug
- # Returns the set behavior or if one isn't set, defaults to +:stderr+
+ # Returns the current behavior or if one isn't set, defaults to +:stderr+
def behavior
@behavior ||= [DEFAULT_BEHAVIORS[:stderr]]
end
- # Sets the behavior to the specified value. Can be a single value or an array.
+ # Sets the behavior to the specified value. Can be a single value, array, or
+ # an object that responds to +call+.
#
- # Available behaviors:
+ # Available behaviors:
#
- # [+:stderr+] Print deprecations to +$stderror+
- # [+:log+] Send to +Rails.logger+
- # [+:notify+] Instrument using +ActiveSupport::Notifications+
- # [+:silence+] Do nothing
+ # [+stderr+] Log all deprecation warnings to +$stderr+.
+ # [+log+] Log all deprecation warnings to +Rails.logger+.
+ # [+notify] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
+ # [+silence+] Do nothing.
#
- # Examples
+ # Setting behaviors only affects deprecations that happen after boot time.
+ # Deprecation warnings raised by gems are not affected by this setting because
+ # they happen before Rails boots up.
#
# ActiveSupport::Deprecation.behavior = :stderr
# ActiveSupport::Deprecation.behavior = [:stderr, :log]
+ # ActiveSupport::Deprecation.behavior = MyCustomHandler
+ # ActiveSupport::Deprecation.behavior = proc { |message, callstack|
+ # # custom stuff
+ # }
def behavior=(behavior)
@behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 61876d89a9..4fcd32edf2 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -106,7 +106,7 @@ module ActiveSupport
# a nicer looking title. +titleize+ is meant for creating pretty output. It is not
# used in the Rails internals.
#
- # +titleize+ is also aliased as as +titlecase+.
+ # +titleize+ is also aliased as +titlecase+.
#
# Examples:
# "man from the boondocks".titleize # => "Man From The Boondocks"
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index b2adfea273..ab12f3f454 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -17,6 +17,7 @@ module ActiveSupport
class << self
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
:escape_html_entities_in_json, :escape_html_entities_in_json=,
+ :encode_big_decimal_as_string, :encode_big_decimal_as_string=,
:to => :'ActiveSupport::JSON::Encoding'
end
@@ -104,6 +105,9 @@ module ActiveSupport
# If true, use ISO 8601 format for dates and times. Otherwise, fall back to the Active Support legacy format.
attr_accessor :use_standard_json_time_format
+ # If false, serializes BigDecimal objects as numeric instead of wrapping them in a string
+ attr_accessor :encode_big_decimal_as_string
+
attr_accessor :escape_regex
attr_reader :escape_html_entities_in_json
@@ -133,6 +137,7 @@ module ActiveSupport
self.use_standard_json_time_format = true
self.escape_html_entities_in_json = false
+ self.encode_big_decimal_as_string = true
end
end
end
@@ -182,6 +187,12 @@ class Numeric
def encode_json(encoder) to_s end #:nodoc:
end
+class Float
+ # Encoding Infinity or NaN to JSON should return "null". The default returns
+ # "Infinity" or "NaN" what breaks parsing the JSON. E.g. JSON.parse('[NaN]').
+ def as_json(options = nil) finite? ? self : NilClass::AS_JSON end #:nodoc:
+end
+
class BigDecimal
# A BigDecimal would be naturally represented as a JSON number. Most libraries,
# however, parse non-integer JSON numbers directly as floats. Clients using
@@ -191,7 +202,15 @@ class BigDecimal
# That's why a JSON string is returned. The JSON literal is not numeric, but if
# the other end knows by contract that the data is supposed to be a BigDecimal,
# it still has the chance to post-process the string and get the real value.
- def as_json(options = nil) to_s end #:nodoc:
+ #
+ # Use ActiveSupport.use_standard_json_big_decimal_format = true to override this behaviour
+ def as_json(options = nil) #:nodoc:
+ if finite?
+ ActiveSupport.encode_big_decimal_as_string ? to_s : self
+ else
+ NilClass::AS_JSON
+ end
+ end
end
class Regexp
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index 85e84bc203..7aecdd11d3 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -48,6 +48,7 @@ module ActiveSupport
# end
# end
#
+ # Exceptions raised inside exception handlers are not propagated up.
def rescue_from(*klasses, &block)
options = klasses.extract_options!
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 0baf9e1914..9543e50395 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -204,6 +204,7 @@ module ActiveSupport
@current_period = nil
end
+ # Returns the offset of this time zone from UTC in seconds.
def utc_offset
if @utc_offset
@utc_offset
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 5365713e6e..bb9ce23276 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -568,6 +568,13 @@ class FileStoreTest < ActiveSupport::TestCase
include CacheDeleteMatchedBehavior
include CacheIncrementDecrementBehavior
+ def test_clear
+ filepath = File.join(cache_dir, ".gitkeep")
+ FileUtils.touch(filepath)
+ @cache.clear
+ assert File.exist?(filepath)
+ end
+
def test_key_transformation
key = @cache.send(:key_file_path, "views/index?id=1")
assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index a2e61d88d5..0566ebf291 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -27,6 +27,10 @@ class TestJSONEncoding < ActiveSupport::TestCase
NilTests = [[ nil, %(null) ]]
NumericTests = [[ 1, %(1) ],
[ 2.5, %(2.5) ],
+ [ 0.0/0.0, %(null) ],
+ [ 1.0/0.0, %(null) ],
+ [ -1.0/0.0, %(null) ],
+ [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
[ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]]
StringTests = [[ 'this is the <string>', %("this is the \\u003Cstring\\u003E")],
@@ -270,6 +274,17 @@ class TestJSONEncoding < ActiveSupport::TestCase
JSON.parse(json_string_and_date))
end
+ def test_opt_out_big_decimal_string_serialization
+ big_decimal = BigDecimal('2.5')
+
+ begin
+ ActiveSupport.encode_big_decimal_as_string = false
+ assert_equal big_decimal.to_s, big_decimal.to_json
+ ensure
+ ActiveSupport.encode_big_decimal_as_string = true
+ end
+ end
+
protected
def object_keys(json_object)
diff --git a/guides/code/getting_started/app/views/posts/_form.html.erb b/guides/code/getting_started/app/views/posts/_form.html.erb
index b35ea2f237..f22139938c 100644
--- a/guides/code/getting_started/app/views/posts/_form.html.erb
+++ b/guides/code/getting_started/app/views/posts/_form.html.erb
@@ -10,12 +10,12 @@
</div>
<% end %>
<p>
- <%= f.label :title %><br>
+ <%= f.label :title %><br />
<%= f.text_field :title %>
</p>
<p>
- <%= f.label :text %><br>
+ <%= f.label :text %><br />
<%= f.text_area :text %>
</p>
diff --git a/guides/code/getting_started/app/views/posts/show.html.erb b/guides/code/getting_started/app/views/posts/show.html.erb
index aea28cd5a2..0580879c1a 100644
--- a/guides/code/getting_started/app/views/posts/show.html.erb
+++ b/guides/code/getting_started/app/views/posts/show.html.erb
@@ -8,5 +8,21 @@
<%= @post.text %>
</p>
-<%= link_to 'Back', :action => :index %>
-| <%= link_to 'Edit', :action => :edit, :id => @post.id %>
+
+<h2>Add a comment:</h2>
+<%= form_for([@post, @post.comments.build]) do |f| %>
+ <p>
+ <%= f.label :commenter %><br />
+ <%= f.text_field :commenter %>
+ </p>
+ <p>
+ <%= f.label :body %><br />
+ <%= f.text_area :body %>
+ </p>
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
+
+<%= link_to 'Edit Post', edit_post_path(@post) %> |
+<%= link_to 'Back to Posts', posts_path %>
diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb
index 6095a05a58..04a6bd374e 100644
--- a/guides/code/getting_started/config/routes.rb
+++ b/guides/code/getting_started/config/routes.rb
@@ -1,15 +1,8 @@
Blog::Application.routes.draw do
- # resources :posts do
- # resources :comments
- # end
- get "posts" => "posts#index"
- get "posts/new"
- post "posts/create"
- get "posts/:id" => "posts#show", :as => :post
- get "posts/:id/edit" => "posts#edit"
- put "posts/:id" => "posts#update"
- delete "posts/:id" => "posts#destroy"
+ resources :posts do
+ resources :comments
+ end
# The priority is based upon order of creation:
# first created -> highest priority.
diff --git a/guides/code/getting_started/test/fixtures/tags.yml b/guides/code/getting_started/test/fixtures/tags.yml
deleted file mode 100644
index 8485668908..0000000000
--- a/guides/code/getting_started/test/fixtures/tags.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-# Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html
-
-one:
- name: MyString
- post:
-
-two:
- name: MyString
- post:
diff --git a/guides/source/action_mailer_basics.textile b/guides/source/action_mailer_basics.textile
index c277f764e7..ebe774fbef 100644
--- a/guides/source/action_mailer_basics.textile
+++ b/guides/source/action_mailer_basics.textile
@@ -4,7 +4,7 @@ This guide should provide you with all you need to get started in sending and re
endprologue.
-WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
+WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not work in earlier versions of Rails.
h3. Introduction
diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile
index 902ceeb78b..f9dbaa1125 100644
--- a/guides/source/active_record_querying.textile
+++ b/guides/source/active_record_querying.textile
@@ -910,7 +910,7 @@ This code looks fine at the first sight. But the problem lies within the total n
Active Record lets you specify in advance all the associations that are going to be loaded. This is possible by specifying the +includes+ method of the +Model.find+ call. With +includes+, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries.
-Revisiting the above case, we could rewrite +Client.all+ to use eager load addresses:
+Revisiting the above case, we could rewrite +Client.limit(10)+ to use eager load addresses:
<ruby>
clients = Client.includes(:address).limit(10)
diff --git a/guides/source/caching_with_rails.textile b/guides/source/caching_with_rails.textile
index 12bc32f4e1..e455b504ce 100644
--- a/guides/source/caching_with_rails.textile
+++ b/guides/source/caching_with_rails.textile
@@ -157,7 +157,7 @@ and you can expire it using the +expire_fragment+ method, like so:
expire_fragment(:controller => 'products', :action => 'recent', :action_suffix => 'all_products')
</ruby>
-If you don't want the cache block to bind to the action that called it, You can also use globally keyed fragments by calling the +cache+ method with a key, like so:
+If you don't want the cache block to bind to the action that called it, you can also use globally keyed fragments by calling the +cache+ method with a key:
<ruby>
<% cache('all_available_products') do %>
diff --git a/guides/source/command_line.textile b/guides/source/command_line.textile
index 858ce47db1..6dc78880f8 100644
--- a/guides/source/command_line.textile
+++ b/guides/source/command_line.textile
@@ -446,6 +446,18 @@ app/model/post.rb:
NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines.
+By default, +rake notes+ will look in the +app+, +config+, +lib+, +script+ and +test+ directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable +SOURCE_ANNOTATION_DIRECTORIES+.
+
+<shell>
+$ export SOURCE_ANNOTATION_DIRECTORIES='rspec,vendor'
+$ rake notes
+(in /home/foobar/commandsapp)
+app/model/user.rb:
+ * [ 35] [FIXME] User should have a subscription at this point
+rspec/model/user_spec.rb:
+ * [122] [TODO] Verify the user that has a subscription works
+</shell>
+
h4. +routes+
+rake routes+ will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with.
diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile
index 68426221bf..66e453c3ff 100644
--- a/guides/source/configuring.textile
+++ b/guides/source/configuring.textile
@@ -70,7 +70,7 @@ NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is
* +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 one of the symbols +:memory_store+, +:file_store+, +:mem_cache_store+, or an object that implements the cache API. Defaults to +:file_store+ if the directory +tmp/cache+ exists, and to +:memory_store+ otherwise.
+* +config.cache_store+ configures which cache store to use for Rails caching. Options include one of the symbols +:memory_store+, +:file_store+, +:mem_cache_store+, +:null_store+, or an object that implements the cache API. Defaults to +:file_store+ if the directory +tmp/cache+ exists, and to +:memory_store+ otherwise.
* +config.colorize_logging+ specifies whether or not to use ANSI color codes when logging information. Defaults to true.
diff --git a/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile
index fbb3483dae..df475a2359 100644
--- a/guides/source/contributing_to_ruby_on_rails.textile
+++ b/guides/source/contributing_to_ruby_on_rails.textile
@@ -105,6 +105,13 @@ $ cd railties
$ TEST_DIR=generators bundle exec rake test
</shell>
+You can run any single test separately too:
+
+<shell>
+$ cd actionpack
+$ ruby -Itest test/template/form_helper_test.rb
+</shell>
+
h4. Warnings
The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings.
@@ -201,6 +208,12 @@ $ bundle exec rake test
will now run the four of them in turn.
+You can also run any single test separately:
+
+<shell>
+$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb
+</shell>
+
You can invoke +test_jdbcmysql+, +test_jdbcsqlite3+ or +test_jdbcpostgresql+ also. See the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/travis.rb+ for the test suite run by the continuous integration server.
h4. Older Versions of Ruby on Rails
diff --git a/guides/source/getting_started.textile b/guides/source/getting_started.textile
index b48ebbceb2..44f3b978db 100644
--- a/guides/source/getting_started.textile
+++ b/guides/source/getting_started.textile
@@ -72,7 +72,8 @@ step needed to make this example application has been left out, so you can
literally follow along step by step. You can get the complete code
"here":https://github.com/lifo/docrails/tree/master/guides/code/getting_started.
-By following along with this guide, you'll create a Rails project called <tt>blog</tt>, a
+By following along with this guide, you'll create a Rails project called
++blog+, a
(very) simple weblog. Before you can start building the application, you need to
make sure that you have Rails itself installed.
@@ -108,7 +109,8 @@ $ rails new blog
This will create a Rails application called Blog in a directory called blog.
-TIP: You can see all of the command line options that the Rails application builder accepts by running <tt>rails new -h</tt>.
+TIP: You can see all of the command line options that the Rails
+application builder accepts by running +rails new -h+.
After you create the blog application, switch to its folder to continue work directly in that application:
@@ -116,7 +118,10 @@ After you create the blog application, switch to its folder to continue work dir
$ cd blog
</shell>
-The +rails new blog+ command we ran above created a folder in your working directory called <tt>blog</tt>. The <tt>blog</tt> directory has a number of auto-generated files and folders that make up the structure of a Rails application. Most of the work in this tutorial will happen in the <tt>app/</tt> folder, but here's a basic rundown on the function of each of the files and folders that Rails created by default:
+The +rails new blog+ command we ran above created a folder in your
+working directory called +blog+. The +blog+ directory has a number of
+auto-generated files and folders that make up the structure of a Rails
+application. Most of the work in this tutorial will happen in the +app/+ folder, but here's a basic rundown on the function of each of the files and folders that Rails created by default:
|_.File/Folder|_.Purpose|
|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
@@ -385,11 +390,10 @@ This action is now displaying the parameters for the post that are coming in fro
h4. Creating the Post model
-Rails uses models to manage database objects, so if you want to save
-data to the database you'll have to create a model. In our blog
-application you want to save posts, so you'll create a +Post+ model.
-
-You can create a model with the following command:
+Models in Rails use a singular name, and their corresponding database tables use
+a plural name. Rails provides a generator for creating models, which
+most Rails developers tend to use when creating new models.
+To create the new model, run this command in your terminal:
<shell>
$ rails generate model Post title:string text:text
@@ -457,7 +461,7 @@ NOTE. Because you're working in the development environment by default, this
command will apply to the database defined in the +development+ section of your
+config/database.yml+ file. If you would like to execute migrations in another
environment, for instance in production, you must explicitly pass it when
-invoking the command: <tt>rake db:migrate RAILS_ENV=production</tt>.
+invoking the command: +rake db:migrate RAILS_ENV=production+.
h4. Saving data in the controller
@@ -1095,424 +1099,65 @@ posts. In the next section will see how Rails can aid us when creating
REST applications, and how we can refactor our Blog app to take
advantage of it.
-h4. Using the Console
-
-To see your validations in action, you can use the console. The console is a
-command-line tool that lets you execute Ruby code in the context of your
-application:
-
-<shell>
-$ rails console
-</shell>
-
-TIP: The default console will make changes to your database. You can instead
-open a console that will roll back any changes you make by using <tt>rails console
---sandbox</tt>.
-
-After the console loads, you can use it to work with your application's models:
-
-<shell>
->> p = Post.new(:content => "A new post")
-=> #<Post id: nil, name: nil, title: nil,
- content: "A new post", created_at: nil,
- updated_at: nil>
->> p.save
-=> false
->> p.errors.full_messages
-=> ["Name can't be blank", "Title can't be blank", "Title is too short (minimum is 5 characters)"]
-</shell>
-
-This code shows creating a new +Post+ instance, attempting to save it and
-getting +false+ for a return value (indicating that the save failed), and
-inspecting the +errors+ of the post.
-
-When you're finished, type +exit+ and hit +return+ to exit the console.
-
-TIP: Unlike the development web server, the console does not automatically load
-your code afresh for each line. If you make changes to your models (in your editor)
-while the console is open, type +reload!+ at the console prompt to load them.
+h4. Going Deeper into REST
-h4. Listing All Posts
-
-Let's dive into the Rails code a little deeper to see how the application is
-showing us the list of Posts. Open the file
-+app/controllers/posts_controller.rb+ and look at the
-+index+ action:
+We've now covered all the CRUD actions of a REST app. We did so by
+declaring separate routes with the appropriate verbs into
++config/routes.rb+. Here's how that file looks so far:
<ruby>
-def index
- @posts = Post.all
-
- respond_to do |format|
- format.html # index.html.erb
- format.json { render :json => @posts }
- end
-end
-</ruby>
-
-+Post.all+ returns all of the posts currently in the database as an array
-of +Post+ records that we store in an instance variable called +@posts+.
-
-TIP: For more information on finding records with Active Record, see "Active
-Record Query Interface":active_record_querying.html.
-
-The +respond_to+ block handles both HTML and JSON calls to this action. If you
-browse to "http://localhost:3000/posts.json":http://localhost:3000/posts.json,
-you'll see a JSON containing all of the posts. The HTML format looks for a view
-in +app/views/posts/+ with a name that corresponds to the action name. Rails
-makes all of the instance variables from the action available to the view.
-Here's +app/views/posts/index.html.erb+:
-
-<erb>
-<h1>Listing posts</h1>
-
-<table>
- <tr>
- <th>Name</th>
- <th>Title</th>
- <th>Content</th>
- <th></th>
- <th></th>
- <th></th>
- </tr>
-
-<% @posts.each do |post| %>
- <tr>
- <td><%= post.name %></td>
- <td><%= post.title %></td>
- <td><%= post.content %></td>
- <td><%= link_to 'Show', post %></td>
- <td><%= link_to 'Edit', edit_post_path(post) %></td>
- <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?',
- :method => :delete %></td>
- </tr>
-<% end %>
-</table>
-
-<br />
-
-<%= link_to 'New post', new_post_path %>
-</erb>
-
-This view iterates over the contents of the +@posts+ array to display content
-and links. A few things to note in the view:
-
-* +link_to+ builds a hyperlink to a particular destination
-* +edit_post_path+ and +new_post_path+ are helpers that Rails provides as part of RESTful routing. You'll see a variety of these helpers for the different actions that the controller includes.
-
-NOTE. In previous versions of Rails, you had to use +&lt;%=h post.name %&gt;+ so
-that any HTML would be escaped before being inserted into the page. In Rails
-3 and above, this is now the default. To get unescaped HTML, you now use <tt>&lt;%= raw post.name %&gt;</tt>.
-
-TIP: For more details on the rendering process, see "Layouts and Rendering in
-Rails":layouts_and_rendering.html.
-
-h4. Customizing the Layout
-
-The view is only part of the story of how HTML is displayed in your web browser.
-Rails also has the concept of +layouts+, which are containers for views. When
-Rails renders a view to the browser, it does so by putting the view's HTML into
-a layout's HTML. In previous versions of Rails, the +rails generate scaffold+
-command would automatically create a controller specific layout, like
-+app/views/layouts/posts.html.erb+, for the posts controller. However this has
-been changed in Rails 3. An application specific +layout+ is used for all the
-controllers and can be found in +app/views/layouts/application.html.erb+. Open
-this layout in your editor and modify the +body+ tag to include the style directive
-below:
-
-<erb>
-<!DOCTYPE html>
-<html>
-<head>
- <title>Blog</title>
- <%= stylesheet_link_tag "application" %>
- <%= javascript_include_tag "application" %>
- <%= csrf_meta_tags %>
-</head>
-<body style="background-color: #EEEEEE;">
-
-<%= yield %>
-
-</body>
-</html>
-</erb>
-
-Now when you refresh the +/posts+ page, you'll see a gray background to the
-page. This same gray background will be used throughout all the views.
-
-h4. Creating New Posts
-
-Creating a new post involves two actions. The first is the +new+ action, which
-instantiates an empty +Post+ object:
-
-<ruby>
-def new
- @post = Post.new
-
- respond_to do |format|
- format.html # new.html.erb
- format.json { render :json => @post }
- end
-end
-</ruby>
-
-The +new.html.erb+ view displays this empty Post to the user:
-
-<erb>
-<h1>New post</h1>
-
-<%= render 'form' %>
-
-<%= link_to 'Back', posts_path %>
-</erb>
-
-The +&lt;%= render 'form' %&gt;+ line is our first introduction to _partials_ in
-Rails. A partial is a snippet of HTML and Ruby code that can be reused in
-multiple locations. In this case, the form used to make a new post is basically
-identical to the form used to edit a post, both having text fields for the name and
-title, a text area for the content, and a button to create the new post or to update
-the existing one.
-
-If you take a look at +views/posts/_form.html.erb+ file, you will see the
-following:
-
-<erb>
-<%= form_for(@post) do |f| %>
- <% if @post.errors.any? %>
- <div id="errorExplanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited
- this post from being saved:</h2>
- <ul>
- <% @post.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
-
- <div class="field">
- <%= f.label :name %><br />
- <%= f.text_field :name %>
- </div>
- <div class="field">
- <%= f.label :title %><br />
- <%= f.text_field :title %>
- </div>
- <div class="field">
- <%= f.label :content %><br />
- <%= f.text_area :content %>
- </div>
- <div class="actions">
- <%= f.submit %>
- </div>
-<% end %>
-</erb>
-
-This partial receives all the instance variables defined in the calling view
-file. In this case, the controller assigned the new +Post+ object to +@post+,
-which will thus be available in both the view and the partial as +@post+.
-
-For more information on partials, refer to the "Layouts and Rendering in
-Rails":layouts_and_rendering.html#using-partials guide.
-
-The +form_for+ block is used to create an HTML form. Within this block, you have
-access to methods to build various controls on the form. For example,
-+f.text_field :name+ tells Rails to create a text input on the form and to hook
-it up to the +name+ attribute of the instance being displayed. You can only use
-these methods with attributes of the model that the form is based on (in this
-case +name+, +title+, and +content+). Rails uses +form_for+ in preference to
-having you write raw HTML because the code is more succinct, and because it
-explicitly ties the form to a particular model instance.
-
-The +form_for+ block is also smart enough to work out if you are doing a _New
-Post_ or an _Edit Post_ action, and will set the form +action+ tags and submit
-button names appropriately in the HTML output.
-
-TIP: If you need to create an HTML form that displays arbitrary fields, not tied
-to a model, you should use the +form_tag+ method, which provides shortcuts for
-building forms that are not necessarily tied to a model instance.
-
-When the user clicks the +Create Post+ button on this form, the browser will
-send information back to the +create+ action of the controller (Rails knows to
-call the +create+ action because the form is sent with an HTTP POST request;
-that's one of the conventions that were mentioned earlier):
-
-<ruby>
-def create
- @post = Post.new(params[:post])
-
- respond_to do |format|
- if @post.save
- format.html { redirect_to(@post,
- :notice => 'Post was successfully created.') }
- format.json { render :json => @post,
- :status => :created, :location => @post }
- else
- format.html { render :action => "new" }
- format.json { render :json => @post.errors,
- :status => :unprocessable_entity }
- end
- end
-end
-</ruby>
-
-The +create+ action instantiates a new Post object from the data supplied by the
-user on the form, which Rails makes available in the +params+ hash. After
-successfully saving the new post, +create+ returns the appropriate format that
-the user has requested (HTML in our case). It then redirects the user to the
-resulting post +show+ action and sets a notice to the user that the Post was
-successfully created.
-
-If the post was not successfully saved, due to a validation error, then the
-controller returns the user back to the +new+ action with any error messages so
-that the user has the chance to fix the error and try again.
-
-The "Post was successfully created." message is stored in the Rails
-+flash+ hash (usually just called _the flash_), so that messages can be carried
-over to another action, providing the user with useful information on the status
-of their request. In the case of +create+, the user never actually sees any page
-rendered during the post creation process, because it immediately redirects to
-the new +Post+ as soon as Rails saves the record. The Flash carries over a message to
-the next action, so that when the user is redirected back to the +show+ action,
-they are presented with a message saying "Post was successfully created."
-
-h4. Showing an Individual Post
-
-When you click the +show+ link for a post on the index page, it will bring you
-to a URL like +http://localhost:3000/posts/1+. Rails interprets this as a call
-to the +show+ action for the resource, and passes in +1+ as the +:id+ parameter.
-Here's the +show+ action:
-
-<ruby>
-def show
- @post = Post.find(params[:id])
-
- respond_to do |format|
- format.html # show.html.erb
- format.json { render :json => @post }
- end
-end
+get "posts" => "posts#index"
+get "posts/new"
+post "posts/create"
+get "posts/:id" => "posts#show", :as => :post
+get "posts/:id/edit" => "posts#edit"
+put "posts/:id" => "posts#update"
+delete "posts/:id" => "posts#destroy"
</ruby>
-The +show+ action uses +Post.find+ to search for a single record in the database
-by its id value. After finding the record, Rails displays it by using
-+app/views/posts/show.html.erb+:
-
-<erb>
-<p id="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
-
-<%= link_to 'Edit', edit_post_path(@post) %> |
-<%= link_to 'Back', posts_path %>
-</erb>
-
-h4. Editing Posts
-
-Like creating a new post, editing a post is a two-part process. The first step
-is a request to +edit_post_path(@post)+ with a particular post. This calls the
-+edit+ action in the controller:
+That's a lot to type for covering a single *resource*. Fortunately,
+Rails provides a +resources+ method which can be used to declare a
+standard REST resource. Here's how +config/routes/rb+ looks after the
+cleanup:
<ruby>
-def edit
- @post = Post.find(params[:id])
-end
-</ruby>
-
-After finding the requested post, Rails uses the +edit.html.erb+ view to display
-it:
-
-<erb>
-<h1>Editing post</h1>
-
-<%= render 'form' %>
-
-<%= link_to 'Show', @post %> |
-<%= link_to 'Back', posts_path %>
-</erb>
-
-Again, as with the +new+ action, the +edit+ action is using the +form+ partial.
-This time, however, the form will do a PUT action to the +PostsController+ and the
-submit button will display "Update Post".
-
-Submitting the form created by this view will invoke the +update+ action within
-the controller:
+Blog::Application.routes.draw do
-<ruby>
-def update
- @post = Post.find(params[:id])
+ resources :posts
- respond_to do |format|
- if @post.update_attributes(params[:post])
- format.html { redirect_to(@post,
- :notice => 'Post was successfully updated.') }
- format.json { head :no_content }
- else
- format.html { render :action => "edit" }
- format.json { render :json => @post.errors,
- :status => :unprocessable_entity }
- end
- end
+ root :to => "welcome#index"
end
</ruby>
-In the +update+ action, Rails first uses the +:id+ parameter passed back from
-the edit view to locate the database record that's being edited. The
-+update_attributes+ call then takes the +post+ parameter (a hash) from the request
-and applies it to this record. If all goes well, the user is redirected to the
-post's +show+ action. If there are any problems, it redirects back to the +edit+ action to
-correct them.
+If you run +rake routes+, you'll see that all the routes that we
+declared before are still available, and the app still works as before.
-h4. Destroying a Post
-
-Finally, clicking one of the +destroy+ links sends the associated id to the
-+destroy+ action:
-
-<ruby>
-def destroy
- @post = Post.find(params[:id])
- @post.destroy
-
- respond_to do |format|
- format.html { redirect_to posts_url }
- format.json { head :no_content }
- end
-end
-</ruby>
+<shell>
+# rake routes
+ posts GET /posts(.:format) posts#index
+ POST /posts(.:format) posts#create
+ new_post GET /posts/new(.:format) posts#new
+edit_post GET /posts/:id/edit(.:format) posts#edit
+ post GET /posts/:id(.:format) posts#show
+ PUT /posts/:id(.:format) posts#update
+ DELETE /posts/:id(.:format) posts#destroy
+ root / welcome#index
+</shell>
-The +destroy+ method of an Active Record model instance removes the
-corresponding record from the database. After that's done, there isn't any
-record to display, so Rails redirects the user's browser to the index action of
-the controller.
+TIP: In general, Rails encourages the use of resources objects in place
+of declaring routes manually. For more information about routing, see
+"Rails Routing from the Outside In":routing.html.
h3. Adding a Second Model
-Now that you've seen what a model built with scaffolding looks like, it's time to
-add a second model to the application. The second model will handle comments on
+It's time to add a second model to the application. The second model will handle comments on
blog posts.
h4. Generating a Model
-Models in Rails use a singular name, and their corresponding database tables use
-a plural name. For the model to hold comments, the convention is to use the name
-+Comment+. Even if you don't want to use the entire apparatus set up by
-scaffolding, most Rails developers still use generators to make things like
-models and controllers. To create the new model, run this command in your
-terminal:
+We're going to se the same generator that we used before when creating
+the +Post+ model. This time we'll create a +Comment+ model to hold
+reference of post comments. Run this command in your terminal:
<shell>
$ rails generate model Comment commenter:string body:text post:references
@@ -1600,7 +1245,6 @@ You'll need to edit the +post.rb+ file to add the other side of the association:
<ruby>
class Post < ActiveRecord::Base
- validates :name, :presence => true
validates :title, :presence => true,
:length => { :minimum => 5 }
@@ -1619,9 +1263,7 @@ h4. Adding a Route for Comments
As with the +welcome+ controller, we will need to add a route so that Rails knows
where we would like to navigate to see +comments+. Open up the
-+config/routes.rb+ file again. Near the top, you will see the entry for +posts+
-that was added automatically by the scaffold generator: <tt>resources
-:posts</tt>. Edit it as follows:
++config/routes.rb+ file again, and edit it as follows:
<ruby>
resources :posts do
@@ -1639,7 +1281,7 @@ In":routing.html guide.
h4. Generating a Controller
With the model in hand, you can turn your attention to creating a matching
-controller. Again, there's a generator for this:
+controller. Again, we'll use the same generator we used before:
<shell>
$ rails generate controller Comments
@@ -1666,40 +1308,33 @@ So first, we'll wire up the Post show template
(+/app/views/posts/show.html.erb+) to let us make a new comment:
<erb>
-<p id="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
<p>
- <b>Title:</b>
+ <strong>Title:</strong>
<%= @post.title %>
</p>
<p>
- <b>Content:</b>
- <%= @post.content %>
+ <strong>Text:</strong>
+ <%= @post.texthttp://beginningruby.org/ %>
</p>
<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
- <div class="field">
+ <p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
- </div>
- <div class="field">
+ </p>
+ <p>
<%= f.label :body %><br />
<%= f.text_area :body %>
- </div>
- <div class="actions">
+ </p>
+ <p>
<%= f.submit %>
- </div>
+ </p>
<% end %>
<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
+<%= link_to 'Back to Posts', posts_path %>
</erb>
This adds a form on the +Post+ show page that creates a new comment by
@@ -2074,6 +1709,7 @@ class Post < ActiveRecord::Base
has_many :comments, :dependent => :destroy
has_many :tags
+ attr_protected :tags
accepts_nested_attributes_for :tags, :allow_destroy => :true,
:reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
diff --git a/guides/source/routing.textile b/guides/source/routing.textile
index 836e0cdd70..4a50edbb15 100644
--- a/guides/source/routing.textile
+++ b/guides/source/routing.textile
@@ -837,7 +837,7 @@ If you have a large route file that you would like to break up into multiple fil
draw :admin
</ruby>
-Then, create a file called +config/routes/admin.rb+. Name the file the same as the symbol passed to the +draw+ method). You can then use the normal routing DSL inside that file:
+Then, create a file called +config/routes/admin.rb+. Name the file the same as the symbol passed to the +draw+ method. You can then use the normal routing DSL inside that file:
<ruby>
# in config/routes/admin.rb
diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile
index e63548abc9..2b2e65c813 100644
--- a/guides/source/upgrading_ruby_on_rails.textile
+++ b/guides/source/upgrading_ruby_on_rails.textile
@@ -38,6 +38,10 @@ h4(#identity_map4_0). IdentityMap
Rails 4.0 has removed <tt>IdentityMap</tt> from <tt>ActiveRecord</tt>, due to "some inconsistencies with associations":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: <tt>config.active_record.identity_map</tt>.
+h4(#active_model4_0). ActiveModel
+
+Rails 4.0 has changed how errors attach with the ConfirmationValidator. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>.
+
h3. Upgrading from Rails 3.1 to Rails 3.2
If your application is currently on any version of Rails older than 3.1.x, you should upgrade to Rails 3.1 before attempting an update to Rails 3.2.
diff --git a/railties/Rakefile b/railties/Rakefile
index c4a91a1d36..108413235a 100755..100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rubygems/package_task'
@@ -60,12 +59,3 @@ task :release => :package do
Rake::Gemcutter::Tasks.new(spec).define
Rake::Task['gem:push'].invoke
end
-
-desc "Publish the guides"
-task :pguides => :generate_guides do
- require 'rake/contrib/sshpublisher'
- mkdir_p 'pkg'
- `tar -czf pkg/guides.gz guides/output`
- Rake::SshFilePublisher.new("web.rubyonrails.org", "/u/sites/guides.rubyonrails.org/public", "pkg", "guides.gz").upload
- `ssh web.rubyonrails.org 'cd /u/sites/guides.rubyonrails.org/public/ && tar -xvzf guides.gz && mv guides/output/* . && rm -rf guides*'`
-end
diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb
index 539f68a3be..8cc8eb1103 100644
--- a/railties/lib/rails/backtrace_cleaner.rb
+++ b/railties/lib/rails/backtrace_cleaner.rb
@@ -17,8 +17,6 @@ module Rails
private
def add_gem_filters
- return unless defined?(Gem)
-
gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
return if gems_paths.empty?
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index 6fc127efae..25a8caeec3 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -5,12 +5,15 @@ require 'rbconfig'
module Rails
class DBConsole
+ attr_reader :arguments
+
def self.start(app)
new(app).start
end
- def initialize(app)
+ def initialize(app, arguments = ARGV)
@app = app
+ @arguments = arguments
end
def start
@@ -31,8 +34,8 @@ module Rails
options['header'] = h
end
- opt.parse!(ARGV)
- abort opt.to_s unless (0..1).include?(ARGV.size)
+ opt.parse!(arguments)
+ abort opt.to_s unless (0..1).include?(arguments.size)
end
unless config = @app.config.database_configuration[Rails.env]
@@ -40,20 +43,6 @@ module Rails
end
- def find_cmd(*commands)
- dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
- commands += commands.map{|cmd| "#{cmd}.exe"} if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
-
- full_path_command = nil
- found = commands.detect do |cmd|
- dir = dirs_on_path.detect do |path|
- full_path_command = File.join(path, cmd)
- File.executable? full_path_command
- end
- end
- found ? full_path_command : abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
- end
-
case config["adapter"]
when /^mysql/
args = {
@@ -72,17 +61,17 @@ module Rails
args << config['database']
- exec(find_cmd('mysql', 'mysql5'), *args)
+ find_cmd_and_exec(['mysql', 'mysql5'], *args)
when "postgresql", "postgres"
ENV['PGUSER'] = config["username"] if config["username"]
ENV['PGHOST'] = config["host"] if config["host"]
ENV['PGPORT'] = config["port"].to_s if config["port"]
ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && include_password
- exec(find_cmd('psql'), config["database"])
+ find_cmd_and_exec('psql', config["database"])
when "sqlite"
- exec(find_cmd('sqlite'), config["database"])
+ find_cmd_and_exec('sqlite', config["database"])
when "sqlite3"
args = []
@@ -91,7 +80,7 @@ module Rails
args << "-header" if options['header']
args << config['database']
- exec(find_cmd('sqlite3'), *args)
+ find_cmd_and_exec('sqlite3', *args)
when "oracle", "oracle_enhanced"
logon = ""
@@ -102,12 +91,35 @@ module Rails
logon << "@#{config['database']}" if config['database']
end
- exec(find_cmd('sqlplus'), logon)
+ find_cmd_and_exec('sqlplus', logon)
else
abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!"
end
end
+
+ protected
+
+ def find_cmd_and_exec(commands, *args)
+ commands = Array(commands)
+
+ dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
+ commands += commands.map{|cmd| "#{cmd}.exe"} if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+
+ full_path_command = nil
+ found = commands.detect do |cmd|
+ dir = dirs_on_path.detect do |path|
+ full_path_command = File.join(path, cmd)
+ File.executable? full_path_command
+ end
+ end
+
+ if found
+ exec full_path_command, *args
+ else
+ abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
+ end
+ end
end
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index d4f632702c..9bf9cbe022 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -332,7 +332,7 @@ module Rails
#
# == Loading priority
#
- # In order to change engine's priority you can use config.railties_order in main application.
+ # In order to change engine's priority you can use +config.railties_order+ in main application.
# It will affect the priority of loading views, helpers, assets and all the other files
# related to engine or application.
#
@@ -608,7 +608,12 @@ module Rails
desc "Copy migrations from #{railtie_name} to application"
task :migrations do
ENV["FROM"] = railtie_name
- Rake::Task["railties:install:migrations"].invoke
+ if Rake::Task.task_defined?("railties:install:migrations")
+ Rake::Task["railties:install:migrations"].invoke
+ else
+ Rake::Task["app:railties:install:migrations"].invoke
+ end
+
end
end
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index b677e974c8..6df33d65e9 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -137,24 +137,24 @@ module Rails
def rails_gemfile_entry
if options.dev?
<<-GEMFILE.strip_heredoc
- gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}'
- gem 'journey', :git => 'https://github.com/rails/journey.git'
- gem 'arel', :git => 'https://github.com/rails/arel.git'
- gem 'active_record_deprecated_finders', :git => 'git://github.com/rails/active_record_deprecated_finders.git'
+ 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'
GEMFILE
elsif options.edge?
<<-GEMFILE.strip_heredoc
- gem 'rails', :git => 'https://github.com/rails/rails.git'
- gem 'journey', :git => 'https://github.com/rails/journey.git'
- gem 'arel', :git => 'https://github.com/rails/arel.git'
- gem 'active_record_deprecated_finders', :git => 'git://github.com/rails/active_record_deprecated_finders.git'
+ 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'
GEMFILE
else
<<-GEMFILE.strip_heredoc
gem 'rails', '#{Rails::VERSION::STRING}'
# Bundle edge Rails instead:
- # gem 'rails', :git => 'https://github.com/rails/rails.git'
+ # gem 'rails', github: 'rails/rails'
GEMFILE
end
end
@@ -194,9 +194,9 @@ module Rails
# Gems used only for assets and not required
# in production environments by default.
group :assets do
- gem 'sprockets-rails', :git => 'https://github.com/rails/sprockets-rails.git'
- gem 'sass-rails', :git => 'https://github.com/rails/sass-rails.git'
- gem 'coffee-rails', :git => 'https://github.com/rails/coffee-rails.git'
+ gem 'sprockets-rails', github: 'rails/sprockets-rails'
+ gem 'sass-rails', github: 'rails/sass-rails'
+ gem 'coffee-rails', github: 'rails/coffee-rails'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
#{javascript_runtime_gemfile_entry}
@@ -208,7 +208,7 @@ module Rails
# Gems used only for assets and not required
# in production environments by default.
group :assets do
- gem 'sprockets-rails', :git => 'https://github.com/rails/sprockets-rails.git'
+ gem 'sprockets-rails', github: 'rails/sprockets-rails'
gem 'sass-rails', '~> 4.0.0.beta'
gem 'coffee-rails', '~> 4.0.0.beta'
@@ -230,7 +230,7 @@ module Rails
if defined?(JRUBY_VERSION)
"gem 'therubyrhino'\n"
else
- "# gem 'therubyracer', :platform => :ruby\n"
+ "# gem 'therubyracer', platform: :ruby\n"
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile
index 4dc1023f1f..6eb23f68a3 100755..100644
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
index 8588e88077..82ffeebb86 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.summary = "TODO: Summary of <%= camelized %>."
s.description = "TODO: Description of <%= camelized %>."
- s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
+ s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"]
<% unless options.skip_test_unit? -%>
s.test_files = Dir["test/**/*"]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
index b7bc69d2e5..743036362e 100755..100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
begin
require 'bundler/setup'
rescue LoadError
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index dc5de4084e..2102f8a03c 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -22,7 +22,7 @@ module Rails
#
# * creating initializers
# * configuring a Rails framework for the application, like setting a generator
- # * adding config.* keys to the environment
+ # * +adding config.*+ keys to the environment
# * setting up a subscriber with ActiveSupport::Notifications
# * adding rake tasks
#
diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb
index cf9e4ad500..1c6b3769a5 100644
--- a/railties/lib/rails/railtie/configuration.rb
+++ b/railties/lib/rails/railtie/configuration.rb
@@ -43,7 +43,7 @@ module Rails
ActiveSupport.on_load(:before_configuration, :yield => true, &block)
end
- # Third configurable block to run. Does not run if config.cache_classes
+ # Third configurable block to run. Does not run if +config.cache_classes+
# set to false.
def before_eager_load(&block)
ActiveSupport.on_load(:before_eager_load, :yield => true, &block)
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index a443c73962..31e34023c0 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -14,6 +14,9 @@
# of the line (or closing ERB comment tag) is considered to be their text.
class SourceAnnotationExtractor
class Annotation < Struct.new(:line, :tag, :text)
+ def self.directories
+ @@directories ||= %w(app config lib script test) + (ENV['SOURCE_ANNOTATION_DIRECTORIES'] || '').split(',')
+ end
# Returns a representation of the annotation that looks like this:
#
@@ -48,7 +51,7 @@ class SourceAnnotationExtractor
# Returns a hash that maps filenames under +dirs+ (recursively) to arrays
# with their annotations.
- def find(dirs=%w(app config lib script test))
+ def find(dirs = Annotation.directories)
dirs.inject({}) { |h, dir| h.update(find_in(dir)) }
end
diff --git a/railties/test/application/queue_test.rb b/railties/test/application/queue_test.rb
index ec809d7cc1..71b0c2bc38 100644
--- a/railties/test/application/queue_test.rb
+++ b/railties/test/application/queue_test.rb
@@ -32,7 +32,6 @@ module ApplicationTests
test "in development mode, an enqueued job will be processed in a separate thread" do
app("development")
- current = Thread.current
job = Struct.new(:origin, :target).new(Thread.current)
def job.run
@@ -48,7 +47,6 @@ module ApplicationTests
test "in test mode, explicitly draining the queue will process it in a separate thread" do
app("test")
- current = Thread.current
job = Struct.new(:origin, :target).new(Thread.current)
def job.run
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index d069703d30..05d73dfc5c 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -12,7 +12,7 @@ module ApplicationTests
teardown_app
end
- test 'notes' do
+ test 'notes finds notes for certain file_types' do
app_file "app/views/home/index.html.erb", "<% # TODO: note in erb %>"
app_file "app/views/home/index.html.haml", "-# TODO: note in haml"
app_file "app/views/home/index.html.slim", "/ TODO: note in slim"
@@ -49,7 +49,79 @@ module ApplicationTests
assert_equal ' ', line[1]
end
end
+ end
+
+ test 'notes finds notes in default directories' do
+ app_file "app/controllers/some_controller.rb", "# TODO: note in app directory"
+ app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory"
+ app_file "lib/some_file.rb", "# TODO: note in lib directory"
+ app_file "script/run_something.rb", "# TODO: note in script directory"
+ app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory"
+
+ app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
+
+ boot_rails
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+
+ Rails.application.load_tasks
+
+ Dir.chdir(app_path) do
+ output = `bundle exec rake notes`
+ lines = output.scan(/\[([0-9\s]+)\]/).flatten
+
+ assert_match /note in app directory/, output
+ assert_match /note in config directory/, output
+ assert_match /note in lib directory/, output
+ assert_match /note in script directory/, output
+ assert_match /note in test directory/, output
+ assert_no_match /note in some_other directory/, output
+
+ assert_equal 5, lines.size
+
+ lines.each do |line_number|
+ assert_equal 4, line_number.size
+ end
+ end
+ end
+
+ test 'notes finds notes in custom directories' do
+ app_file "app/controllers/some_controller.rb", "# TODO: note in app directory"
+ app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory"
+ app_file "lib/some_file.rb", "# TODO: note in lib directory"
+ app_file "script/run_something.rb", "# TODO: note in script directory"
+ app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory"
+
+ app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
+
+ boot_rails
+
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+
+ Rails.application.load_tasks
+
+ Dir.chdir(app_path) do
+ output = `SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bundle exec rake notes`
+ lines = output.scan(/\[([0-9\s]+)\]/).flatten
+
+ assert_match /note in app directory/, output
+ assert_match /note in config directory/, output
+ assert_match /note in lib directory/, output
+ assert_match /note in script directory/, output
+ assert_match /note in test directory/, output
+
+ assert_match /note in some_other directory/, output
+
+ assert_equal 6, lines.size
+
+ lines.each do |line_number|
+ assert_equal 4, line_number.size
+ end
+ end
end
private
diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb
index cbe7d35f6d..2dd74f8fd1 100644
--- a/railties/test/backtrace_cleaner_test.rb
+++ b/railties/test/backtrace_cleaner_test.rb
@@ -1,26 +1,24 @@
require 'abstract_unit'
require 'rails/backtrace_cleaner'
-if defined? Gem
- class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase
- def setup
- @cleaner = Rails::BacktraceCleaner.new
- end
+class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase
+ def setup
+ @cleaner = Rails::BacktraceCleaner.new
+ end
+
+ test "should format installed gems correctly" do
+ @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
+ @result = @cleaner.clean(@backtrace, :all)
+ assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
+ end
- test "should format installed gems correctly" do
- @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
+ test "should format installed gems not in Gem.default_dir correctly" do
+ @target_dir = Gem.path.detect { |p| p != Gem.default_dir }
+ # skip this test if default_dir is the only directory on Gem.path
+ if @target_dir
+ @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
@result = @cleaner.clean(@backtrace, :all)
assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
end
-
- test "should format installed gems not in Gem.default_dir correctly" do
- @target_dir = Gem.path.detect { |p| p != Gem.default_dir }
- # skip this test if default_dir is the only directory on Gem.path
- if @target_dir
- @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
- @result = @cleaner.clean(@backtrace, :all)
- assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
- end
- end
end
end
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
new file mode 100644
index 0000000000..0bf417c014
--- /dev/null
+++ b/railties/test/commands/dbconsole_test.rb
@@ -0,0 +1,128 @@
+require 'abstract_unit'
+require 'rails/commands/dbconsole'
+
+class Rails::DBConsoleTest < ActiveSupport::TestCase
+ def teardown
+ %w[PGUSER PGHOST PGPORT PGPASSWORD'].each{|key| ENV.delete(key)}
+ end
+
+ def test_no_database_configured
+ start [], false
+ assert aborted
+ assert_match /No database is configured for the environment '\w+'/, output
+ end
+
+ def test_mysql
+ dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], 'db')
+ start [], {adapter: 'mysql', database: 'db'}
+ assert !aborted
+ end
+
+ def test_mysql_full
+ dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], '--host=locahost', '--port=1234', '--socket=socket', '--user=user', '--default-character-set=UTF-8', '-p', 'db')
+ start [], {adapter: 'mysql', database: 'db', host: 'locahost', port: 1234, socket: 'socket', username: 'user', password: 'qwerty', encoding: 'UTF-8'}
+ assert !aborted
+ end
+
+ def test_mysql_include_password
+ dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], '--user=user', '--password=qwerty', 'db')
+ start ['-p'], {adapter: 'mysql', database: 'db', username: 'user', password: 'qwerty'}
+ assert !aborted
+ end
+
+ def test_postgresql
+ dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
+ start [], {adapter: 'postgresql', database: 'db'}
+ assert !aborted
+ end
+
+ def test_postgresql_full
+ dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
+ start [], {adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3', host: 'host', port: 5432}
+ assert !aborted
+ assert_equal 'user', ENV['PGUSER']
+ assert_equal 'host', ENV['PGHOST']
+ assert_equal '5432', ENV['PGPORT']
+ assert_not_equal 'q1w2e3', ENV['PGPASSWORD']
+ end
+
+ def test_postgresql_include_password
+ dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
+ start ['-p'], {adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3'}
+ assert !aborted
+ assert_equal 'user', ENV['PGUSER']
+ assert_equal 'q1w2e3', ENV['PGPASSWORD']
+ end
+
+ def test_sqlite
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite', 'db')
+ start [], {adapter: 'sqlite', database: 'db'}
+ assert !aborted
+ end
+
+ def test_sqlite3
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', 'db')
+ start [], {adapter: 'sqlite3', database: 'db'}
+ assert !aborted
+ end
+
+ def test_sqlite3_mode
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-html', 'db')
+ start ['--mode', 'html'], {adapter: 'sqlite3', database: 'db'}
+ assert !aborted
+ end
+
+ def test_sqlite3_header
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-header', 'db')
+ start ['--header'], {adapter: 'sqlite3', database: 'db'}
+ assert !aborted
+ end
+
+ def test_oracle
+ dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user@db')
+ start [], {adapter: 'oracle', database: 'db', username: 'user', password: 'secret'}
+ assert !aborted
+ end
+
+ def test_oracle_include_password
+ dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user/secret@db')
+ start ['-p'], {adapter: 'oracle', database: 'db', username: 'user', password: 'secret'}
+ assert !aborted
+ end
+
+ def test_unknown_command_line_client
+ start [], {adapter: 'unknown', database: 'db'}
+ assert aborted
+ assert_match /Unknown command-line client for db/, output
+ end
+
+ private
+ attr_reader :aborted, :output
+
+ def dbconsole
+ @dbconsole ||= Rails::DBConsole.new(app)
+ end
+
+ def start(argv = [], database_configuration = {})
+ dbconsole.stubs(arguments: argv)
+ app.config.stubs(database_configuration: {
+ Rails.env => database_configuration ? database_configuration.stringify_keys : database_configuration
+ })
+
+ @aborted = false
+ @output = capture(:stderr) do
+ begin
+ dbconsole.start
+ rescue SystemExit
+ @aborted = true
+ end
+ end
+ end
+
+ def app
+ @app ||= begin
+ config = mock("config")
+ stub("app", config: config)
+ end
+ end
+end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 94983c504c..d13dc8d4ac 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -246,7 +246,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
if defined?(JRUBY_VERSION)
assert_file "Gemfile", /gem\s+["']therubyrhino["']$/
else
- assert_file "Gemfile", /# gem\s+["']therubyracer["']+, :platform => :ruby$/
+ assert_file "Gemfile", /# gem\s+["']therubyracer["']+, platform: :ruby$/
end
end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 4bb5f04a79..6c31b80c7d 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -32,7 +32,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
content = capture(:stderr){ run_generator [File.join(destination_root, "things4.3")] }
assert_equal "Invalid plugin name things4.3. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content
-
+
content = capture(:stderr){ run_generator [File.join(destination_root, "43things")] }
assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content
end
@@ -211,7 +211,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_creating_gemspec
run_generator
assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/
- assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*"\]/
+ assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.rdoc"\]/
assert_file "bukkits.gemspec", /s.test_files = Dir\["test\/\*\*\/\*"\]/
assert_file "bukkits.gemspec", /s.version\s+ = Bukkits::VERSION/
end
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 92117855b7..e78e67725d 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -104,13 +104,13 @@ module SharedGeneratorTests
generator([destination_root], :dev => true).expects(:bundle_command).with('install').once
quietly { generator.invoke_all }
rails_path = File.expand_path('../../..', Rails.root)
- assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/
+ assert_file 'Gemfile', /^gem\s+["']rails["'],\s+path:\s+["']#{Regexp.escape(rails_path)}["']$/
end
def test_edge_option
generator([destination_root], :edge => true).expects(:bundle_command).with('install').once
quietly { generator.invoke_all }
- assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("https://github.com/rails/rails.git")}["']$}
+ assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$}
end
def test_skip_gemfile
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 9d9565e5f3..9a6b2b66ca 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -111,6 +111,37 @@ module RailtiesTest
end
end
+ test "mountable engine should copy migrations within engine_path" do
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ class Engine < ::Rails::Engine
+ isolate_namespace Bukkits
+ end
+ end
+ RUBY
+
+ @plugin.write "db/migrate/0_add_first_name_to_users.rb", <<-RUBY
+ class AddFirstNameToUsers < ActiveRecord::Migration
+ end
+ RUBY
+
+ @plugin.write "Rakefile", <<-RUBY
+ APP_RAKEFILE = '#{app_path}/Rakefile'
+ load 'rails/tasks/engine.rake'
+ RUBY
+
+ add_to_config "ActiveRecord::Base.timestamped_migrations = false"
+
+ boot_rails
+
+ Dir.chdir(@plugin.path) do
+ output = `bundle exec rake app:bukkits:install:migrations`
+ assert File.exists?("#{app_path}/db/migrate/0_add_first_name_to_users.bukkits.rb")
+ assert_match(/Copied migration 0_add_first_name_to_users.bukkits.rb from bukkits/, output)
+ assert_equal 1, Dir["#{app_path}/db/migrate/*.rb"].length
+ end
+ end
+
test "no rake task without migrations" do
boot_rails
require 'rake'