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/head.rb22
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb7
-rw-r--r--actionpack/lib/action_controller/test_case.rb27
-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/http/request.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb37
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb4
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb166
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb7
-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/routing/url_for.rb2
-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/form_options_helper.rb35
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb9
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb4
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb41
-rw-r--r--actionpack/test/controller/caching_test.rb33
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb66
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb17
-rw-r--r--actionpack/test/controller/routing_test.rb22
-rw-r--r--actionpack/test/dispatch/header_test.rb5
-rw-r--r--actionpack/test/dispatch/request/session_test.rb48
-rw-r--r--actionpack/test/dispatch/routing_test.rb28
-rw-r--r--actionpack/test/dispatch/session/abstract_store_test.rb56
-rw-r--r--actionpack/test/fixtures/test/hello_world_with_partial.html.erb2
-rw-r--r--actionpack/test/template/render_test.rb8
-rw-r--r--activemodel/README.rdoc2
-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.rb37
-rw-r--r--activemodel/test/cases/observing_test.rb11
-rw-r--r--activerecord/CHANGELOG.md19
-rw-r--r--[-rwxr-xr-x]activerecord/Rakefile1
-rw-r--r--activerecord/lib/active_record.rb2
-rw-r--r--activerecord/lib/active_record/aggregations.rb12
-rw-r--r--activerecord/lib/active_record/associations.rb18
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb8
-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.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb2
-rw-r--r--activerecord/lib/active_record/core.rb5
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb108
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb169
-rw-r--r--activerecord/lib/active_record/dynamic_scope_match.rb30
-rw-r--r--activerecord/lib/active_record/explain.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb97
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb12
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb5
-rw-r--r--activerecord/test/cases/base_test.rb8
-rw-r--r--activerecord/test/cases/column_test.rb24
-rw-r--r--activerecord/test/cases/dynamic_finder_match_test.rb106
-rw-r--r--activerecord/test/cases/finder_test.rb32
-rw-r--r--activerecord/test/cases/migration_test.rb4
-rw-r--r--activerecord/test/cases/named_scope_test.rb5
-rw-r--r--activerecord/test/cases/relations_test.rb4
-rw-r--r--activerecord/test/models/company.rb5
-rw-r--r--activerecord/test/schema/schema.rb1
-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/date_time/calculations.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb15
-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/core_ext/date_time_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb30
-rw-r--r--activesupport/test/json/encoding_test.rb15
-rw-r--r--guides/assets/images/getting_started/post_with_comments.pngbin0 -> 31630 bytes
-rw-r--r--guides/code/getting_started/app/controllers/comments_controller.rb5
-rw-r--r--guides/code/getting_started/app/controllers/posts_controller.rb2
-rw-r--r--guides/code/getting_started/app/views/comments/_comment.html.erb8
-rw-r--r--guides/code/getting_started/app/views/comments/_form.html.erb12
-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.erb10
-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/asset_pipeline.textile2
-rw-r--r--guides/source/caching_with_rails.textile2
-rw-r--r--guides/source/command_line.textile9
-rw-r--r--guides/source/configuring.textile36
-rw-r--r--guides/source/contributing_to_ruby_on_rails.textile13
-rw-r--r--guides/source/getting_started.textile816
-rw-r--r--guides/source/layouts_and_rendering.textile66
-rw-r--r--guides/source/routing.textile2
-rw-r--r--guides/source/security.textile3
-rw-r--r--guides/source/upgrading_ruby_on_rails.textile4
-rw-r--r--railties/CHANGELOG.md4
-rw-r--r--[-rwxr-xr-x]railties/Rakefile10
-rw-r--r--railties/lib/rails/application.rb2
-rw-r--r--railties/lib/rails/application/configuration.rb8
-rw-r--r--railties/lib/rails/application/finisher.rb4
-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/engine/commands.rb4
-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/queueing.rb10
-rw-r--r--railties/lib/rails/railtie.rb2
-rw-r--r--railties/lib/rails/railtie/configuration.rb2
-rw-r--r--railties/test/application/queue_test.rb41
-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/queueing/threaded_consumer_test.rb19
-rw-r--r--railties/test/railties/engine_test.rb31
150 files changed, 1682 insertions, 1552 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/head.rb b/actionpack/lib/action_controller/metal/head.rb
index a618533d09..5bdbde9ebb 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -27,8 +27,28 @@ module ActionController
self.status = status
self.location = url_for(location) if location
- self.content_type = Mime[formats.first] if formats
+
+ if include_content_headers?(self.status)
+ self.content_type = Mime[formats.first] if formats
+ else
+ headers.delete('Content-Type')
+ headers.delete('Content-Length')
+ end
+
self.response_body = " "
end
+
+ private
+ # :nodoc:
+ def include_content_headers?(status)
+ case status
+ when 100..199
+ false
+ when 204, 205, 304
+ false
+ else
+ true
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 17e2db74d4..1f52c164de 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:
#
@@ -166,8 +166,9 @@ module ActionController
unless options[:include] || options[:exclude]
model ||= _default_wrap_model
- if model.respond_to?(:accessible_attributes) && model.accessible_attributes.present?
- options[:include] = model.accessible_attributes.to_a
+ role = options.fetch(:as, :default)
+ if model.respond_to?(:accessible_attributes) && model.accessible_attributes(role).present?
+ options[:include] = model.accessible_attributes(role).to_a
elsif model.respond_to?(:attribute_names) && model.attribute_names.present?
options[:include] = model.attribute_names
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 21997c4d79..ad02375f12 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -20,7 +20,12 @@ module ActionController
ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
path = payload[:layout]
- @layouts[path] += 1
+ if path
+ @layouts[path] += 1
+ if path =~ /^layouts\/(.*)/
+ @layouts[$1] += 1
+ end
+ end
end
ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
@@ -59,6 +64,15 @@ module ActionController
# # assert that the exact template "admin/posts/new" was rendered
# assert_template %r{\Aadmin/posts/new\Z}
#
+ # # assert that the layout 'admin' was rendered
+ # assert_template :layout => 'admin'
+ # assert_template :layout => 'layouts/admin'
+ # assert_template :layout => :admin
+ #
+ # # assert that no layout was rendered
+ # assert_template :layout => nil
+ # assert_template :layout => false
+ #
# # assert that the "_customer" partial was rendered twice
# assert_template :partial => '_customer', :count => 2
#
@@ -90,16 +104,17 @@ module ActionController
end
end
when Hash
- if expected_layout = options[:layout]
+ if options.key?(:layout)
+ expected_layout = options[:layout]
msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
expected_layout, @layouts.keys)
case expected_layout
- when String
- assert_includes @layouts.keys, expected_layout, msg
+ when String, Symbol
+ assert_includes @layouts.keys, expected_layout.to_s, msg
when Regexp
assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg)
- when nil
+ when nil, false
assert(@layouts.empty?, msg)
end
end
@@ -120,7 +135,7 @@ module ActionController
options[:partial], @partials.keys)
assert_includes @partials, expected_partial, msg
end
- else
+ elsif options.key?(:partial)
assert @partials.empty?,
"Expected no partials to be rendered"
end
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/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 796e0dbc45..9748956052 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -6,6 +6,7 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/string/access'
require 'active_support/inflector'
require 'action_dispatch/http/headers'
+require 'action_dispatch/request/session'
require 'action_controller/metal/exceptions'
module ActionDispatch
@@ -220,11 +221,11 @@ module ActionDispatch
end
def session=(session) #:nodoc:
- @env['rack.session'] = session
+ Session.set @env, session
end
def session_options=(options)
- @env['rack.session.options'] = options
+ Session::Options.set @env, options
end
# Override Rack's GET method to support indifferent access
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 196f655de3..de9c55d96c 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/keys'
module ActionDispatch
- class Request
+ class Request < Rack::Request
def cookie_jar
env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
end
@@ -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/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index fd0ce62a3b..17776c2356 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -1,5 +1,5 @@
module ActionDispatch
- class Request
+ class Request < Rack::Request
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one.
@@ -218,7 +218,7 @@ module ActionDispatch
def call(env)
@app.call(env)
ensure
- session = env['rack.session'] || {}
+ session = Request::Session.find(env) || {}
flash_hash = env[KEY]
if flash_hash
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..64159fa8e7 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -2,26 +2,23 @@ require 'rack/utils'
require 'rack/request'
require 'rack/session/abstract/id'
require 'action_dispatch/middleware/cookies'
+require 'action_dispatch/request/session'
require 'active_support/core_ext/object/blank'
module ActionDispatch
module Session
class SessionRestoreError < StandardError #:nodoc:
- end
+ attr_reader :original_exception
+
+ def initialize(const_error)
+ @original_exception = const_error
- module DestroyableSession
- def destroy
- clear
- options = @env[Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY] if @env
- options ||= {}
- @by.send(:destroy_session, @env, options[:id], options) if @by
- options[:id] = nil
- @loaded = false
+ 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
- ::Rack::Session::Abstract::SessionHash.send :include, DestroyableSession
-
module Compatibility
def initialize(app, options = {})
options[:key] ||= '_session_id'
@@ -58,11 +55,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
@@ -71,9 +65,20 @@ module ActionDispatch
end
end
+ module SessionObject # :nodoc:
+ def prepare_session(env)
+ Request::Session.create(self, env, @default_options)
+ end
+
+ def loaded_session?(session)
+ !session.is_a?(Request::Session) || session.loaded?
+ end
+ end
+
class AbstractStore < Rack::Session::Abstract::ID
include Compatibility
include StaleSessionCheck
+ include SessionObject
private
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index a4866f5a8f..dbcf703ec3 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -43,6 +43,7 @@ module ActionDispatch
class CookieStore < Rack::Session::Cookie
include Compatibility
include StaleSessionCheck
+ include SessionObject
private
diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
index 4dd9a946c2..38a737cd2b 100644
--- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
@@ -6,6 +6,7 @@ module ActionDispatch
class MemCacheStore < Rack::Session::Memcache
include Compatibility
include StaleSessionCheck
+ include SessionObject
def initialize(app, options = {})
require 'memcache'
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/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
new file mode 100644
index 0000000000..4ad7071820
--- /dev/null
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -0,0 +1,166 @@
+require 'rack/session/abstract/id'
+
+module ActionDispatch
+ class Request < Rack::Request
+ # SessionHash is responsible to lazily load the session from store.
+ class Session # :nodoc:
+ ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc:
+ ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc:
+
+ def self.create(store, env, default_options)
+ session_was = find env
+ session = Request::Session.new(store, env)
+ session.merge! session_was if session_was
+
+ set(env, session)
+ Options.set(env, Request::Session::Options.new(store, env, default_options))
+ session
+ end
+
+ def self.find(env)
+ env[ENV_SESSION_KEY]
+ end
+
+ def self.set(env, session)
+ env[ENV_SESSION_KEY] = session
+ end
+
+ class Options #:nodoc:
+ def self.set(env, options)
+ env[ENV_SESSION_OPTIONS_KEY] = options
+ end
+
+ def self.find(env)
+ env[ENV_SESSION_OPTIONS_KEY]
+ end
+
+ def initialize(by, env, default_options)
+ @by = by
+ @env = env
+ @delegate = default_options.dup
+ end
+
+ def [](key)
+ if key == :id
+ @delegate.fetch(key) {
+ @delegate[:id] = @by.send(:extract_session_id, @env)
+ }
+ else
+ @delegate[key]
+ end
+ end
+
+ def []=(k,v); @delegate[k] = v; end
+ def to_hash; @delegate.dup; end
+ def values_at(*args); @delegate.values_at(*args); end
+ end
+
+ def initialize(by, env)
+ @by = by
+ @env = env
+ @delegate = {}
+ @loaded = false
+ @exists = nil # we haven't checked yet
+ end
+
+ def options
+ Options.find @env
+ end
+
+ def destroy
+ clear
+ options = self.options || {}
+ @by.send(:destroy_session, @env, options[:id], options)
+ options[:id] = nil
+ @loaded = false
+ end
+
+ def [](key)
+ load_for_read!
+ @delegate[key.to_s]
+ end
+
+ def has_key?(key)
+ load_for_read!
+ @delegate.key?(key.to_s)
+ end
+ alias :key? :has_key?
+ alias :include? :has_key?
+
+ def []=(key, value)
+ load_for_write!
+ @delegate[key.to_s] = value
+ end
+
+ def clear
+ load_for_write!
+ @delegate.clear
+ end
+
+ def to_hash
+ load_for_read!
+ @delegate.dup.delete_if { |_,v| v.nil? }
+ end
+
+ def update(hash)
+ load_for_write!
+ @delegate.update stringify_keys(hash)
+ end
+
+ def delete(key)
+ load_for_write!
+ @delegate.delete key.to_s
+ end
+
+ def inspect
+ if loaded?
+ super
+ else
+ "#<#{self.class}:0x#{(object_id << 1).to_s(16)} not yet loaded>"
+ end
+ end
+
+ def exists?
+ return @exists unless @exists.nil?
+ @exists = @by.send(:session_exists?, @env)
+ end
+
+ def loaded?
+ @loaded
+ end
+
+ def empty?
+ load_for_read!
+ @delegate.empty?
+ end
+
+ def merge!(other)
+ load_for_write!
+ @delegate.merge!(other)
+ end
+
+ private
+
+ def load_for_read!
+ load! if !loaded? && exists?
+ end
+
+ def load_for_write!
+ load! unless loaded?
+ end
+
+ def load!
+ id, session = @by.load_session @env
+ options[:id] = id
+ @delegate.replace(stringify_keys(session))
+ @loaded = true
+ end
+
+ def stringify_keys(other)
+ other.each_with_object({}) { |(key, value), hash|
+ hash[key.to_s] = value
+ }
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 1a1a054985..2a7d540517 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)
@@ -533,7 +535,8 @@ module ActionDispatch
private
def map_method(method, args, &block)
options = args.extract_options!
- options[:via] = method
+ options[:via] = method
+ options[:path] ||= args.first if args.first.is_a?(String)
match(*args, options, &block)
self
end
@@ -1507,7 +1510,7 @@ module ActionDispatch
prefix = shallow_scoping? ?
"#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
- path = if canonical_action?(action, path.blank?)
+ if canonical_action?(action, path.blank?)
prefix.to_s
else
"#{prefix}/#{action_path(action, path)}"
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/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index d75bb1c2de..ee02f4b531 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -68,7 +68,7 @@ module ActionDispatch
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
- # you can do that by including ActionController::UrlFor in your class:
+ # you can do that by including Rails.application.routes.url_helpers in your class:
#
# class User < ActiveRecord::Base
# include Rails.application.routes.url_helpers
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/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index d61c2bbee2..cafcd93f58 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -288,38 +288,55 @@ module ActionView
#
# Examples (call, result):
# options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
- # <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
+ # # <option value="$">Dollar</option>
+ # # <option value="DKK">Kroner</option>
#
# options_for_select([ "VISA", "MasterCard" ], "MasterCard")
- # <option>VISA</option>\n<option selected="selected">MasterCard</option>
+ # # <option>VISA</option>
+ # # <option selected="selected">MasterCard</option>
#
# options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
- # <option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
+ # # <option value="$20">Basic</option>
+ # # <option value="$40" selected="selected">Plus</option>
#
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
- # <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
+ # # <option selected="selected">VISA</option>
+ # # <option>MasterCard</option>
+ # # <option selected="selected">Discover</option>
#
# You can optionally provide html attributes as the last element of the array.
#
# Examples:
# options_for_select([ "Denmark", ["USA", {:class => 'bold'}], "Sweden" ], ["USA", "Sweden"])
- # <option value="Denmark">Denmark</option>\n<option value="USA" class="bold" selected="selected">USA</option>\n<option value="Sweden" selected="selected">Sweden</option>
+ # # <option value="Denmark">Denmark</option>
+ # # <option value="USA" class="bold" selected="selected">USA</option>
+ # # <option value="Sweden" selected="selected">Sweden</option>
#
# options_for_select([["Dollar", "$", {:class => "bold"}], ["Kroner", "DKK", {:onclick => "alert('HI');"}]])
- # <option value="$" class="bold">Dollar</option>\n<option value="DKK" onclick="alert('HI');">Kroner</option>
+ # # <option value="$" class="bold">Dollar</option>
+ # # <option value="DKK" onclick="alert('HI');">Kroner</option>
#
# If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
# or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
#
# Examples:
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => "Super Platinum")
- # <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # <option value="Free">Free</option>
+ # # <option value="Basic">Basic</option>
+ # # <option value="Advanced">Advanced</option>
+ # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => ["Advanced", "Super Platinum"])
- # <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced" disabled="disabled">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # <option value="Free">Free</option>
+ # # <option value="Basic">Basic</option>
+ # # <option value="Advanced" disabled="disabled">Advanced</option>
+ # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :selected => "Free", :disabled => "Super Platinum")
- # <option value="Free" selected="selected">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # <option value="Free" selected="selected">Free</option>
+ # # <option value="Basic">Basic</option>
+ # # <option value="Advanced">Advanced</option>
+ # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
def options_for_select(container, selected = nil)
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index b5e0970612..ef35a411ad 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -45,7 +45,7 @@ module ActionView
# # => <form action="/posts" method="post">
#
# form_tag('/posts/1', :method => :put)
- # # => <form action="/posts/1" method="put">
+ # # => <form action="/posts/1" method="post"> ... <input name="_method" type="hidden" value="put" /> ...
#
# form_tag('/upload', :multipart => true)
# # => <form action="/upload" method="post" enctype="multipart/form-data">
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 12bb162da2..fffc37ce9e 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -45,7 +45,7 @@ module ActionView
# if logged_in
# concat "Logged in!"
# else
- # concat link_to('login', :action => login)
+ # concat link_to('login', :action => :login)
# end
# # will either display "Logged in!" or a login link
# %>
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index d0f716cc80..1145f348c2 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -303,7 +303,10 @@ module ActionView
#
# <%= button_to "Create", :action => "create", :remote => true, :form => { "data-type" => "json" } %>
# # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
- # # <div><input value="Create" type="submit" /></div>
+ # # <div>
+ # # <input value="Create" type="submit" />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
+ # # </div>
# # </form>"
#
#
@@ -312,7 +315,8 @@ module ActionView
# # => "<form method="post" action="/images/delete/1" class="button_to">
# # <div>
# # <input type="hidden" name="_method" value="delete" />
- # # <input data-confirm='Are you sure?' value="Delete" type="submit" />
+ # # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </div>
# # </form>"
#
@@ -323,6 +327,7 @@ module ActionView
# # <div>
# # <input name='_method' value='delete' type='hidden' />
# # <input value='Destroy' type='submit' disable_with='loading...' data-confirm='Are you sure?' />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </div>
# # </form>"
# #
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index c5d5540510..87609fd5ff 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -343,8 +343,8 @@ module ActionView
if String === partial && @variable.to_s !~ /^[a-z_][a-zA-Z_0-9]*$/
raise ArgumentError.new("The partial name (#{partial}) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a letter or underscore, " +
- "and is followed by any combinations of letters, numbers, or underscores.")
+ "make sure your partial name starts with a lowercase letter or underscore, " +
+ "and is followed by any combination of letters, numbers and underscores.")
end
self
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index b121ca9481..9b0d4d0f4c 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -76,6 +76,11 @@ class ActionPackAssertionsController < ActionController::Base
render "test/hello_world", :layout => "layouts/standard"
end
+ def render_with_layout_and_partial
+ @variable_for_layout = nil
+ render "test/hello_world_with_partial", :layout => "layouts/standard"
+ end
+
def session_stuffing
session['xmas'] = 'turkey'
render :text => "ho ho ho"
@@ -179,6 +184,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 +220,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
@@ -469,11 +478,43 @@ class AssertTemplateTest < ActionController::TestCase
end
end
+ def test_fails_expecting_no_layout
+ get :render_with_layout
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :layout => nil
+ end
+ end
+
def test_passes_with_correct_layout
get :render_with_layout
assert_template :layout => "layouts/standard"
end
+ def test_passes_with_layout_and_partial
+ get :render_with_layout_and_partial
+ assert_template :layout => "layouts/standard"
+ end
+
+ def test_passed_with_no_layout
+ get :hello_world
+ assert_template :layout => nil
+ end
+
+ def test_passed_with_no_layout_false
+ get :hello_world
+ assert_template :layout => false
+ end
+
+ def test_passes_with_correct_layout_without_layouts_prefix
+ get :render_with_layout
+ assert_template :layout => "standard"
+ end
+
+ def test_passes_with_correct_layout_symbol
+ get :render_with_layout
+ assert_template :layout => :standard
+ end
+
def test_assert_template_reset_between_requests
get :hello_world
assert_template 'test/hello_world'
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/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index c424dcbd8d..5bcd79ebec 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -37,6 +37,36 @@ module BareMetalTest
def index
head :not_found
end
+
+ def continue
+ self.content_type = "text/html"
+ head 100
+ end
+
+ def switching_protocols
+ self.content_type = "text/html"
+ head 101
+ end
+
+ def processing
+ self.content_type = "text/html"
+ head 102
+ end
+
+ def no_content
+ self.content_type = "text/html"
+ head 204
+ end
+
+ def reset_content
+ self.content_type = "text/html"
+ head 205
+ end
+
+ def not_modified
+ self.content_type = "text/html"
+ head 304
+ end
end
class HeadTest < ActiveSupport::TestCase
@@ -44,6 +74,42 @@ module BareMetalTest
status = HeadController.action(:index).call(Rack::MockRequest.env_for("/")).first
assert_equal 404, status
end
+
+ test "head :continue (100) does not return a content-type header" do
+ headers = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :continue (101) does not return a content-type header" do
+ headers = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :processing (102) does not return a content-type header" do
+ headers = HeadController.action(:processing).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :no_content (204) does not return a content-type header" do
+ headers = HeadController.action(:no_content).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :reset_content (205) does not return a content-type header" do
+ headers = HeadController.action(:reset_content).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :not_modified (304) does not return a content-type header" do
+ headers = HeadController.action(:not_modified).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
end
class BareControllerTest < ActionController::TestCase
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index c4d2614200..fa1608b9df 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -174,7 +174,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_accessible_wrapped_keys_from_matching_model
User.expects(:respond_to?).with(:accessible_attributes).returns(true)
- User.expects(:accessible_attributes).twice.returns(["username"])
+ User.expects(:accessible_attributes).with(:default).twice.returns(["username"])
with_default_wrapper_options do
@request.env['CONTENT_TYPE'] = 'application/json'
@@ -186,7 +186,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_accessible_wrapped_keys_from_specified_model
with_default_wrapper_options do
Person.expects(:respond_to?).with(:accessible_attributes).returns(true)
- Person.expects(:accessible_attributes).twice.returns(["username"])
+ Person.expects(:accessible_attributes).with(:default).twice.returns(["username"])
UsersController.wrap_parameters Person
@@ -195,6 +195,19 @@ class ParamsWrapperTest < ActionController::TestCase
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
end
end
+
+ def test_accessible_wrapped_keys_with_role_from_specified_model
+ with_default_wrapper_options do
+ Person.expects(:respond_to?).with(:accessible_attributes).returns(true)
+ Person.expects(:accessible_attributes).with(:admin).twice.returns(["username"])
+
+ UsersController.wrap_parameters Person, :as => :admin
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
def test_not_wrapping_abstract_model
User.expects(:respond_to?).with(:accessible_attributes).returns(false)
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 9441b46d47..cd91064ab8 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
@@ -1423,7 +1405,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
end
end
-
+
def test_route_with_subdomain_and_constraints_must_receive_params
name_param = nil
set.draw do
@@ -1436,7 +1418,7 @@ class RouteSetTest < ActiveSupport::TestCase
set.recognize_path('http://subdomain.example.org/page/mypage'))
assert_equal(name_param, 'mypage')
end
-
+
def test_route_requirement_recognize_with_ignore_case
set.draw do
get 'page/:name' => 'pages#show',
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/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
new file mode 100644
index 0000000000..4d24456ba6
--- /dev/null
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -0,0 +1,48 @@
+require 'abstract_unit'
+require 'action_dispatch/middleware/session/abstract_store'
+
+module ActionDispatch
+ class Request
+ class SessionTest < ActiveSupport::TestCase
+ def test_create_adds_itself_to_env
+ env = {}
+ s = Session.create(store, env, {})
+ assert_equal s, env[Rack::Session::Abstract::ENV_SESSION_KEY]
+ end
+
+ def test_to_hash
+ env = {}
+ s = Session.create(store, env, {})
+ s['foo'] = 'bar'
+ assert_equal 'bar', s['foo']
+ assert_equal({'foo' => 'bar'}, s.to_hash)
+ end
+
+ def test_create_merges_old
+ env = {}
+ s = Session.create(store, env, {})
+ s['foo'] = 'bar'
+
+ s1 = Session.create(store, env, {})
+ refute_equal s, s1
+ assert_equal 'bar', s1['foo']
+ end
+
+ def test_find
+ env = {}
+ assert_nil Session.find(env)
+
+ s = Session.create(store, env, {})
+ assert_equal s, Session.find(env)
+ end
+
+ private
+ def store
+ Class.new {
+ def load_session(env); [1, {}]; end
+ def session_exists?(env); true; end
+ }.new
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 4b8d308043..c8e1b34b99 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -171,6 +171,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
post :preview, :on => :collection
end
end
+
+ post 'new', :action => 'new', :on => :collection, :as => :new
end
resources :replies do
@@ -876,6 +878,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/projects/1/edit', edit_project_path(:id => '1')
end
+ def test_projects_with_post_action_and_new_path_on_collection
+ post '/projects/new'
+ assert_equal "project#new", @response.body
+ assert_equal "/projects/new", new_projects_path
+ end
+
def test_projects_involvements
get '/projects/1/involvements'
assert_equal 'involvements#index', @response.body
@@ -2450,7 +2458,6 @@ class TestMultipleNestedController < ActionDispatch::IntegrationTest
get "/foo/bar/baz"
assert_equal "/pooh", @response.body
end
-
end
class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest
@@ -2512,3 +2519,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/dispatch/session/abstract_store_test.rb b/actionpack/test/dispatch/session/abstract_store_test.rb
new file mode 100644
index 0000000000..8daf3d3f5e
--- /dev/null
+++ b/actionpack/test/dispatch/session/abstract_store_test.rb
@@ -0,0 +1,56 @@
+require 'abstract_unit'
+require 'action_dispatch/middleware/session/abstract_store'
+
+module ActionDispatch
+ module Session
+ class AbstractStoreTest < ActiveSupport::TestCase
+ class MemoryStore < AbstractStore
+ def initialize(app)
+ @sessions = {}
+ super
+ end
+
+ def get_session(env, sid)
+ sid ||= 1
+ session = @sessions[sid] ||= {}
+ [sid, session]
+ end
+
+ def set_session(env, sid, session, options)
+ @sessions[sid] = session
+ end
+ end
+
+ def test_session_is_set
+ env = {}
+ as = MemoryStore.new app
+ as.call(env)
+
+ assert @env
+ assert Request::Session.find @env
+ end
+
+ def test_new_session_object_is_merged_with_old
+ env = {}
+ as = MemoryStore.new app
+ as.call(env)
+
+ assert @env
+ session = Request::Session.find @env
+ session['foo'] = 'bar'
+
+ as.call(@env)
+ session1 = Request::Session.find @env
+
+ refute_equal session, session1
+ assert_equal session.to_hash, session1.to_hash
+ end
+
+ private
+ def app(&block)
+ @env = nil
+ lambda { |env| @env = env }
+ end
+ end
+ end
+end
diff --git a/actionpack/test/fixtures/test/hello_world_with_partial.html.erb b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb
new file mode 100644
index 0000000000..ec31545356
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb
@@ -0,0 +1,2 @@
+Hello world!
+<%= render '/test/partial' %>
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index cdaca56ef2..e7f5f100bf 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -153,15 +153,15 @@ module RenderTestCases
def test_render_partial_with_invalid_name
e = assert_raises(ArgumentError) { @view.render(:partial => "test/200") }
assert_equal "The partial name (test/200) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a letter or underscore, " +
- "and is followed by any combinations of letters, numbers, or underscores.", e.message
+ "make sure your partial name starts with a lowercase letter or underscore, " +
+ "and is followed by any combination of letters, numbers and underscores.", e.message
end
def test_render_partial_with_missing_filename
e = assert_raises(ArgumentError) { @view.render(:partial => "test/") }
assert_equal "The partial name (test/) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a letter or underscore, " +
- "and is followed by any combinations of letters, numbers, or underscores.", e.message
+ "make sure your partial name starts with a lowercase letter or underscore, " +
+ "and is followed by any combination of letters, numbers and underscores.", e.message
end
def test_render_partial_with_incompatible_object
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 9b05384792..1fd75141f8 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -25,7 +25,7 @@ to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>.
person = Person.new(:name => 'bob', :age => '18')
person.name # => 'bob'
- person.age # => 18
+ person.age # => '18'
person.valid? # => true
It includes model name introspections, conversions, translations and
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 4c7dae42f0..f5ea285ccb 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -110,25 +110,24 @@ module ActiveModel
end
end
- private
- # 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
+ # 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
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index c91938a7ee..ade6026602 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -138,7 +138,14 @@ 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
@@ -150,7 +157,7 @@ class ObserverTest < ActiveModel::TestCase
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.rb b/activerecord/lib/active_record.rb
index c4b10a8dae..ed26b4899f 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -63,8 +63,6 @@ module ActiveRecord
autoload :CounterCache
autoload :ConnectionHandling
autoload :DynamicMatchers
- autoload :DynamicFinderMatch
- autoload :DynamicScopeMatch
autoload :Explain
autoload :Inheritance
autoload :Integration
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index c39284539c..a4db627535 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -86,6 +86,12 @@ module ActiveRecord
# customer.address_street = "Hyancintvej"
# customer.address_city = "Copenhagen"
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
+ #
+ # customer.address_street = "Vesterbrogade"
+ # customer.address # => Address.new("Hyancintvej", "Copenhagen")
+ # customer.clear_aggregation_cache
+ # customer.address # => Address.new("Vesterbrogade", "Copenhagen")
+ #
# customer.address = Address.new("May Street", "Chicago")
# customer.address_street # => "May Street"
# customer.address_city # => "Chicago"
@@ -101,8 +107,8 @@ module ActiveRecord
# ActiveRecord::Base classes are entity objects.
#
# It's also important to treat the value objects as immutable. Don't allow the Money object to have
- # its amount changed after creation. Create a new Money object with the new value instead. This
- # is exemplified by the Money#exchange_to method that returns a new value object instead of changing
+ # its amount changed after creation. Create a new Money object with the new value instead. The
+ # Money#exchange_to method is an example of this. It returns a new value object instead of changing
# its own values. Active Record won't persist value objects that have been changed through means
# other than the writer method.
#
@@ -119,7 +125,7 @@ module ActiveRecord
# option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
# a custom constructor to be specified.
#
- # When a new value is assigned to the value object the default assumption is that the new value
+ # When a new value is assigned to the value object, the default assumption is that the new value
# is an instance of the value class. Specifying a custom converter allows the new value to be automatically
# converted to an instance of value class if necessary.
#
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index b901f06ca4..c30e8e08b8 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1129,7 +1129,7 @@ module ActiveRecord
# it would skip the first 4 rows.
# [:select]
# By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
- # you, for example, want to do a join but not include the joined columns. Do not forget
+ # you want to do a join but not include the joined columns, for example. Do not forget
# to include the primary and foreign keys, otherwise it will raise an error.
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
@@ -1264,8 +1264,8 @@ module ActiveRecord
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
- # you want to do a join but not include the joined columns. Do not forget to include the
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
+ # you want to do a join but not include the joined columns, for example. Do not forget to include the
# primary and foreign keys, otherwise it will raise an error.
# [:through]
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
@@ -1355,7 +1355,7 @@ module ActiveRecord
# SQL fragment, such as <tt>authorized = 1</tt>.
# [:select]
# By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed
- # if, for example, you want to do a join but not include the joined columns. Do not
+ # if you want to do a join but not include the joined columns, for example. Do not
# forget to include the primary and foreign keys, otherwise it will raise an error.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
@@ -1382,7 +1382,7 @@ module ActiveRecord
# and +decrement_counter+. The counter cache is incremented when an object of this
# class is created and decremented when it's destroyed. This requires that a column
# named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
- # is used on the associate class (such as a Post class) - that is the migration for
+ # is used on the associate class (such as a Post class) - that is the migration for
# <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will
# return the count cached, see note below). You can also specify a custom counter
# cache column by providing a column name instead of a +true+/+false+ value to this
@@ -1432,7 +1432,7 @@ module ActiveRecord
# Specifies a many-to-many relationship with another class. This associates two classes via an
# intermediate join table. Unless the join table is explicitly specified as an option, it is
# guessed using the lexical order of the class names. So a join between Developer and Project
- # will give the default join table name of "developers_projects" because "D" outranks "P".
+ # will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically.
# Note that this precedence is calculated using the <tt><</tt> operator for String. This
# means that if the strings are of different lengths, and the strings are equal when compared
# up to the shortest length, then the longer string is considered of higher
@@ -1576,8 +1576,8 @@ module ActiveRecord
# An integer determining the offset from where the rows should be fetched. So at 5,
# it would skip the first 4 rows.
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
- # you want to do a join but not include the joined columns. Do not forget to include the primary
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
+ # you want to do a join but exclude the joined columns, for example. Do not forget to include the primary
# and foreign keys, otherwise it will raise an error.
# [:readonly]
# If true, all the associated objects are readonly through the association.
@@ -1596,7 +1596,7 @@ module ActiveRecord
# has_and_belongs_to_many :categories, :join_table => "prods_cats"
# has_and_belongs_to_many :categories, :readonly => true
# has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
- # "DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}"
+ # proc { |record| "DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}" }
def has_and_belongs_to_many(name, options = {}, &extension)
Builder::HasAndBelongsToMany.build(self, name, options, &extension)
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index ad029d1101..261a829281 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -79,9 +79,9 @@ module ActiveRecord
end
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|
+ match = DynamicMatchers::Method.match(self, method)
+ if match && match.is_a?(DynamicMatchers::Instantiator)
+ scoped.send(method, *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..4d5459939b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -511,8 +511,13 @@ module ActiveRecord
else super
end
when Float
- return super unless value.infinite? && column.type == :datetime
- "'#{value.to_s.downcase}'"
+ if value.infinite? && column.type == :datetime
+ "'#{value.to_s.downcase}'"
+ elsif value.infinite? || value.nan?
+ "'#{value.to_s}'"
+ else
+ super
+ end
when Numeric
return super unless column.sql_type == 'money'
# Not truly string input, so doesn't require (or allow) escape string syntax.
@@ -1233,7 +1238,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/core.rb b/activerecord/lib/active_record/core.rb
index eb8f4ad669..9a76c9f617 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -1,5 +1,6 @@
require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/object/duplicable'
require 'thread'
module ActiveRecord
@@ -165,7 +166,9 @@ module ActiveRecord
# # Instantiates a single new object bypassing mass-assignment security
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
def initialize(attributes = nil, options = {})
- @attributes = self.class.initialize_attributes(self.class.column_defaults.dup)
+ # TODO: use deep_dup after fixing it to also dup values
+ defaults = Hash[self.class.column_defaults.map { |k, v| [k, v.duplicable? ? v.dup : v] }]
+ @attributes = self.class.initialize_attributes(defaults)
@columns_hash = self.class.column_types.dup
init_internals
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
deleted file mode 100644
index 0473d6aafc..0000000000
--- a/activerecord/lib/active_record/dynamic_finder_match.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-module ActiveRecord
-
- # = Active Record Dynamic Finder Match
- #
- # Refer to ActiveRecord::Base documentation for Dynamic attribute-based finders for detailed info
- #
- class DynamicFinderMatch
- def self.match(method)
- method = method.to_s
- klass = klasses.find do |_klass|
- _klass.matches?(method)
- end
- klass.new(method) if klass
- end
-
- def self.matches?(method)
- method =~ self::METHOD_PATTERN
- end
-
- def self.klasses
- [FindBy, FindByBang, FindOrInitializeCreateBy, FindOrCreateByBang]
- end
-
- def initialize(method)
- @finder = :first
- @instantiator = nil
- match_data = method.match(self.class::METHOD_PATTERN)
- @attribute_names = match_data[-1].split("_and_")
- initialize_from_match_data(match_data)
- end
-
- attr_reader :finder, :attribute_names, :instantiator
-
- def finder?
- @finder && !@instantiator
- end
-
- def creator?
- @finder == :first && @instantiator == :create
- end
-
- def instantiator?
- @instantiator
- end
-
- def bang?
- false
- end
-
- def valid_arguments?(arguments)
- arguments.size >= @attribute_names.size
- end
-
- def save_record?
- @instantiator == :create
- end
-
- def save_method
- bang? ? :save! : :save
- end
-
- private
-
- def initialize_from_match_data(match_data)
- end
- end
-
- class FindBy < DynamicFinderMatch
- METHOD_PATTERN = /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/
-
- def initialize_from_match_data(match_data)
- @finder = :last if match_data[1] == 'last_'
- @finder = :all if match_data[1] == 'all_'
- end
- end
-
- class FindByBang < DynamicFinderMatch
- METHOD_PATTERN = /^find_by_([_a-zA-Z]\w*)\!$/
-
- def bang?
- true
- end
- end
-
- class FindOrInitializeCreateBy < DynamicFinderMatch
- METHOD_PATTERN = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
-
- def initialize_from_match_data(match_data)
- @instantiator = match_data[1] == 'initialize' ? :new : :create
- end
-
- def valid_arguments?(arguments)
- arguments.size == 1 && arguments.first.is_a?(Hash) || super
- end
- end
-
- class FindOrCreateByBang < DynamicFinderMatch
- METHOD_PATTERN = /^find_or_create_by_([_a-zA-Z]\w*)\!$/
-
- def initialize_from_match_data(match_data)
- @instantiator = :create
- end
-
- def bang?
- true
- end
- end
-end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index e35b1c91a0..e278e62ce7 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -1,83 +1,130 @@
module ActiveRecord
- module DynamicMatchers
- def respond_to?(method_id, include_private = false)
- match = find_dynamic_match(method_id)
- valid_match = match && all_attributes_exists?(match.attribute_names)
+ module DynamicMatchers #:nodoc:
+ # This code in this file seems to have a lot of indirection, but the indirection
+ # is there to provide extension points for the active_record_deprecated_finders
+ # gem. When we stop supporting active_record_deprecated_finders (from Rails 5),
+ # then we can remove the indirection.
- valid_match || super
+ def respond_to?(name, include_private = false)
+ match = Method.match(self, name)
+ match && match.valid? || super
end
private
- # Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
- # <tt>User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders
- # section at the top of this file for more detailed information.
- #
- # It's even possible to use all the additional parameters to +find+. For example, the
- # full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
- #
- # Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
- # is first invoked, so that future attempts to use it do not run through method_missing.
- def method_missing(method_id, *arguments, &block)
- if match = find_dynamic_match(method_id)
- attribute_names = match.attribute_names
- super unless all_attributes_exists?(attribute_names)
-
- unless match.valid_arguments?(arguments)
- method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'"
- backtrace = [method_trace] + caller
- raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace
- end
+ def method_missing(name, *arguments, &block)
+ match = Method.match(self, name)
- if match.respond_to?(:scope?) && match.scope?
- define_scope_method(method_id, attribute_names)
- send(method_id, *arguments)
- elsif match.finder?
- options = arguments.extract_options!
- relation = options.any? ? scoped(options) : scoped
- relation.send :find_by_attributes, match, attribute_names, *arguments, &block
- elsif match.instantiator?
- scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
- end
+ if match && match.valid?
+ match.define
+ send(name, *arguments, &block)
else
super
end
end
- def define_scope_method(method_id, attribute_names) #:nodoc
- self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
- conditions = Hash[[:#{attribute_names.join(',:')}].zip(args)] # conditions = Hash[[:user_name, :password].zip(args)]
- where(conditions) # where(conditions)
- end # end
- METHOD
- end
+ class Method
+ @matchers = []
- def find_dynamic_match(method_id) #:nodoc:
- DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id)
- end
+ class << self
+ attr_reader :matchers
- # Similar in purpose to +expand_hash_conditions_for_aggregates+.
- def expand_attribute_names_for_aggregates(attribute_names)
- attribute_names.map do |attribute_name|
- if aggregation = reflect_on_aggregation(attribute_name.to_sym)
- aggregate_mapping(aggregation).map do |field_attr, _|
- field_attr.to_sym
- end
- else
- attribute_name.to_sym
+ def match(model, name)
+ klass = matchers.find { |k| name =~ k.pattern }
+ klass.new(model, name) if klass
+ end
+
+ def pattern
+ /^#{prefix}_([_a-zA-Z]\w*)#{suffix}$/
+ end
+
+ def prefix
+ raise NotImplementedError
+ end
+
+ def suffix
+ ''
end
- end.flatten
+ end
+
+ attr_reader :model, :name, :attribute_names
+
+ def initialize(model, name)
+ @model = model
+ @name = name.to_s
+ @attribute_names = @name.match(self.class.pattern)[1].split('_and_')
+ end
+
+ def valid?
+ attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
+ end
+
+ def define
+ model.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def self.#{name}(#{signature})
+ #{body}
+ end
+ CODE
+ end
+
+ def body
+ raise NotImplementedError
+ end
end
- def all_attributes_exists?(attribute_names)
- (expand_attribute_names_for_aggregates(attribute_names) -
- column_methods_hash.keys).empty?
+ module Finder
+ # Extended in active_record_deprecated_finders
+ def body
+ result
+ end
+
+ # Extended in active_record_deprecated_finders
+ def result
+ "#{finder}(#{attributes_hash})"
+ end
+
+ # Extended in active_record_deprecated_finders
+ def signature
+ attribute_names.join(', ')
+ end
+
+ def attributes_hash
+ "{" + attribute_names.map { |name| ":#{name} => #{name}" }.join(',') + "}"
+ end
+
+ def finder
+ raise NotImplementedError
+ end
end
- def aggregate_mapping(reflection)
- mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
- mapping.first.is_a?(Array) ? mapping : [mapping]
+ class FindBy < Method
+ Method.matchers << self
+ include Finder
+
+ def self.prefix
+ "find_by"
+ end
+
+ def finder
+ "find_by"
+ end
+ end
+
+ class FindByBang < Method
+ Method.matchers << self
+ include Finder
+
+ def self.prefix
+ "find_by"
+ end
+
+ def self.suffix
+ "!"
+ end
+
+ def finder
+ "find_by!"
+ end
end
end
end
diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb
deleted file mode 100644
index 6c043d29c4..0000000000
--- a/activerecord/lib/active_record/dynamic_scope_match.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module ActiveRecord
-
- # = Active Record Dynamic Scope Match
- #
- # Provides dynamic attribute-based scopes such as <tt>scoped_by_price(4.99)</tt>
- # if, for example, the <tt>Product</tt> has an attribute with that name. You can
- # chain more <tt>scoped_by_* </tt> methods after the other. It acts like a named
- # scope except that it's dynamic.
- class DynamicScopeMatch
- METHOD_PATTERN = /^scoped_by_([_a-zA-Z]\w*)$/
-
- def self.match(method)
- if method.to_s =~ METHOD_PATTERN
- new(true, $1 && $1.split('_and_'))
- end
- end
-
- def initialize(scope, attribute_names)
- @scope = scope
- @attribute_names = attribute_names
- end
-
- attr_reader :scope, :attribute_names
- alias :scope? :scope
-
- def valid_arguments?(arguments)
- arguments.size >= @attribute_names.size
- end
- end
-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/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 9796b0a321..a01e2f74ff 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -823,7 +823,7 @@ module ActiveRecord
end
def setup_fixtures
- return unless !ActiveRecord::Base.configurations.blank?
+ return if ActiveRecord::Base.configurations.blank?
if pre_loaded_fixtures && !use_transactional_fixtures
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 29b8b2fb73..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
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index d4f4d593c6..c380b5c029 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -153,6 +153,10 @@ module ActiveRecord
# Holds all the meta-data about an aggregation as it was specified in the
# Active Record class.
class AggregateReflection < MacroReflection #:nodoc:
+ def mapping
+ mapping = options[:mapping] || [name, name]
+ mapping.first.is_a?(Array) ? mapping : [mapping]
+ end
end
# Holds all the meta-data about an association as it was specified in the
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 f388b75c05..3ce9995031 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -144,7 +144,7 @@ module ActiveRecord
# 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
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
def ids
pluck primary_key
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 1ceb1949a4..a78e3d08e4 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
@@ -210,47 +243,6 @@ module ActiveRecord
ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
end
- def find_by_attributes(match, attributes, *args)
- conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
- result = where(conditions).send(match.finder)
-
- if match.bang? && result.blank?
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
- else
- if block_given? && result
- yield(result)
- else
- result
- end
- end
- end
-
- def find_or_instantiator_by_attributes(match, attributes, *args)
- options = args.size > 1 && args.last(2).all?{ |a| a.is_a?(Hash) } ? args.extract_options! : {}
- protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
- args.each_with_index do |arg, i|
- if arg.is_a?(Hash)
- protected_attributes_for_create = args[i].with_indifferent_access
- else
- unprotected_attributes_for_create[attributes[i]] = args[i]
- end
- end
-
- conditions = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes).symbolize_keys
-
- record = where(conditions).first
-
- unless record
- record = @klass.new(protected_attributes_for_create, options) do |r|
- r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
- end
- yield(record) if block_given?
- record.send(match.save_method) if match.save_record?
- end
-
- record
- end
-
def find_with_ids(*ids)
return to_a.find { |*block_args| yield(*block_args) } if block_given?
@@ -315,11 +307,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
@@ -331,7 +336,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/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 81b13fe529..5530be3219 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -58,7 +58,7 @@ module ActiveRecord
expanded_attrs = {}
attrs.each do |attr, value|
if aggregation = reflect_on_aggregation(attr.to_sym)
- mapping = aggregate_mapping(aggregation)
+ mapping = aggregation.mapping
mapping.each do |field_attr, aggregate_attr|
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
expanded_attrs[field_attr] = value
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 172055f15c..f8a605b67c 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -19,6 +19,18 @@ module ActiveRecord
assert_equal 'f', @conn.type_cast(false, nil)
assert_equal 'f', @conn.type_cast(false, c)
end
+
+ def test_quote_float_nan
+ nan = 0.0/0
+ c = Column.new(nil, 1, 'float')
+ assert_equal "'NaN'", @conn.quote(nan, c)
+ end
+
+ def test_quote_float_infinity
+ infinity = 1.0/0
+ c = Column.new(nil, 1, 'float')
+ assert_equal "'Infinity'", @conn.quote(infinity, c)
+ end
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 35e0045e4c..c1b0cb8886 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1909,7 +1909,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_attribute_names
- assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id"],
+ assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id", "description"],
Company.attribute_names
end
@@ -2001,6 +2001,12 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil hash['firm_name']
end
+ def test_default_values_are_deeply_dupped
+ company = Company.new
+ company.description << "foo"
+ assert_equal "", Company.new.description
+ end
+
["find_by", "find_by!"].each do |meth|
test "#{meth} delegates to scoped" do
record = stub
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/dynamic_finder_match_test.rb b/activerecord/test/cases/dynamic_finder_match_test.rb
deleted file mode 100644
index db619faa83..0000000000
--- a/activerecord/test/cases/dynamic_finder_match_test.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- class DynamicFinderMatchTest < ActiveRecord::TestCase
- def test_find_or_create_by
- match = DynamicFinderMatch.match("find_or_create_by_age_and_sex_and_location")
- assert_not_nil match
- assert !match.finder?
- assert match.instantiator?
- assert_equal :first, match.finder
- assert_equal :create, match.instantiator
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_or_initialize_by
- match = DynamicFinderMatch.match("find_or_initialize_by_age_and_sex_and_location")
- assert_not_nil match
- assert !match.finder?
- assert match.instantiator?
- assert_equal :first, match.finder
- assert_equal :new, match.instantiator
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_no_match
- assert_nil DynamicFinderMatch.match("not_a_finder")
- end
-
- def find_by_bang
- match = DynamicFinderMatch.match("find_by_age_and_sex_and_location!")
- assert_not_nil match
- assert match.finder?
- assert match.bang?
- assert_equal :first, match.finder
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_by
- match = DynamicFinderMatch.match("find_by_age_and_sex_and_location")
- assert_not_nil match
- assert match.finder?
- assert_equal :first, match.finder
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_by_with_symbol
- m = DynamicFinderMatch.match(:find_by_foo)
- assert_equal :first, m.finder
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_all_by_with_symbol
- m = DynamicFinderMatch.match(:find_all_by_foo)
- assert_equal :all, m.finder
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_all_by
- match = DynamicFinderMatch.match("find_all_by_age_and_sex_and_location")
- assert_not_nil match
- assert match.finder?
- assert_equal :all, match.finder
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_last_by
- m = DynamicFinderMatch.match(:find_last_by_foo)
- assert_equal :last, m.finder
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_by!
- m = DynamicFinderMatch.match(:find_by_foo!)
- assert_equal :first, m.finder
- assert m.bang?, 'should be banging'
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_or_create
- m = DynamicFinderMatch.match(:find_or_create_by_foo)
- assert_equal :first, m.finder
- assert_equal %w{ foo }, m.attribute_names
- assert_equal :create, m.instantiator
- end
-
- def test_find_or_create!
- m = DynamicFinderMatch.match(:find_or_create_by_foo!)
- assert_equal :first, m.finder
- assert m.bang?, 'should be banging'
- assert_equal %w{ foo }, m.attribute_names
- assert_equal :create, m.instantiator
- end
-
- def test_find_or_initialize
- m = DynamicFinderMatch.match(:find_or_initialize_by_foo)
- assert_equal :first, m.finder
- assert_equal %w{ foo }, m.attribute_names
- assert_equal :new, m.instantiator
- end
-
- def test_garbage
- assert !DynamicFinderMatch.match(:fooo), 'should be false'
- assert !DynamicFinderMatch.match(:find_by), 'should be false'
- 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/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index f8557259fb..2e87c5be2f 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -442,13 +442,12 @@ end
class DynamicScopeMatchTest < ActiveRecord::TestCase
def test_scoped_by_no_match
- assert_nil ActiveRecord::DynamicScopeMatch.match("not_scoped_at_all")
+ assert_nil ActiveRecord::DynamicMatchers::Method.match(nil, "not_scoped_at_all")
end
def test_scoped_by
- match = ActiveRecord::DynamicScopeMatch.match("scoped_by_age_and_sex_and_location")
+ match = ActiveRecord::DynamicMatchers::Method.match(nil, "scoped_by_age_and_sex_and_location")
assert_not_nil match
- assert match.scope?
assert_equal %w(age sex location), match.attribute_names
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/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 377fde5c96..7082d5dc86 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -173,6 +173,7 @@ ActiveRecord::Schema.define do
t.integer :client_of
t.integer :rating, :default => 1
t.integer :account_id
+ t.string :description, :null => false, :default => ""
end
add_index :companies, [:firm_id, :type, :rating, :ruby_type], :name => "company_index"
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/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 0e5aa5af10..020fa1a06d 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -91,6 +91,17 @@ class DateTime
change(:hour => 23, :min => 59, :sec => 59)
end
+ # Returns a new DateTime representing the start of the hour (hh:00:00)
+ def beginning_of_hour
+ change(:min => 0)
+ end
+ alias :at_beginning_of_hour :beginning_of_hour
+
+ # Returns a new DateTime representing the end of the hour (hh:59:59)
+ def end_of_hour
+ change(:min => 59, :sec => 59)
+ end
+
# Adjusts DateTime to UTC by adding its offset value; offset is set to 0
#
# Example:
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/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 1434e186c3..a0f610d60c 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -257,6 +257,21 @@ class Time
)
end
+ # Returns a new Time representing the start of the hour (x:00)
+ def beginning_of_hour
+ change(:min => 0)
+ end
+ alias :at_beginning_of_hour :beginning_of_hour
+
+ # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9)
+ def end_of_hour
+ change(
+ :min => 59,
+ :sec => 59,
+ :usec => 999999.999
+ )
+ end
+
# Returns a new Time representing the start of the month (1st of the month, 0:00)
def beginning_of_month
#self - ((self.mday-1).days + self.seconds_since_midnight)
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/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index cd8cb5d18b..3da0825489 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -91,6 +91,14 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2005,2,4,23,59,59), DateTime.civil(2005,2,4,10,10,10).end_of_day
end
+ def test_beginning_of_hour
+ assert_equal DateTime.civil(2005,2,4,19,0,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_hour
+ end
+
+ def test_end_of_hour
+ assert_equal DateTime.civil(2005,2,4,19,59,59), DateTime.civil(2005,2,4,19,30,10).end_of_hour
+ end
+
def test_beginning_of_month
assert_equal DateTime.civil(2005,2,1,0,0,0), DateTime.civil(2005,2,22,10,10,10).beginning_of_month
end
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index c542acca68..4c1ed4b1ae 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -93,6 +93,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_beginning_of_hour
+ assert_equal Time.local(2005,2,4,19,0,0), Time.local(2005,2,4,19,30,10).beginning_of_hour
+ end
+
def test_beginning_of_month
assert_equal Time.local(2005,2,1,0,0,0), Time.local(2005,2,22,10,10,10).beginning_of_month
end
@@ -127,6 +131,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,02,0,0,0).end_of_week #sunday
end
+ def test_end_of_hour
+ assert_equal Time.local(2005,2,4,19,59,59,999999.999), Time.local(2005,2,4,19,30,10).end_of_hour
+ end
+
def test_end_of_month
assert_equal Time.local(2005,3,31,23,59,59,999999.999), Time.local(2005,3,20,10,10,10).end_of_month
assert_equal Time.local(2005,2,28,23,59,59,999999.999), Time.local(2005,2,20,10,10,10).end_of_month
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 00a03de875..b62337e31b 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -191,7 +191,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
end
- def future_with_time_current_as_time_with_zone
+ def test_future_with_time_current_as_time_with_zone
twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) )
Time.stubs(:current).returns(twz)
assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
@@ -483,36 +483,50 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.advance(:seconds => 30).inspect
end
- def beginning_of_year
+ def test_beginning_of_year
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 01 Jan 1999 00:00:00 EST -05:00", @twz.beginning_of_year.inspect
end
- def end_of_year
+ def test_end_of_year
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_year.inspect
end
- def beginning_of_month
+ def test_beginning_of_month
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
- assert_equal "Fri, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect
+ assert_equal "Wed, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect
end
- def end_of_month
+ def test_end_of_month
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_month.inspect
end
- def beginning_of_day
+ def test_beginning_of_day
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_day.inspect
end
- def end_of_day
+ def test_end_of_day
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_day.inspect
end
+ def test_beginning_of_hour
+ utc = Time.utc(2000, 1, 1, 0, 30)
+ twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
+ assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", twz.beginning_of_hour.inspect
+ end
+
+ def test_end_of_hour
+ utc = Time.utc(2000, 1, 1, 0, 30)
+ twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
+ assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect
+ assert_equal "Fri, 31 Dec 1999 19:59:59 EST -05:00", twz.end_of_hour.inspect
+ end
+
def test_since
assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect
end
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/assets/images/getting_started/post_with_comments.png b/guides/assets/images/getting_started/post_with_comments.png
new file mode 100644
index 0000000000..bd9b2e10f5
--- /dev/null
+++ b/guides/assets/images/getting_started/post_with_comments.png
Binary files differ
diff --git a/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb
index 7447fd078b..cf3d1be42e 100644
--- a/guides/code/getting_started/app/controllers/comments_controller.rb
+++ b/guides/code/getting_started/app/controllers/comments_controller.rb
@@ -1,16 +1,17 @@
class CommentsController < ApplicationController
http_basic_authenticate_with :name => "dhh", :password => "secret", :only => :destroy
+
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment])
redirect_to post_path(@post)
end
-
+
def destroy
@post = Post.find(params[:post_id])
@comment = @post.comments.find(params[:id])
@comment.destroy
redirect_to post_path(@post)
end
-
+
end
diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb
index 85d2c1de47..a8ac9aba5a 100644
--- a/guides/code/getting_started/app/controllers/posts_controller.rb
+++ b/guides/code/getting_started/app/controllers/posts_controller.rb
@@ -1,5 +1,7 @@
class PostsController < ApplicationController
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :except => [:index, :show]
+
def index
@posts = Post.all
end
diff --git a/guides/code/getting_started/app/views/comments/_comment.html.erb b/guides/code/getting_started/app/views/comments/_comment.html.erb
index 4c3fbf26cd..0cebe0bd96 100644
--- a/guides/code/getting_started/app/views/comments/_comment.html.erb
+++ b/guides/code/getting_started/app/views/comments/_comment.html.erb
@@ -1,13 +1,13 @@
<p>
- <b>Commenter:</b>
+ <strong>Commenter:</strong>
<%= comment.commenter %>
</p>
-
+
<p>
- <b>Comment:</b>
+ <strong>Comment:</strong>
<%= comment.body %>
</p>
-
+
<p>
<%= link_to 'Destroy Comment', [comment.post, comment],
:confirm => 'Are you sure?',
diff --git a/guides/code/getting_started/app/views/comments/_form.html.erb b/guides/code/getting_started/app/views/comments/_form.html.erb
index d15bdd6b59..00cb3a08f0 100644
--- a/guides/code/getting_started/app/views/comments/_form.html.erb
+++ b/guides/code/getting_started/app/views/comments/_form.html.erb
@@ -1,13 +1,13 @@
<%= 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 %>
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..65809033ed 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,11 @@
<%= @post.text %>
</p>
-<%= link_to 'Back', :action => :index %>
-| <%= link_to 'Edit', :action => :edit, :id => @post.id %>
+<h2>Comments</h2>
+<%= render @post.comments %>
+
+<h2>Add a comment:</h2>
+<%= render "comments/form" %>
+
+<%= 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/asset_pipeline.textile b/guides/source/asset_pipeline.textile
index d79eb01ab2..010154f1d1 100644
--- a/guides/source/asset_pipeline.textile
+++ b/guides/source/asset_pipeline.textile
@@ -204,6 +204,8 @@ Images can also be organized into subdirectories if required, and they can be ac
<%= image_tag "icons/rails.png" %>
</erb>
+WARNING: If you're precompiling your assets (see "In Production":#in-production below), linking to an asset that does not exist will raise an exception in the calling page. This includes linking to a blank string. As such, be careful using <tt>image_tag</tt> and the other helpers with user-supplied data.
+
h5. CSS and ERB
The asset pipeline automatically evaluates ERB. This means that if you add an +erb+ extension to a CSS asset (for example, +application.css.erb+), then helpers like +asset_path+ are available in your CSS rules:
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 95a01710b5..b656a0857a 100644
--- a/guides/source/command_line.textile
+++ b/guides/source/command_line.textile
@@ -12,7 +12,7 @@ endprologue.
NOTE: This tutorial assumes you have basic Rails knowledge from reading the "Getting Started with Rails Guide":getting_started.html.
-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. Command Line Basics
@@ -31,7 +31,7 @@ h4. +rails new+
The first thing we'll want to do is create a new Rails application by running the +rails new+ command after installing Rails.
-WARNING: You can install the rails gem by typing +gem install rails+, if you don't have it already. Follow the instructions in the "Rails 3 Release Notes":/3_0_release_notes.html
+TIP: You can install the rails gem by typing +gem install rails+, if you don't have it already.
<shell>
$ rails new commandsapp
@@ -185,8 +185,6 @@ $ rails server
=> Booting WEBrick...
</shell>
-WARNING: Make sure that you do not have any "tilde backup" files in +app/views/(controller)+, or else WEBrick will _not_ show the expected output. This seems to be a *bug* in Rails 2.3.0.
-
The URL will be "http://localhost:3000/greetings/hello":http://localhost:3000/greetings/hello.
INFO: With a normal, plain-old Rails application, your URLs will generally follow the pattern of http://(host)/(controller)/(action), and a URL like http://(host)/(controller) will hit the *index* action of that controller.
@@ -446,8 +444,7 @@ 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.
-Be default, rake notes will look in the app, config, lib, script and test directories for notes. If you would like to search additional directories,
-simply provide the directories as a comma seperated list in an environment variable +SOURCE_ANNOTATION_DIRECTORIES+.
+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'
diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile
index 68426221bf..5629c82ca0 100644
--- a/guides/source/configuring.textile
+++ b/guides/source/configuring.textile
@@ -70,12 +70,23 @@ 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.
* +config.consider_all_requests_local+ is a flag. If true then any error will cause detailed debugging information to be dumped in the HTTP response, and the +Rails::Info+ controller will show the application runtime context in +/rails/info/properties+. True by default in development and test environments, and false in production mode. For finer-grained control, set this to false and implement +local_request?+ in controllers to specify which requests should provide debugging information on errors.
+* +config.console+ allows you to set class that will be used as console you run +rails console+. It's best to run it in +console+ block:
+
+<ruby>
+console do
+ # this block is called only when running console,
+ # so we can safely require pry here
+ require "pry"
+ config.console = Pry
+end
+</ruby>
+
* +config.dependency_loading+ is a flag that allows you to disable constant autoloading setting it to false. It only has effect if +config.cache_classes+ is true, which it is by default in production mode. This flag is set to false by +config.threadsafe!+.
* +config.eager_load_paths+ accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. Defaults to every folder in the +app+ directory of the application.
@@ -100,6 +111,10 @@ NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is
* +config.preload_frameworks+ enables or disables preloading all frameworks at startup. Enabled by +config.threadsafe!+. Defaults to +nil+, so is disabled.
+* +config.queue+ configures a different queue implementation for the application. Defaults to +Rails::Queueing::Queue+. Note that, if the default queue is changed, the default +queue_consumer+ is not going to be initialized, it is up to the new queue implementation to handle starting and shutting down its own consumer(s).
+
+* +config.queue_consumer+ configures a different consumer implementation for the default queue. Defaults to +Rails::Queueing::ThreadedConsumer+.
+
* +config.reload_classes_only_on_change+ enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If +config.cache_classes+ is true, this option is ignored.
* +config.secret_token+ used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get +config.secret_token+ initialized to a random key in +config/initializers/secret_token.rb+.
@@ -122,17 +137,6 @@ WARNING: Threadsafe operation is incompatible with the normal workings of develo
* +config.whiny_nils+ enables or disables warnings when a certain set of methods are invoked on +nil+ and it does not respond to them. Defaults to true in development and test environments.
-* +config.console+ allows you to set class that will be used as console you run +rails console+. It's best to run it in +console+ block:
-
-<ruby>
-console do
- # this block is called only when running console,
- # so we can safely require pry here
- require "pry"
- config.console = Pry
-end
-</ruby>
-
h4. Configuring Assets
Rails 3.1, by default, is set up to use the +sprockets+ gem to manage assets within an application. This gem concatenates and compresses assets in order to make serving them much less painful.
@@ -248,14 +252,6 @@ They can also be removed from the stack completely:
config.middleware.delete ActionDispatch::BestStandardsSupport
</ruby>
-In addition to these methods to handle the stack, if your application is going to be used as an API endpoint only, the middleware stack can be configured like this:
-
-<ruby>
-config.middleware.http_only!
-</ruby>
-
-By doing this, Rails will create a smaller middleware stack, by not adding some middlewares that are usually useful for browser access only, such as Cookies, Session and Flash, BestStandardsSupport, and MethodOverride. You can always add any of them later manually if you want. Refer to the "API App docs":api_app.html for more info on how to setup your application for API only apps.
-
h4. Configuring i18n
* +config.i18n.default_locale+ sets the default locale of an application used for i18n. Defaults to +:en+.
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..947abd7ba0 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.
@@ -86,7 +87,10 @@ To install Rails, use the +gem install+ command provided by RubyGems:
# gem install rails
</shell>
-TIP. If you're working on Windows, you can quickly install Ruby and Rails with "Rails Installer":http://railsinstaller.org.
+TIP. A number of tools exist to help you quickly install Ruby and Ruby
+on Rails on your system. Windows users can use "Rails
+Installer":http://railsinstaller.org, while Mac OS X users can use
+"Rails One Click":http://railsoneclick.com.
To verify that you have everything installed correctly, you should be able to run the following:
@@ -108,7 +112,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 +121,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 +393,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
@@ -397,7 +404,10 @@ $ rails generate model Post title:string text:text
With that command we told Rails that we want a +Post+ model, which in
turn should have a title attribute of type string, and a text attribute
-of type text. Rails in turn responded by creating a bunch of files. For
+of type text. Those attributes are automatically added to the +posts+
+table in the database and mapped to the +Post+ model.
+
+Rails in turn responded by creating a bunch of files. For
now, we're only interested in +app/models/post.rb+ and
+db/migrate/20120419084633_create_posts.rb+. The latter is responsible
for creating the database structure, which is what we'll look at next.
@@ -457,7 +467,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 +1105,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>
+h4. Going Deeper into REST
-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. 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.
-
-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
+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.
- 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 +1251,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 +1269,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 +1287,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 +1314,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
@@ -1732,60 +1373,53 @@ template. This is where we want the comment to show, so let's add that to the
+app/views/posts/show.html.erb+.
<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>Comments</h2>
<% @post.comments.each do |comment| %>
<p>
- <b>Commenter:</b>
+ <strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
- <b>Comment:</b>
+ <strong>Comment:</strong>
<%= comment.body %>
</p>
<% end %>
<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 %>
-<br />
-
<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
+<%= link_to 'Back to Posts', posts_path %>
</erb>
Now you can add posts and comments to your blog and have them show up in the
right places.
+!images/getting_started/post_with_comments.png(Post with Comments)!
+
h3. Refactoring
Now that we have posts and comments working, take a look at the
@@ -1800,12 +1434,12 @@ following into it:
<erb>
<p>
- <b>Commenter:</b>
+ <strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
- <b>Comment:</b>
+ <strong>Comment:</strong>
<%= comment.body %>
</p>
</erb>
@@ -1814,21 +1448,14 @@ Then you can change +app/views/posts/show.html.erb+ to look like the
following:
<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>Comments</h2>
@@ -1836,23 +1463,21 @@ following:
<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 %>
-<br />
-
<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
+<%= link_to 'Back to Posts', posts_path %>
</erb>
This will now render the partial in +app/views/comments/_comment.html.erb+ once
@@ -1868,50 +1493,38 @@ create a file +app/views/comments/_form.html.erb+ containing:
<erb>
<%= 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 %>
</erb>
Then you make the +app/views/posts/show.html.erb+ look like the following:
<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>Comments</h2>
-<%= render @post.comments %>
-
<h2>Add a comment:</h2>
<%= render "comments/form" %>
-<br />
-
<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
+<%= link_to 'Back to Posts', posts_path %>
</erb>
The second render just defines the partial template we want to render,
@@ -1933,12 +1546,12 @@ So first, let's add the delete link in the
<erb>
<p>
- <b>Commenter:</b>
+ <strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
- <b>Comment:</b>
+ <strong>Comment:</strong>
<%= comment.body %>
</p>
@@ -1987,7 +1600,6 @@ model, +app/models/post.rb+, as follows:
<ruby>
class Post < ActiveRecord::Base
- validates :name, :presence => true
validates :title, :presence => true,
:length => { :minimum => 5 }
has_many :comments, :dependent => :destroy
@@ -2016,11 +1628,8 @@ class PostsController < ApplicationController
http_basic_authenticate_with :name => "dhh", :password => "secret", :except => [:index, :show]
- # GET /posts
- # GET /posts.json
def index
@posts = Post.all
- respond_to do |format|
# snipped for brevity
</ruby>
@@ -2042,213 +1651,6 @@ Authentication challenge
!images/challenge.png(Basic HTTP Authentication Challenge)!
-h3. Building a Multi-Model Form
-
-Another feature of your average blog is the ability to tag posts. To implement
-this feature your application needs to interact with more than one model on a
-single form. Rails offers support for nested forms.
-
-To demonstrate this, we will add support for giving each post multiple tags,
-right in the form where you create the post. First, create a new model to hold
-the tags:
-
-<shell>
-$ rails generate model Tag name:string post:references
-</shell>
-
-Again, run the migration to create the database table:
-
-<shell>
-$ rake db:migrate
-</shell>
-
-Next, edit the +post.rb+ file to create the other side of the association, and
-to tell Rails (via the +accepts_nested_attributes_for+ macro) that you intend to
-edit tags via posts:
-
-<ruby>
-class Post < ActiveRecord::Base
- validates :name, :presence => true
- validates :title, :presence => true,
- :length => { :minimum => 5 }
-
- has_many :comments, :dependent => :destroy
- has_many :tags
-
- accepts_nested_attributes_for :tags, :allow_destroy => :true,
- :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
-end
-</ruby>
-
-The +:allow_destroy+ option tells Rails to enable destroying tags through the
-nested attributes (you'll handle that by displaying a "remove" checkbox on the
-view that you'll build shortly). The +:reject_if+ option prevents saving new
-tags that do not have any attributes filled in.
-
-We will modify +views/posts/_form.html.erb+ to render a partial to make a tag:
-
-<erb>
-<% @post.tags.build %>
-<%= form_for(@post) do |post_form| %>
- <% 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">
- <%= post_form.label :name %><br />
- <%= post_form.text_field :name %>
- </div>
- <div class="field">
- <%= post_form.label :title %><br />
- <%= post_form.text_field :title %>
- </div>
- <div class="field">
- <%= post_form.label :content %><br />
- <%= post_form.text_area :content %>
- </div>
- <h2>Tags</h2>
- <%= render :partial => 'tags/form',
- :locals => {:form => post_form} %>
- <div class="actions">
- <%= post_form.submit %>
- </div>
-<% end %>
-</erb>
-
-Note that we have changed the +f+ in +form_for(@post) do |f|+ to +post_form+ to
-make it easier to understand what is going on.
-
-This example shows another option of the render helper, being able to pass in
-local variables, in this case, we want the local variable +form+ in the partial
-to refer to the +post_form+ object.
-
-We also add a <tt>@post.tags.build</tt> at the top of this form. This is to make
-sure there is a new tag ready to have its name filled in by the user. If you do
-not build the new tag, then the form will not appear as there is no new Tag
-object ready to create.
-
-Now create the folder <tt>app/views/tags</tt> and make a file in there called
-<tt>_form.html.erb</tt> which contains the form for the tag:
-
-<erb>
-<%= form.fields_for :tags do |tag_form| %>
- <div class="field">
- <%= tag_form.label :name, 'Tag:' %>
- <%= tag_form.text_field :name %>
- </div>
- <% unless tag_form.object.nil? || tag_form.object.new_record? %>
- <div class="field">
- <%= tag_form.label :_destroy, 'Remove:' %>
- <%= tag_form.check_box :_destroy %>
- </div>
- <% end %>
-<% end %>
-</erb>
-
-Finally, we will edit the <tt>app/views/posts/show.html.erb</tt> template to
-show our tags.
-
-<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>
-
-<p>
- <b>Tags:</b>
- <%= @post.tags.map { |t| t.name }.join(", ") %>
-</p>
-
-<h2>Comments</h2>
-<%= render @post.comments %>
-
-<h2>Add a comment:</h2>
-<%= render "comments/form" %>
-
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
-</erb>
-
-With these changes in place, you'll find that you can edit a post and its tags
-directly on the same view.
-
-However, that method call <tt>@post.tags.map { |t| t.name }.join(", ")</tt> is
-awkward, we could handle this by making a helper method.
-
-h3. View Helpers
-
-View Helpers live in <tt>app/helpers</tt> and provide small snippets of reusable
-code for views. In our case, we want a method that strings a bunch of objects
-together using their name attribute and joining them with a comma. As this is
-for the Post show template, we put it in the PostsHelper.
-
-Open up <tt>app/helpers/posts_helper.rb</tt> and add the following:
-
-<erb>
-module PostsHelper
- def join_tags(post)
- post.tags.map { |t| t.name }.join(", ")
- end
-end
-</erb>
-
-Now you can edit the view in <tt>app/views/posts/show.html.erb</tt> to look like
-this:
-
-<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>
-
-<p>
- <b>Tags:</b>
- <%= join_tags(@post) %>
-</p>
-
-<h2>Comments</h2>
-<%= render @post.comments %>
-
-<h2>Add a comment:</h2>
-<%= render "comments/form" %>
-
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
-</erb>
-
h3. What's Next?
Now that you've seen your first Rails application, you should feel free to
diff --git a/guides/source/layouts_and_rendering.textile b/guides/source/layouts_and_rendering.textile
index f69afaa281..e4a1fd6951 100644
--- a/guides/source/layouts_and_rendering.textile
+++ b/guides/source/layouts_and_rendering.textile
@@ -78,16 +78,16 @@ If we want to display the properties of all the books in our view, we can do so
<tr>
<td><%= book.title %></td>
<td><%= book.content %></td>
- <td><%= link_to 'Show', book %></td>
- <td><%= link_to 'Edit', edit_book_path(book) %></td>
- <td><%= link_to 'Remove', book, :confirm => 'Are you sure?', :method => :delete %></td>
+ <td><%= link_to "Show", book %></td>
+ <td><%= link_to "Edit", edit_book_path(book) %></td>
+ <td><%= link_to "Remove", book, :confirm => "Are you sure?", :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
-<%= link_to 'New book', new_book_path %>
+<%= link_to "New book", new_book_path %>
</ruby>
NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. Beginning with Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), and +.builder+ for Builder (XML generator).
@@ -177,13 +177,13 @@ h5. Rendering an Action's Template from Another Controller
What if you want to render a template from an entirely different controller from the one that contains the action code? You can also do that with +render+, which accepts the full path (relative to +app/views+) of the template to render. For example, if you're running code in an +AdminProductsController+ that lives in +app/controllers/admin+, you can render the results of an action to a template in +app/views/products+ this way:
<ruby>
-render 'products/show'
+render "products/show"
</ruby>
Rails knows that this view belongs to a different controller because of the embedded slash character in the string. If you want to be explicit, you can use the +:template+ option (which was required on Rails 2.2 and earlier):
<ruby>
-render :template => 'products/show'
+render :template => "products/show"
</ruby>
h5. Rendering an Arbitrary File
@@ -216,18 +216,18 @@ In fact, in the BooksController class, inside of the update action where we want
<ruby>
render :edit
render :action => :edit
-render 'edit'
-render 'edit.html.erb'
-render :action => 'edit'
-render :action => 'edit.html.erb'
-render 'books/edit'
-render 'books/edit.html.erb'
-render :template => 'books/edit'
-render :template => 'books/edit.html.erb'
-render '/path/to/rails/app/views/books/edit'
-render '/path/to/rails/app/views/books/edit.html.erb'
-render :file => '/path/to/rails/app/views/books/edit'
-render :file => '/path/to/rails/app/views/books/edit.html.erb'
+render "edit"
+render "edit.html.erb"
+render :action => "edit"
+render :action => "edit.html.erb"
+render "books/edit"
+render "books/edit.html.erb"
+render :template => "books/edit"
+render :template => "books/edit.html.erb"
+render "/path/to/rails/app/views/books/edit"
+render "/path/to/rails/app/views/books/edit.html.erb"
+render :file => "/path/to/rails/app/views/books/edit"
+render :file => "/path/to/rails/app/views/books/edit.html.erb"
</ruby>
Which one you use is really a matter of style and convention, but the rule of thumb is to use the simplest one that makes sense for the code you are writing.
@@ -306,7 +306,7 @@ h6. The +:content_type+ Option
By default, Rails will serve the results of a rendering operation with the MIME content-type of +text/html+ (or +application/json+ if you use the +:json+ option, or +application/xml+ for the +:xml+ option.). There are times when you might like to change this, and you can do so by setting the +:content_type+ option:
<ruby>
-render :file => filename, :content_type => 'application/rss'
+render :file => filename, :content_type => "application/rss"
</ruby>
h6. The +:layout+ Option
@@ -316,7 +316,7 @@ With most of the options to +render+, the rendered content is displayed as part
You can use the +:layout+ option to tell Rails to use a specific file as the layout for the current action:
<ruby>
-render :layout => 'special_layout'
+render :layout => "special_layout"
</ruby>
You can also tell Rails to render with no layout at all:
@@ -378,7 +378,7 @@ You can use a symbol to defer the choice of layout until a request is processed:
<ruby>
class ProductsController < ApplicationController
- layout :products_layout
+ layout "products_layout"
def show
@product = Product.find(params[:id])
@@ -398,7 +398,7 @@ You can even use an inline method, such as a Proc, to determine the layout. For
<ruby>
class ProductsController < ApplicationController
- layout Proc.new { |controller| controller.request.xhr? ? 'popup' : 'application' }
+ layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" }
end
</ruby>
@@ -445,7 +445,7 @@ end
<ruby>
class OldPostsController < SpecialPostsController
- layout nil
+ layout false
def show
@post = Post.find(params[:id])
@@ -583,7 +583,7 @@ def show
@book = Book.find_by_id(params[:id])
if @book.nil?
@books = Book.all
- render "index", :alert => 'Your book was not found!'
+ render "index", :alert => "Your book was not found!"
end
end
</ruby>
@@ -770,7 +770,7 @@ By default, the combined file will be delivered as +javascripts/all.js+. You can
<erb>
<%= javascript_include_tag "main", "columns",
- :cache => 'cache/main/display' %>
+ :cache => "cache/main/display" %>
</erb>
You can even use dynamic paths such as +cache/#{current_site}/main/display+.
@@ -833,7 +833,7 @@ By default, the combined file will be delivered as +stylesheets/all.css+. You ca
<erb>
<%= stylesheet_link_tag "main", "columns",
- :cache => 'cache/main/display' %>
+ :cache => "cache/main/display" %>
</erb>
You can even use dynamic paths such as +cache/#{current_site}/main/display+.
@@ -884,7 +884,7 @@ In addition to the above special tags, you can supply a final hash of standard H
<erb>
<%= image_tag "home.gif", :alt => "Go Home",
:id => "HomeImage",
- :class => 'nav_bar' %>
+ :class => "nav_bar" %>
</erb>
h5. Linking to Videos with the +video_tag+
@@ -905,7 +905,7 @@ Like an +image_tag+ you can supply a path, either absolute, or relative to the +
The video tag also supports all of the +&lt;video&gt;+ HTML options through the HTML options hash, including:
-* +:poster => 'image_name.png'+, provides an image to put in place of the video before it starts playing.
+* +:poster => "image_name.png"+, provides an image to put in place of the video before it starts playing.
* +:autoplay => true+, starts playing the video on page load.
* +:loop => true+, loops the video once it gets to the end.
* +:controls => true+, provides browser supplied controls for the user to interact with the video.
@@ -1159,7 +1159,7 @@ In the event that the collection is empty, +render+ will return nil, so it shoul
<erb>
<h1>Products</h1>
-<%= render(@products) || 'There are no products available.' %>
+<%= render(@products) || "There are no products available." %>
</erb>
h5. Local Variables
@@ -1175,7 +1175,7 @@ With this change, you can access an instance of the +@products+ collection as th
You can also pass in arbitrary local variables to any partial you are rendering with the +:locals => {}+ option:
<erb>
-<%= render :partial => 'products', :collection => @products,
+<%= render :partial => "products", :collection => @products,
:as => :item, :locals => {:title => "Products Page"} %>
</erb>
@@ -1214,8 +1214,8 @@ Suppose you have the following +ApplicationController+ layout:
<erb>
<html>
<head>
- <title><%= @page_title or 'Page Title' %></title>
- <%= stylesheet_link_tag 'layout' %>
+ <title><%= @page_title or "Page Title" %></title>
+ <%= stylesheet_link_tag "layout" %>
<style><%= yield :stylesheets %></style>
</head>
<body>
@@ -1239,7 +1239,7 @@ On pages generated by +NewsController+, you want to hide the top menu and add a
<div id="right_menu">Right menu items here</div>
<%= content_for?(:news_content) ? yield(:news_content) : yield %>
<% end %>
-<%= render :template => 'layouts/application' %>
+<%= render :template => "layouts/application" %>
</erb>
That's it. The News views will use the new layout, hiding the top menu and adding a new right menu inside the "content" div.
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/security.textile b/guides/source/security.textile
index c065529cac..ac64b82bf6 100644
--- a/guides/source/security.textile
+++ b/guides/source/security.textile
@@ -1,7 +1,6 @@
h2. Ruby On Rails Security Guide
-This manual describes common security problems in web applications and how to avoid them with Rails. If you have any questions or suggestions, please
-mail me, Heiko Webers, at 42 {_et_} rorsecurity.info. After reading it, you should be familiar with:
+This manual describes common security problems in web applications and how to avoid them with Rails. After reading it, you should be familiar with:
* All countermeasures _(highlight)that are highlighted_
* The concept of sessions in Rails, what to put in there and popular attack methods
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/CHANGELOG.md b/railties/CHANGELOG.md
index bc34ced283..b7c042cee3 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,9 @@
## Rails 4.0.0 (unreleased) ##
+* Add `config.queue_consumer` to allow the default consumer to be configurable. *Carlos Antonio da Silva*
+
+* Add Rails.queue as an interface with a default implementation that consumes jobs in a separate thread. *Yehuda Katz*
+
* Remove Rack::SSL in favour of ActionDispatch::SSL. *Rafael Mendonça França*
* Remove Active Resource from Rails framework. *Prem Sichangrist*
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/application.rb b/railties/lib/rails/application.rb
index c7b19c964a..c4edbae55b 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -66,7 +66,7 @@ module Rails
end
end
- attr_accessor :assets, :sandbox
+ attr_accessor :assets, :sandbox, :queue_consumer
alias_method :sandbox?, :sandbox
attr_reader :reloaders
attr_writer :queue
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 25bb680f69..a2e5dece16 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -8,10 +8,11 @@ module Rails
attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets, :autoflush_log,
:cache_classes, :cache_store, :consider_all_requests_local, :console,
:dependency_loading, :exceptions_app, :file_watcher, :filter_parameters,
- :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, :preload_frameworks,
- :railties_order, :relative_url_root, :secret_token,
+ :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
+ :preload_frameworks, :railties_order, :relative_url_root, :secret_token,
:serve_static_assets, :ssl_options, :static_cache_control, :session_options,
- :time_zone, :reload_classes_only_on_change, :use_schema_cache_dump, :queue
+ :time_zone, :reload_classes_only_on_change, :use_schema_cache_dump,
+ :queue, :queue_consumer
attr_writer :log_level
attr_reader :encoding
@@ -44,6 +45,7 @@ module Rails
@log_formatter = ActiveSupport::Logger::SimpleFormatter.new
@use_schema_cache_dump = true
@queue = Rails::Queueing::Queue
+ @queue_consumer = Rails::Queueing::ThreadedConsumer
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 6a24e01f29..84f2601f28 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -96,8 +96,8 @@ module Rails
initializer :activate_queue_consumer do |app|
if config.queue == Rails::Queueing::Queue
- consumer = Rails::Queueing::ThreadedConsumer.start(app.queue)
- at_exit { consumer.shutdown }
+ app.queue_consumer = config.queue_consumer.start(app.queue)
+ at_exit { app.queue_consumer.shutdown }
end
end
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/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index b71119af77..ffbc0b4bd6 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -34,6 +34,10 @@ The common rails commands available for engines are:
destroy Undo code generated with "generate" (short-cut alias: "d")
All commands can be run with -h for more information.
+
+If you want to run any commands that need to be run in context
+of the application, like `rails server` or `rails console`,
+you should do it from application's directory (typically test/dummy).
EOT
exit(1)
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/queueing.rb b/railties/lib/rails/queueing.rb
index 2e187b8555..b4bc7fcd18 100644
--- a/railties/lib/rails/queueing.rb
+++ b/railties/lib/rails/queueing.rb
@@ -16,13 +16,13 @@ module Rails
# Jobs are run in a separate thread to catch mistakes where code
# assumes that the job is run in the same thread.
class TestQueue < ::Queue
- # Get a list of the jobs off this queue. This method may not be
+ # Get a list of the jobs off this queue. This method may not be
# available on production queues.
def jobs
@que.dup
end
- # Drain the queue, running all jobs in a different thread. This method
+ # Drain the queue, running all jobs in a different thread. This method
# may not be available on production queues.
def drain
# run the jobs in a separate thread so assumptions of synchronous
@@ -53,7 +53,7 @@ module Rails
begin
job.run
rescue Exception => e
- Rails.logger.error "Job Error: #{e.message}\n#{e.backtrace.join("\n")}"
+ handle_exception e
end
end
end
@@ -64,6 +64,10 @@ module Rails
@queue.push nil
@thread.join
end
+
+ def handle_exception(e)
+ Rails.logger.error "Job Error: #{e.message}\n#{e.backtrace.join("\n")}"
+ end
end
end
end
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/test/application/queue_test.rb b/railties/test/application/queue_test.rb
index ec809d7cc1..da8bdeed52 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
@@ -65,7 +63,7 @@ module ApplicationTests
test "in test mode, the queue can be observed" do
app("test")
- job = Class.new(Struct.new(:id)) do
+ job = Struct.new(:id) do
def run
end
end
@@ -81,7 +79,7 @@ module ApplicationTests
assert_equal jobs, Rails.queue.jobs
end
- test "a custom queue implementation can be provided" do
+ def setup_custom_queue
add_to_env_config "production", <<-RUBY
require "my_queue"
config.queue = MyQueue
@@ -96,10 +94,14 @@ module ApplicationTests
RUBY
app("production")
+ end
+
+ test "a custom queue implementation can be provided" do
+ setup_custom_queue
assert_kind_of MyQueue, Rails.queue
- job = Class.new(Struct.new(:id, :ran)) do
+ job = Struct.new(:id, :ran) do
def run
self.ran = true
end
@@ -110,5 +112,34 @@ module ApplicationTests
assert_equal true, job1.ran
end
+
+ test "a custom consumer implementation can be provided" do
+ add_to_env_config "production", <<-RUBY
+ require "my_queue_consumer"
+ config.queue_consumer = MyQueueConsumer
+ RUBY
+
+ app_file "lib/my_queue_consumer.rb", <<-RUBY
+ class MyQueueConsumer < Rails::Queueing::ThreadedConsumer
+ attr_reader :started
+
+ def start
+ @started = true
+ self
+ end
+ end
+ RUBY
+
+ app("production")
+
+ assert_kind_of MyQueueConsumer, Rails.application.queue_consumer
+ assert Rails.application.queue_consumer.started
+ end
+
+ test "default consumer is not used with custom queue implementation" do
+ setup_custom_queue
+
+ assert_nil Rails.application.queue_consumer
+ end
end
end
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/queueing/threaded_consumer_test.rb b/railties/test/queueing/threaded_consumer_test.rb
index 559de2a82d..c34a966d6e 100644
--- a/railties/test/queueing/threaded_consumer_test.rb
+++ b/railties/test/queueing/threaded_consumer_test.rb
@@ -78,4 +78,23 @@ class TestThreadConsumer < ActiveSupport::TestCase
assert_equal 1, logger.logged(:error).size
assert_match(/Job Error: RuntimeError: Error!/, logger.logged(:error).last)
end
+
+ test "test overriding exception handling" do
+ @consumer.shutdown
+ @consumer = Class.new(Rails::Queueing::ThreadedConsumer) do
+ attr_reader :last_error
+ def handle_exception(e)
+ @last_error = e.message
+ end
+ end.start(@queue)
+
+ job = Job.new(1) do
+ raise "RuntimeError: Error!"
+ end
+
+ @queue.push job
+ sleep 0.1
+
+ assert_equal "RuntimeError: Error!", @consumer.last_error
+ end
end
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'