" +
+ ("
").html_safe
end
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index a3e2230f6f..187e010e30 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -141,14 +141,6 @@
minute: "Minute"
second: "Seconds"
- errors:
- template:
- header:
- one: "1 error prohibited this {{model}} from being saved"
- other: "{{count}} errors prohibited this {{model}} from being saved"
- # The variable :count is also available
- body: "There were problems with the following fields:"
-
helpers:
select:
# Default value for :prompt => true in FormOptionsHelper
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 9b59aac0eb..6e61d85dcc 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -38,7 +38,7 @@ module ActionView
end
register_detail(:formats) { Mime::SET.symbols }
- register_detail(:locale) { [I18n.locale] }
+ register_detail(:locale) { [I18n.locale, I18n.default_locale] }
class DetailsKey #:nodoc:
alias :eql? :equal?
@@ -160,7 +160,7 @@ module ActionView
config = I18n.config.respond_to?(:i18n_config) ? I18n.config.i18n_config : I18n.config
config.locale = value
end
- super(I18n.locale)
+ super(_locale_defaults)
end
# Update the details keys by merging the given hash into the current
diff --git a/actionpack/lib/action_view/render/layouts.rb b/actionpack/lib/action_view/render/layouts.rb
index 7311730a19..31b09d9f0a 100644
--- a/actionpack/lib/action_view/render/layouts.rb
+++ b/actionpack/lib/action_view/render/layouts.rb
@@ -48,7 +48,7 @@ module ActionView
#
def _layout_for(name = nil, &block) #:nodoc:
if !block || name
- @_content_for[name || :layout]
+ @_content_for[name || :layout].html_safe
else
capture(&block)
end
diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb
new file mode 100644
index 0000000000..41ff2f3809
--- /dev/null
+++ b/actionpack/test/controller/localized_templates_test.rb
@@ -0,0 +1,22 @@
+require 'abstract_unit'
+
+class LocalizedController < ActionController::Base
+ def hello_world
+ end
+end
+
+class LocalizedTemplatesTest < ActionController::TestCase
+ tests LocalizedController
+
+ def test_localized_template_is_used
+ I18n.locale = :de
+ get :hello_world
+ assert_equal "Gutten Tag", @response.body
+ end
+
+ def test_default_locale_template_is_used_when_locale_is_missing
+ I18n.locale = :dk
+ get :hello_world
+ assert_equal "Hello World", @response.body
+ end
+end
\ No newline at end of file
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index 8910454b8b..6f1ce2fef7 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -189,6 +189,12 @@ XML
assert_equal Hash.new, @request.session.to_hash
end
+ def test_response_and_request_have_nice_accessors
+ process :no_op
+ assert_equal @response, response
+ assert_equal @request, request
+ end
+
def test_process_with_request_uri_with_no_params
process :test_uri
assert_equal "/test_test/test/test_uri", @response.body
diff --git a/actionpack/test/controller/verification_test.rb b/actionpack/test/controller/verification_test.rb
deleted file mode 100644
index 0600ec2ec1..0000000000
--- a/actionpack/test/controller/verification_test.rb
+++ /dev/null
@@ -1,270 +0,0 @@
-require 'abstract_unit'
-
-class VerificationTest < ActionController::TestCase
- class TestController < ActionController::Base
- verify :only => :guarded_one, :params => "one",
- :add_flash => { :error => 'unguarded' },
- :redirect_to => { :action => "unguarded" }
-
- verify :only => :guarded_two, :params => %w( one two ),
- :redirect_to => { :action => "unguarded" }
-
- verify :only => :guarded_with_flash, :params => "one",
- :add_flash => { :notice => "prereqs failed" },
- :redirect_to => { :action => "unguarded" }
-
- verify :only => :guarded_in_session, :session => "one",
- :redirect_to => { :action => "unguarded" }
-
- verify :only => [:multi_one, :multi_two], :session => %w( one two ),
- :redirect_to => { :action => "unguarded" }
-
- verify :only => :guarded_by_method, :method => :post,
- :redirect_to => { :action => "unguarded" }
-
- verify :only => :guarded_by_xhr, :xhr => true,
- :redirect_to => { :action => "unguarded" }
-
- verify :only => :guarded_by_not_xhr, :xhr => false,
- :redirect_to => { :action => "unguarded" }
-
- before_filter :unconditional_redirect, :only => :two_redirects
- verify :only => :two_redirects, :method => :post,
- :redirect_to => { :action => "unguarded" }
-
- verify :only => :must_be_post, :method => :post, :render => { :status => 405, :text => "Must be post" }, :add_headers => { "Allow" => "POST" }
-
- verify :only => :guarded_one_for_named_route_test, :params => "one",
- :redirect_to => :foo_url
-
- verify :only => :no_default_action, :params => "santa"
-
- verify :only => :guarded_with_back, :method => :post,
- :redirect_to => :back
-
- def guarded_one
- render :text => "#{params[:one]}"
- end
-
- def guarded_one_for_named_route_test
- render :text => "#{params[:one]}"
- end
-
- def guarded_with_flash
- render :text => "#{params[:one]}"
- end
-
- def guarded_two
- render :text => "#{params[:one]}:#{params[:two]}"
- end
-
- def guarded_in_session
- render :text => "#{session["one"]}"
- end
-
- def multi_one
- render :text => "#{session["one"]}:#{session["two"]}"
- end
-
- def multi_two
- render :text => "#{session["two"]}:#{session["one"]}"
- end
-
- def guarded_by_method
- render :text => "#{request.method.downcase}"
- end
-
- def guarded_by_xhr
- render :text => "#{request.xhr?}"
- end
-
- def guarded_by_not_xhr
- render :text => "#{request.xhr?}"
- end
-
- def unguarded
- render :text => "#{params[:one]}"
- end
-
- def two_redirects
- render :nothing => true
- end
-
- def must_be_post
- render :text => "Was a post!"
- end
-
- def guarded_with_back
- render :text => "#{params[:one]}"
- end
-
- def no_default_action
- # Will never run
- end
-
- protected
-
- def unconditional_redirect
- redirect_to :action => "unguarded"
- end
- end
-
- tests TestController
-
- def test_using_symbol_back_with_no_referrer
- assert_raise(ActionController::RedirectBackError) { get :guarded_with_back }
- end
-
- def test_using_symbol_back_redirects_to_referrer
- @request.env["HTTP_REFERER"] = "/foo"
- get :guarded_with_back
- assert_redirected_to '/foo'
- end
-
- def test_no_deprecation_warning_for_named_route
- assert_not_deprecated do
- with_routing do |set|
- set.draw do |map|
- match 'foo', :to => 'test#foo', :as => :foo
- match 'verification_test/:action', :to => ::VerificationTest::TestController
- end
- get :guarded_one_for_named_route_test, :two => "not one"
- assert_redirected_to '/foo'
- end
- end
- end
-
- def test_guarded_one_with_prereqs
- get :guarded_one, :one => "here"
- assert_equal "here", @response.body
- end
-
- def test_guarded_one_without_prereqs
- get :guarded_one
- assert_redirected_to :action => "unguarded"
- assert_equal 'unguarded', flash[:error]
- end
-
- def test_guarded_with_flash_with_prereqs
- get :guarded_with_flash, :one => "here"
- assert_equal "here", @response.body
- assert flash.empty?
- end
-
- def test_guarded_with_flash_without_prereqs
- get :guarded_with_flash
- assert_redirected_to :action => "unguarded"
- assert_equal "prereqs failed", flash[:notice]
- end
-
- def test_guarded_two_with_prereqs
- get :guarded_two, :one => "here", :two => "there"
- assert_equal "here:there", @response.body
- end
-
- def test_guarded_two_without_prereqs_one
- get :guarded_two, :two => "there"
- assert_redirected_to :action => "unguarded"
- end
-
- def test_guarded_two_without_prereqs_two
- get :guarded_two, :one => "here"
- assert_redirected_to :action => "unguarded"
- end
-
- def test_guarded_two_without_prereqs_both
- get :guarded_two
- assert_redirected_to :action => "unguarded"
- end
-
- def test_unguarded_with_params
- get :unguarded, :one => "here"
- assert_equal "here", @response.body
- end
-
- def test_unguarded_without_params
- get :unguarded
- assert @response.body.blank?
- end
-
- def test_guarded_in_session_with_prereqs
- get :guarded_in_session, {}, "one" => "here"
- assert_equal "here", @response.body
- end
-
- def test_guarded_in_session_without_prereqs
- get :guarded_in_session
- assert_redirected_to :action => "unguarded"
- end
-
- def test_multi_one_with_prereqs
- get :multi_one, {}, "one" => "here", "two" => "there"
- assert_equal "here:there", @response.body
- end
-
- def test_multi_one_without_prereqs
- get :multi_one
- assert_redirected_to :action => "unguarded"
- end
-
- def test_multi_two_with_prereqs
- get :multi_two, {}, "one" => "here", "two" => "there"
- assert_equal "there:here", @response.body
- end
-
- def test_multi_two_without_prereqs
- get :multi_two
- assert_redirected_to :action => "unguarded"
- end
-
- def test_guarded_by_method_with_prereqs
- post :guarded_by_method
- assert_equal "post", @response.body
- end
-
- def test_guarded_by_method_without_prereqs
- get :guarded_by_method
- assert_redirected_to :action => "unguarded"
- end
-
- def test_guarded_by_xhr_with_prereqs
- xhr :post, :guarded_by_xhr
- assert_equal "true", @response.body
- end
-
- def test_guarded_by_xhr_without_prereqs
- get :guarded_by_xhr
- assert_redirected_to :action => "unguarded"
- end
-
- def test_guarded_by_not_xhr_with_prereqs
- get :guarded_by_not_xhr
- assert_equal "false", @response.body
- end
-
- def test_guarded_by_not_xhr_without_prereqs
- xhr :post, :guarded_by_not_xhr
- assert_redirected_to :action => "unguarded"
- end
-
- def test_guarded_post_and_calls_render_succeeds
- post :must_be_post
- assert_equal "Was a post!", @response.body
- end
-
- def test_default_failure_should_be_a_bad_request
- post :no_default_action
- assert_response :bad_request
- end
-
- def test_guarded_post_and_calls_render_fails_and_sets_allow_header
- get :must_be_post
- assert_response 405
- assert_equal "Must be post", @response.body
- assert_equal "POST", @response.headers["Allow"]
- end
-
- def test_second_redirect
- assert_nothing_raised { get :two_redirects }
- end
-end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 6ff478aec1..d38c48bfd4 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -186,6 +186,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
+
+ scope :module => 'api' do
+ resource :token
+ end
+
+ scope :path => 'api' do
+ resource :me
+ match '/' => 'mes#index'
+ end
end
end
@@ -942,6 +951,25 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_module_scope
+ with_test_routes do
+ get '/token'
+ assert_equal 'api/tokens#show', @response.body
+ assert_equal '/token', token_path
+ end
+ end
+
+ def test_path_scope
+ with_test_routes do
+ get '/api/me'
+ assert_equal 'mes#show', @response.body
+ assert_equal '/api/me', me_path
+
+ get '/api'
+ assert_equal 'mes#index', @response.body
+ end
+ end
+
private
def with_test_routes
yield
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 97da680f17..b447b0715c 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -106,4 +106,13 @@ class ShowExceptionsTest < ActionController::IntegrationTest
assert_response 405
assert_match /ActionController::MethodNotAllowed/, body
end
+
+ test "does not show filtered parameters" do
+ @app = DevelopmentApp
+
+ get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.parameter_filter' => [:foo]}
+ assert_response 500
+ assert_match ""foo"=>"[FILTERED]"", body
+ end
end
diff --git a/actionpack/test/fixtures/localized/hello_world.de.html b/actionpack/test/fixtures/localized/hello_world.de.html
new file mode 100644
index 0000000000..4727d7a7e0
--- /dev/null
+++ b/actionpack/test/fixtures/localized/hello_world.de.html
@@ -0,0 +1 @@
+Gutten Tag
\ No newline at end of file
diff --git a/actionpack/test/fixtures/localized/hello_world.en.html b/actionpack/test/fixtures/localized/hello_world.en.html
new file mode 100644
index 0000000000..5e1c309dae
--- /dev/null
+++ b/actionpack/test/fixtures/localized/hello_world.en.html
@@ -0,0 +1 @@
+Hello World
\ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_utf8_partial.html.erb b/actionpack/test/fixtures/test/_utf8_partial.html.erb
new file mode 100644
index 0000000000..8d717fd427
--- /dev/null
+++ b/actionpack/test/fixtures/test/_utf8_partial.html.erb
@@ -0,0 +1 @@
+<%= "текст" %>
diff --git a/actionpack/test/fixtures/test/_utf8_partial_magic.html.erb b/actionpack/test/fixtures/test/_utf8_partial_magic.html.erb
new file mode 100644
index 0000000000..4e2224610a
--- /dev/null
+++ b/actionpack/test/fixtures/test/_utf8_partial_magic.html.erb
@@ -0,0 +1,2 @@
+<%# encoding: utf-8 -%>
+<%= "текст" %>
diff --git a/actionpack/test/fixtures/test/utf8.html.erb b/actionpack/test/fixtures/test/utf8.html.erb
index 14fe12debc..ac98c2f012 100644
--- a/actionpack/test/fixtures/test/utf8.html.erb
+++ b/actionpack/test/fixtures/test/utf8.html.erb
@@ -1,4 +1,4 @@
-Русский текст
+Русский <%= render :partial => 'test/utf8_partial' %>
<%= "日".encoding %>
<%= @output_buffer.encoding %>
<%= __ENCODING__ %>
diff --git a/actionpack/test/fixtures/test/utf8_magic.html.erb b/actionpack/test/fixtures/test/utf8_magic.html.erb
index 58cd03b439..257279c29f 100644
--- a/actionpack/test/fixtures/test/utf8_magic.html.erb
+++ b/actionpack/test/fixtures/test/utf8_magic.html.erb
@@ -1,5 +1,5 @@
<%# encoding: utf-8 -%>
-Русский текст
+Русский <%= render :partial => 'test/utf8_partial_magic' %>
<%= "日".encoding %>
<%= @output_buffer.encoding %>
<%= __ENCODING__ %>
diff --git a/actionpack/test/fixtures/test/utf8_magic_with_bare_partial.html.erb b/actionpack/test/fixtures/test/utf8_magic_with_bare_partial.html.erb
new file mode 100644
index 0000000000..cb22692f9a
--- /dev/null
+++ b/actionpack/test/fixtures/test/utf8_magic_with_bare_partial.html.erb
@@ -0,0 +1,5 @@
+<%# encoding: utf-8 -%>
+Русский <%= render :partial => 'test/utf8_partial' %>
+<%= "日".encoding %>
+<%= @output_buffer.encoding %>
+<%= __ENCODING__ %>
diff --git a/actionpack/test/template/active_model_helper_i18n_test.rb b/actionpack/test/template/active_model_helper_i18n_test.rb
deleted file mode 100644
index 4eb2f262bd..0000000000
--- a/actionpack/test/template/active_model_helper_i18n_test.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require 'abstract_unit'
-
-class ActiveModelHelperI18nTest < Test::Unit::TestCase
- include ActionView::Context
- include ActionView::Helpers::ActiveModelHelper
-
- attr_reader :request
-
- def setup
- @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
- @object.stubs :to_model => @object
- @object.stubs :class => stub(:model_name => stub(:human => ""))
-
- @object_name = 'book_seller'
- @object_name_without_underscore = 'book seller'
-
- stubs(:content_tag).returns 'content_tag'
-
- I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
- I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:'
- end
-
- def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message
- I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').never
- error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en')
- end
-
- def test_error_messages_for_given_no_header_option_it_translates_header_message
- I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns 'header message'
- error_messages_for(:object => @object, :locale => 'en')
- end
-
- def test_error_messages_for_given_a_message_option_it_does_not_translate_message
- I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).never
- error_messages_for(:object => @object, :message => 'message', :locale => 'en')
- end
-
- def test_error_messages_for_given_no_message_option_it_translates_message
- I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:'
- error_messages_for(:object => @object, :locale => 'en')
- end
-end
diff --git a/actionpack/test/template/active_model_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb
index 47eb620f7a..8deeb78eab 100644
--- a/actionpack/test/template/active_model_helper_test.rb
+++ b/actionpack/test/template/active_model_helper_test.rb
@@ -4,132 +4,25 @@ class ActiveModelHelperTest < ActionView::TestCase
tests ActionView::Helpers::ActiveModelHelper
silence_warnings do
- class Post < Struct.new(:title, :author_name, :body, :secret, :written_on)
- extend ActiveModel::Naming
+ class Post < Struct.new(:author_name, :body)
include ActiveModel::Conversion
- end
+ include ActiveModel::Validations
- class User < Struct.new(:email)
- extend ActiveModel::Naming
- include ActiveModel::Conversion
- end
-
- class Column < Struct.new(:type, :name, :human_name)
- extend ActiveModel::Naming
- include ActiveModel::Conversion
- end
- end
-
- class DirtyPost
- class Errors
- def empty?
+ def persisted?
false
end
-
- def count
- 1
- end
-
- def full_messages
- ["Author name can't be
empty "]
- end
-
- def [](field)
- ["can't be
empty "]
- end
- end
-
- def errors
- Errors.new
end
end
- def setup_post
- @post = Post.new
- def @post.errors
- Class.new {
- def [](field)
- case field.to_s
- when "author_name"
- ["can't be empty"]
- when "body"
- ['foo']
- else
- []
- end
- end
- def empty?() false end
- def count() 1 end
- def full_messages() [ "Author name can't be empty" ] end
- }.new
- end
-
- def @post.persisted?() false end
- def @post.to_param() nil end
-
- def @post.column_for_attribute(attr_name)
- Post.content_columns.select { |column| column.name == attr_name }.first
- end
-
- silence_warnings do
- def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end
- end
-
- @post.title = "Hello World"
- @post.author_name = ""
- @post.body = "Back to the hill and over it again!"
- @post.secret = 1
- @post.written_on = Date.new(2004, 6, 15)
- end
-
- def setup_user
- @user = User.new
- def @user.errors
- Class.new {
- def [](field) field == "email" ? ['nonempty'] : [] end
- def empty?() false end
- def count() 1 end
- def full_messages() [ "User email can't be empty" ] end
- }.new
- end
-
- def @user.new_record?() true end
- def @user.to_param() nil end
-
- def @user.column_for_attribute(attr_name)
- User.content_columns.select { |column| column.name == attr_name }.first
- end
-
- silence_warnings do
- def User.content_columns() [ Column.new(:string, "email", "Email") ] end
- end
-
- @user.email = ""
- end
-
- def protect_against_forgery?
- @protect_against_forgery ? true : false
- end
- attr_accessor :request_forgery_protection_token, :form_authenticity_token
-
def setup
super
- setup_post
- setup_user
- @response = ActionController::TestResponse.new
- end
+ @post = Post.new
+ @post.errors[:author_name] << "can't be empty"
+ @post.errors[:body] << "foo"
- def url_for(options)
- options = options.symbolize_keys
-
- [options[:action], options[:id].to_param].compact.join('/')
- end
-
- def test_generic_input_tag
- assert_dom_equal(
- %(
), input("post", "title")
- )
+ @post.author_name = ""
+ @post.body = "Back to the hill and over it again!"
end
def test_text_area_with_errors
@@ -160,176 +53,11 @@ class ActiveModelHelperTest < ActionView::TestCase
ActionView::Base.field_error_proc = old_proc if old_proc
end
- def test_form_with_string
- assert_dom_equal(
- %(
),
- form("post")
- )
-
- silence_warnings do
- class << @post
- def persisted?() true end
- def to_param() id end
- def id() 1 end
+ def test_deprecations
+ %w(input form error_messages_for error_message_on).each do |method|
+ assert_deprecated do
+ send(method, "post")
end
end
-
- assert_dom_equal(
- %(
Title
\nBody
Back to the hill and over it again!
),
- form("post")
- )
- end
-
- def test_form_with_protect_against_forgery
- @protect_against_forgery = true
- @request_forgery_protection_token = 'authenticity_token'
- @form_authenticity_token = '123'
- assert_dom_equal(
- %(
Title
\nBody
Back to the hill and over it again!
),
- form("post")
- )
- end
-
- def test_form_with_method_option
- assert_dom_equal(
- %(
Title
\nBody
Back to the hill and over it again!
),
- form("post", :method=>'get')
- )
- end
-
- def test_form_with_action_option
- output_buffer << form("post", :action => "sign")
- assert_select "form[action=sign]" do |form|
- assert_select "input[type=submit][value=Sign]"
- end
- end
-
- def test_form_with_date
- silence_warnings do
- def Post.content_columns() [ Column.new(:date, "written_on", "Written on") ] end
- end
-
- assert_dom_equal(
- %(
Written on \n1999 \n2000 \n2001 \n2002 \n2003 \n2004 \n2005 \n2006 \n2007 \n2008 \n2009 \n \n\nJanuary \nFebruary \nMarch \nApril \nMay \nJune \nJuly \nAugust \nSeptember \nOctober \nNovember \nDecember \n \n\n1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n \n
),
- form("post")
- )
- end
-
- def test_form_with_datetime
- silence_warnings do
- def Post.content_columns() [ Column.new(:datetime, "written_on", "Written on") ] end
- end
- @post.written_on = Time.gm(2004, 6, 15, 16, 30)
-
- assert_dom_equal(
- %(
Written on \n1999 \n2000 \n2001 \n2002 \n2003 \n2004 \n2005 \n2006 \n2007 \n2008 \n2009 \n \n\nJanuary \nFebruary \nMarch \nApril \nMay \nJune \nJuly \nAugust \nSeptember \nOctober \nNovember \nDecember \n \n\n1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n \n — \n00 \n01 \n02 \n03 \n04 \n05 \n06 \n07 \n08 \n09 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n \n : \n00 \n01 \n02 \n03 \n04 \n05 \n06 \n07 \n08 \n09 \n10 \n11 \n12 \n13 \n14 \n15 \n16 \n17 \n18 \n19 \n20 \n21 \n22 \n23 \n24 \n25 \n26 \n27 \n28 \n29 \n30 \n31 \n32 \n33 \n34 \n35 \n36 \n37 \n38 \n39 \n40 \n41 \n42 \n43 \n44 \n45 \n46 \n47 \n48 \n49 \n50 \n51 \n52 \n53 \n54 \n55 \n56 \n57 \n58 \n59 \n \n
),
- form("post")
- )
- end
-
- def test_error_for_block
- assert_dom_equal %(
1 error prohibited this post from being saved There were problems with the following fields:
Author name can't be empty ), error_messages_for("post")
- assert_equal %(
1 error prohibited this post from being saved There were problems with the following fields:
Author name can't be empty ), error_messages_for("post", :class => "errorDeathByClass", :id => "errorDeathById", :header_tag => "h1")
- assert_equal %(
1 error prohibited this post from being saved There were problems with the following fields:
Author name can't be empty ), error_messages_for("post", :class => nil, :id => "errorDeathById", :header_tag => "h1")
- assert_equal %(
1 error prohibited this post from being saved There were problems with the following fields:
Author name can't be empty ), error_messages_for("post", :class => "errorDeathByClass", :id => nil, :header_tag => "h1")
- end
-
- def test_error_messages_for_escapes_html
- @dirty_post = DirtyPost.new
- assert_dom_equal %(
1 error prohibited this dirty post from being saved There were problems with the following fields:
Author name can't be <em>empty</em> ), error_messages_for("dirty_post")
- end
-
- def test_error_messages_for_handles_nil
- assert_equal "", error_messages_for("notthere")
- end
-
- def test_error_message_on_escapes_html
- @dirty_post = DirtyPost.new
- assert_dom_equal "
can't be <em>empty</em>
", error_message_on(:dirty_post, :author_name)
- end
-
- def test_error_message_on_handles_nil
- assert_equal "", error_message_on("notthere", "notthere")
- end
-
- def test_error_message_on
- assert_dom_equal "
can't be empty
", error_message_on(:post, :author_name)
- end
-
- def test_error_message_on_no_instance_variable
- other_post = @post
- assert_dom_equal "
can't be empty
", error_message_on(other_post, :author_name)
- end
-
- def test_error_message_on_with_options_hash
- assert_dom_equal "
beforecan't be emptyafter
", error_message_on(:post, :author_name, :css_class => 'differentError', :prepend_text => 'before', :append_text => 'after')
- end
-
- def test_error_message_on_with_tag_option_in_options_hash
- assert_dom_equal "
beforecan't be emptyafter ", error_message_on(:post, :author_name, :html_tag => "span", :css_class => 'differentError', :prepend_text => 'before', :append_text => 'after')
- end
-
- def test_error_message_on_handles_empty_errors
- assert_equal "", error_message_on(@post, :tag)
- end
-
- def test_error_messages_for_many_objects
- assert_dom_equal %(
2 errors prohibited this post from being saved There were problems with the following fields:
Author name can't be empty User email can't be empty ), error_messages_for("post", "user")
-
- # reverse the order, error order changes and so does the title
- assert_dom_equal %(
2 errors prohibited this user from being saved There were problems with the following fields:
User email can't be empty Author name can't be empty ), error_messages_for("user", "post")
-
- # add the default to put post back in the title
- assert_dom_equal %(
2 errors prohibited this post from being saved There were problems with the following fields:
User email can't be empty Author name can't be empty ), error_messages_for("user", "post", :object_name => "post")
-
- # symbols work as well
- assert_dom_equal %(
2 errors prohibited this post from being saved There were problems with the following fields:
User email can't be empty Author name can't be empty ), error_messages_for(:user, :post, :object_name => :post)
-
- # any default works too
- assert_dom_equal %(
2 errors prohibited this monkey from being saved There were problems with the following fields:
User email can't be empty Author name can't be empty ), error_messages_for(:user, :post, :object_name => "monkey")
-
- # should space object name
- assert_dom_equal %(
2 errors prohibited this chunky bacon from being saved There were problems with the following fields:
User email can't be empty Author name can't be empty ), error_messages_for(:user, :post, :object_name => "chunky_bacon")
-
- # hide header and explanation messages with nil or empty string
- assert_dom_equal %(
User email can't be empty Author name can't be empty ), error_messages_for(:user, :post, :header_message => nil, :message => "")
-
- # override header and explanation messages
- header_message = "Yikes! Some errors"
- message = "Please fix the following fields and resubmit:"
- assert_dom_equal %(
#{header_message} #{message}
User email can't be empty Author name can't be empty ), error_messages_for(:user, :post, :header_message => header_message, :message => message)
- end
-
- def test_error_messages_for_non_instance_variable
- actual_user = @user
- actual_post = @post
- @user = nil
- @post = nil
-
- #explicitly set object
- assert_dom_equal %(
1 error prohibited this post from being saved There were problems with the following fields:
Author name can't be empty ), error_messages_for("post", :object => actual_post)
-
- #multiple objects
- assert_dom_equal %(
2 errors prohibited this user from being saved There were problems with the following fields:
User email can't be empty Author name can't be empty ), error_messages_for("user", "post", :object => [actual_user, actual_post])
-
- #nil object
- assert_equal '', error_messages_for('user', :object => nil)
- end
-
- def test_error_messages_for_model_objects
- error = error_messages_for(@post)
- assert_dom_equal %(
1 error prohibited this post from being saved There were problems with the following fields:
Author name can't be empty ),
- error
-
- error = error_messages_for(@user, @post)
- assert_dom_equal %(
2 errors prohibited this user from being saved There were problems with the following fields:
User email can't be empty Author name can't be empty ),
- error
- end
-
- def test_form_with_string_multipart
- assert_dom_equal(
- %(
Title
\nBody
Back to the hill and over it again!
),
- form("post", :multipart => true)
- )
end
end
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 223a430f92..124bf734ac 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -157,6 +157,14 @@ class AssetTagHelperTest < ActionView::TestCase
%(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(
)
}
+ FaviconLinkToTag = {
+ %(favicon_link_tag) => %(
),
+ %(favicon_link_tag 'favicon.ico') => %(
),
+ %(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(
),
+ %(favicon_link_tag 'favicon.ico', :rel => 'foo', :type => 'bar') => %(
),
+ %(favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png') => %(
)
+ }
+
VideoPathToTag = {
%(video_path("xml")) => %(/videos/xml),
%(video_path("xml.ogg")) => %(/videos/xml.ogg),
@@ -331,6 +339,10 @@ class AssetTagHelperTest < ActionView::TestCase
ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_favicon_link_tag
+ FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_image_tag_windows_behaviour
old_asset_id, ENV["RAILS_ASSET_ID"] = ENV["RAILS_ASSET_ID"], "1"
# This simulates the behaviour of File#exist? on windows when testing a file ending in "."
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 64dfbde0b0..28a13b07be 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -1314,12 +1314,12 @@ class FormHelperTest < ActionView::TestCase
class LabelledFormBuilder < ActionView::Helpers::FormBuilder
(field_helpers - %w(hidden_field)).each do |selector|
- src = <<-END_SRC
+ src, line = <<-END_SRC, __LINE__ + 1
def #{selector}(field, *args, &proc)
("
\#{field.to_s.humanize}: " + super + "
").html_safe
end
END_SRC
- class_eval src, __FILE__, __LINE__
+ class_eval src, __FILE__, line
end
end
@@ -1366,43 +1366,6 @@ class FormHelperTest < ActionView::TestCase
ActionView::Base.default_form_builder = old_default_form_builder
end
- def test_default_form_builder_with_active_record_helpers
- assert_deprecated do
- form_for(:post, @post) do |f|
- concat f.error_message_on('author_name')
- concat f.error_messages
- end
- end
-
- expected = %(
) +
- %(can't be empty
) +
- %(1 error prohibited this post from being saved There were problems with the following fields:
Author name can't be empty ) +
- %( )
-
- assert_dom_equal expected, output_buffer
-
- end
-
- def test_default_form_builder_no_instance_variable
- post = @post
- @post = nil
-
- assert_deprecated do
- form_for(:post, post) do |f|
- concat f.error_message_on('author_name')
- concat f.error_messages
- end
- end
-
- expected = %(
) +
- %(can't be empty
) +
- %(1 error prohibited this post from being saved There were problems with the following fields:
Author name can't be empty ) +
- %( )
-
- assert_dom_equal expected, output_buffer
-
- end
-
def test_fields_for_with_labelled_builder
output_buffer = fields_for(:post, @post, :builder => LabelledFormBuilder) do |f|
concat f.text_field(:title)
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 5f33c933db..c9a50da418 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -245,31 +245,6 @@ module RenderTestCases
assert_equal %(\n
title \n\n),
@view.render(:file => "test/layout_render_file.erb")
end
-
- if '1.9'.respond_to?(:force_encoding)
- def test_render_utf8_template_with_magic_comment
- with_external_encoding Encoding::ASCII_8BIT do
- result = @view.render(:file => "test/utf8_magic.html.erb", :layouts => "layouts/yield")
- assert_equal "Русский текст\nUTF-8\nUTF-8\nUTF-8\n", result
- assert_equal Encoding::UTF_8, result.encoding
- end
- end
-
- def test_render_utf8_template_with_default_external_encoding
- with_external_encoding Encoding::UTF_8 do
- result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield")
- assert_equal "Русский текст\nUTF-8\nUTF-8\nUTF-8\n", result
- assert_equal Encoding::UTF_8, result.encoding
- end
- end
-
- def with_external_encoding(encoding)
- old, Encoding.default_external = Encoding.default_external, encoding
- yield
- ensure
- Encoding.default_external = old
- end
- end
end
class CachedViewRenderTest < ActiveSupport::TestCase
@@ -302,4 +277,51 @@ class LazyViewRenderTest < ActiveSupport::TestCase
def teardown
GC.start
end
+
+ if '1.9'.respond_to?(:force_encoding)
+ def test_render_utf8_template_with_magic_comment
+ with_external_encoding Encoding::ASCII_8BIT do
+ result = @view.render(:file => "test/utf8_magic.html.erb", :layouts => "layouts/yield")
+ assert_equal Encoding::UTF_8, result.encoding
+ assert_equal "Русский текст\n\nUTF-8\nUTF-8\nUTF-8\n", result
+ end
+ end
+
+ def test_render_utf8_template_with_default_external_encoding
+ with_external_encoding Encoding::UTF_8 do
+ result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield")
+ assert_equal Encoding::UTF_8, result.encoding
+ assert_equal "Русский текст\n\nUTF-8\nUTF-8\nUTF-8\n", result
+ end
+ end
+
+ def test_render_utf8_template_with_incompatible_external_encoding
+ with_external_encoding Encoding::SJIS do
+ begin
+ result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield")
+ flunk 'Should have raised incompatible encoding error'
+ rescue ActionView::Template::Error => error
+ assert_match 'invalid byte sequence in Shift_JIS', error.original_exception.message
+ end
+ end
+ end
+
+ def test_render_utf8_template_with_partial_with_incompatible_encoding
+ with_external_encoding Encoding::SJIS do
+ begin
+ result = @view.render(:file => "test/utf8_magic_with_bare_partial.html.erb", :layouts => "layouts/yield")
+ flunk 'Should have raised incompatible encoding error'
+ rescue ActionView::Template::Error => error
+ assert_match 'invalid byte sequence in Shift_JIS', error.original_exception.message
+ end
+ end
+ end
+
+ def with_external_encoding(encoding)
+ old, Encoding.default_external = Encoding.default_external, encoding
+ yield
+ ensure
+ Encoding.default_external = old
+ end
+ end
end
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 35e73fbf1e..de63030714 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -74,56 +74,56 @@ class UrlHelperTest < ActiveSupport::TestCase
# todo: missing test cases
def test_button_to_with_straight_url
- assert_dom_equal "
", button_to("Hello", "http://www.example.com")
+ assert_dom_equal "
", button_to("Hello", "http://www.example.com")
end
def test_button_to_with_query
- assert_dom_equal "
", button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
+ assert_dom_equal "
", button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
end
def test_button_to_with_escaped_query
- assert_dom_equal "
", button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
+ assert_dom_equal "
", button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
end
def test_button_to_with_query_and_no_name
- assert_dom_equal "
", button_to(nil, "http://www.example.com?q1=v1&q2=v2")
+ assert_dom_equal "
", button_to(nil, "http://www.example.com?q1=v1&q2=v2")
end
def test_button_to_with_javascript_confirm
assert_dom_equal(
- "
",
+ "
",
button_to("Hello", "http://www.example.com", :confirm => "Are you sure?")
)
end
def test_button_to_with_remote_and_javascript_confirm
assert_dom_equal(
- "
",
+ "
",
button_to("Hello", "http://www.example.com", :remote => true, :confirm => "Are you sure?")
)
end
def test_button_to_enabled_disabled
assert_dom_equal(
- "
",
+ "
",
button_to("Hello", "http://www.example.com", :disabled => false)
)
assert_dom_equal(
- "
",
+ "
",
button_to("Hello", "http://www.example.com", :disabled => true)
)
end
def test_button_to_with_method_delete
assert_dom_equal(
- "
",
+ "
",
button_to("Hello", "http://www.example.com", :method => :delete)
)
end
def test_button_to_with_method_get
assert_dom_equal(
- "
",
+ "
",
button_to("Hello", "http://www.example.com", :method => :get)
)
end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index f04829ef09..b7c368ad8d 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -221,7 +221,7 @@ module ActiveModel
def alias_attribute(new_name, old_name)
attribute_method_matchers.each do |matcher|
- module_eval <<-STR, __FILE__, __LINE__+1
+ module_eval <<-STR, __FILE__, __LINE__ + 1
def #{matcher.method_name(new_name)}(*args)
send(:#{matcher.method_name(old_name)}, *args)
end
@@ -265,7 +265,7 @@ module ActiveModel
else
method_name = matcher.method_name(attr_name)
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__+1
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
if method_defined?(:#{method_name})
undef :#{method_name}
end
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index d4e98de57b..ad12600d7c 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -105,7 +105,7 @@ module ActiveModel
end
def _define_before_model_callback(klass, callback) #:nodoc:
- klass.class_eval <<-CALLBACK, __FILE__, __LINE__
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.before_#{callback}(*args, &block)
set_callback(:#{callback}, :before, *args, &block)
end
@@ -113,7 +113,7 @@ module ActiveModel
end
def _define_around_model_callback(klass, callback) #:nodoc:
- klass.class_eval <<-CALLBACK, __FILE__, __LINE__
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.around_#{callback}(*args, &block)
set_callback(:#{callback}, :around, *args, &block)
end
@@ -121,7 +121,7 @@ module ActiveModel
end
def _define_after_model_callback(klass, callback) #:nodoc:
- klass.class_eval <<-CALLBACK, __FILE__, __LINE__
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.after_#{callback}(*args, &block)
options = args.extract_options!
options[:prepend] = true
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 64b28f6def..e6c86c7843 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -142,6 +142,11 @@ module ActiveModel
to_a.size
end
+ # Returns true if there are any errors, false if not.
+ def empty?
+ all? { |k, v| v && v.empty? }
+ end
+
# Returns an xml formatted representation of the Errors hash.
#
# p.errors.add(:name, "can't be blank")
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index a33657626f..aac47d5f81 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -3,7 +3,7 @@ module ActiveModel
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta2"
+ BUILD = "beta3"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 9fedd84c73..925a68da91 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -97,6 +97,12 @@ class ValidationsTest < ActiveModel::TestCase
assert_equal 2, r.errors.count
end
+ def test_errors_empty_after_errors_on_check
+ t = Topic.new
+ assert t.errors[:id].empty?
+ assert t.errors.empty?
+ end
+
def test_validates_each
hits = 0
Topic.validates_each(:title, :content, [:title, :content]) do |record, attr|
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index e638da0f3e..bf05389eae 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -50,6 +50,7 @@ end
t.libs << "test" << connection_path
t.test_files=Dir.glob( "test/cases/**/*_test{,_#{adapter_short}}.rb" ).sort
t.verbose = true
+ t.warning = true
}
task "isolated_test_#{adapter}" do
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 5ecdf1ac8d..b808f8c306 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -105,6 +105,7 @@ module ActiveRecord
def reset
reset_target!
+ reset_named_scopes_cache!
@loaded = false
end
@@ -146,7 +147,7 @@ module ActiveRecord
# has_many :books
# end
#
- # Author.find(:first).books.transaction do
+ # Author.first.books.transaction do
# # same effect as calling Book.transaction
# end
def transaction(*args)
@@ -162,6 +163,7 @@ module ActiveRecord
load_target
delete(@target)
reset_target!
+ reset_named_scopes_cache!
end
# Calculate sum using SQL, not Enumerable
@@ -250,6 +252,7 @@ module ActiveRecord
load_target
destroy(@target)
reset_target!
+ reset_named_scopes_cache!
end
def create(attrs = {})
@@ -406,8 +409,8 @@ module ActiveRecord
super
end
elsif @reflection.klass.scopes[method]
- @_scopes ||= {}
- @_scopes[method] ||= with_scope(construct_scope) { @reflection.klass.send(method, *args) }
+ @_named_scopes_cache ||= {}
+ @_named_scopes_cache[method] ||= with_scope(construct_scope) { @reflection.klass.send(method, *args) }
else
with_scope(construct_scope) do
if block_given?
@@ -428,6 +431,10 @@ module ActiveRecord
@target = Array.new
end
+ def reset_named_scopes_cache!
+ @_named_scopes_cache = {}
+ end
+
def find_target
records =
if @reflection.options[:finder_sql]
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 435aea9b09..36f2a9777c 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -52,6 +52,8 @@ module ActiveRecord
@changed_attributes.delete(attr) unless field_changed?(attr, old, value)
else
old = clone_attribute_value(:read_attribute, attr)
+ # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
+ old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
@changed_attributes[attr] = old if field_changed?(attr, old, value)
end
@@ -84,6 +86,10 @@ module ActiveRecord
old != value
end
+
+ def clone_with_time_zone_conversion_attribute?(attr, old)
+ old.class.name == "Time" && time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index a8e3e28a7a..783d61383b 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -17,7 +17,7 @@ module ActiveRecord
# This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
def define_method_attribute(attr_name)
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
- method_body = <<-EOV
+ method_body, line = <<-EOV, __LINE__ + 1
def #{attr_name}(reload = false)
cached = @attributes_cache['#{attr_name}']
return cached if cached && !reload
@@ -25,7 +25,7 @@ module ActiveRecord
@attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
end
EOV
- generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__)
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
else
super
end
@@ -35,7 +35,7 @@ module ActiveRecord
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
def define_method_attribute=(attr_name)
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
- method_body = <<-EOV
+ method_body, line = <<-EOV, __LINE__ + 1
def #{attr_name}=(time)
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
@@ -44,7 +44,7 @@ module ActiveRecord
write_attribute(:#{attr_name}, time)
end
EOV
- generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__)
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
else
super
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index f0b107255c..d544c48a4c 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -56,15 +56,15 @@ module ActiveRecord #:nodoc:
#
# class User < ActiveRecord::Base
# def self.authenticate_unsafely(user_name, password)
- # find(:first, :conditions => "user_name = '#{user_name}' AND password = '#{password}'")
+ # where("user_name = '#{user_name}' AND password = '#{password}'").first
# end
#
# def self.authenticate_safely(user_name, password)
- # find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ])
+ # where("user_name = ? AND password = ?", user_name, password).first
# end
#
# def self.authenticate_safely_simply(user_name, password)
- # find(:first, :conditions => { :user_name => user_name, :password => password })
+ # where(:user_name => user_name, :password => password).first
# end
# end
#
@@ -77,30 +77,30 @@ module ActiveRecord #:nodoc:
# question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
# the question marks with symbols and supplying a hash with values for the matching symbol keys:
#
- # Company.find(:first, :conditions => [
+ # Company.where(
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
# { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
- # ])
+ # ).first
#
# Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
# operator. For instance:
#
- # Student.find(:all, :conditions => { :first_name => "Harvey", :status => 1 })
- # Student.find(:all, :conditions => params[:student])
+ # Student.where(:first_name => "Harvey", :status => 1)
+ # Student.where(params[:student])
#
# A range may be used in the hash to use the SQL BETWEEN operator:
#
- # Student.find(:all, :conditions => { :grade => 9..12 })
+ # Student.where(:grade => 9..12)
#
# An array may be used in the hash to use the SQL IN operator:
#
- # Student.find(:all, :conditions => { :grade => [9,11,12] })
+ # Student.where(:grade => [9,11,12])
#
# When joining tables, nested hashes or keys written in the form 'table_name.column_name' can be used to qualify the table name of a
# particular condition. For instance:
#
- # Student.find(:all, :conditions => { :schools => { :type => 'public' }}, :joins => :schools)
- # Student.find(:all, :conditions => { 'schools.type' => 'public' }, :joins => :schools)
+ # Student.joins(:schools).where(:schools => { :type => 'public' })
+ # Student.joins(:schools).where('schools.type' => 'public' )
#
# == Overwriting default accessors
#
@@ -153,18 +153,18 @@ module ActiveRecord #:nodoc:
# Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
# appending the name of an attribute to
find_by_ ,
find_last_by_ , or
find_all_by_ , so you get finders like
Person.find_by_user_name ,
#
Person.find_all_by_last_name , and
Payment.find_by_transaction_id . So instead of writing
- #
Person.find(:first, :conditions => ["user_name = ?", user_name]) , you just do
Person.find_by_user_name(user_name) .
- # And instead of writing
Person.find(:all, :conditions => ["last_name = ?", last_name]) , you just do
Person.find_all_by_last_name(last_name) .
+ #
Person.where(:user_name => user_name).first , you just do
Person.find_by_user_name(user_name) .
+ # And instead of writing
Person.where(:last_name => last_name).all , you just do
Person.find_all_by_last_name(last_name) .
#
# It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
#
Person.find_by_user_name_and_password or even
Payment.find_by_purchaser_and_state_and_country . So instead of writing
- #
Person.find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password]) , you just do
+ #
Person.where(:user_name => user_name, :password => password).first , you just do
#
Person.find_by_user_name_and_password(user_name, password) .
#
- # It's even possible to use all the additional parameters to find. For example, the full interface for
Payment.find_all_by_amount
- # is actually
Payment.find_all_by_amount(amount, options) . And the full interface to
Person.find_by_user_name is
- # actually
Person.find_by_user_name(user_name, options) . So you could call
Payment.find_all_by_amount(50, :order => "created_on") .
- # Also you may call
Payment.find_last_by_amount(amount, options) returning the last record matching that amount and options.
+ # It's even possible to call these dynamic finder methods on relations and named scopes. For example :
+ #
+ # Payment.order("created_on").find_all_by_amount(50)
+ # Payment.pending.find_last_by_amount(100)
#
# The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
#
find_or_create_by_ and will return the object if it already exists and otherwise creates it, then returns it. Protected attributes won't be set unless they are given in a block. For example:
@@ -224,7 +224,7 @@ module ActiveRecord #:nodoc:
# class PriorityClient < Client; end
#
# When you do
Firm.create(:name => "37signals") , this record will be saved in the companies table with type = "Firm". You can then
- # fetch this row again using
Company.find(:first, "name = '37signals'") and it will return a Firm object.
+ # fetch this row again using
Company.where(:name => '37signals').first and it will return a Firm object.
#
# If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
# like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
@@ -1117,10 +1117,6 @@ module ActiveRecord #:nodoc:
# It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
# is actually
find_all_by_amount(amount, options) .
#
- # Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
- # are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password])
- # respectively.
- #
# Each dynamic finder, scope or initializer/creator 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)
@@ -1138,7 +1134,7 @@ module ActiveRecord #:nodoc:
attribute_names = match.attribute_names
super unless all_attributes_exists?(attribute_names)
if match.scope?
- self.class_eval %{
+ self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
options = args.extract_options! # options = args.extract_options!
attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
@@ -1147,7 +1143,7 @@ module ActiveRecord #:nodoc:
#
scoped(:conditions => attributes) # scoped(:conditions => attributes)
end # end
- }, __FILE__, __LINE__
+ METHOD
send(method_id, *arguments)
end
else
@@ -1196,12 +1192,12 @@ module ActiveRecord #:nodoc:
protected
# Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
- # method_name may be
:find or
:create .
:find parameters may include the
:conditions ,
:joins ,
- #
:include ,
:offset ,
:limit , and
:readonly options.
:create parameters are an attributes hash.
+ # method_name may be
:find or
:create .
:find parameter is
Relation while
+ #
:create parameters are an attributes hash.
#
# class Article < ActiveRecord::Base
# def self.create_with_scope
- # with_scope(:find => { :conditions => "blog_id = 1" }, :create => { :blog_id => 1 }) do
+ # with_scope(:find => where(:blog_id => 1), :create => { :blog_id => 1 }) do
# find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
# a = create(1)
# a.blog_id # => 1
@@ -1210,20 +1206,20 @@ module ActiveRecord #:nodoc:
# end
#
# In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
- #
:conditions ,
:include , and
:joins options in
:find , which are merged.
+ #
where ,
includes , and
joins operations in
Relation , which are merged.
#
- #
:joins options are uniqued so multiple scopes can join in the same table without table aliasing
+ #
joins operations are uniqued so multiple scopes can join in the same table without table aliasing
# problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
# array of strings format for your joins.
#
# class Article < ActiveRecord::Base
# def self.find_with_scope
- # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
- # with_scope(:find => { :limit => 10 }) do
- # find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
+ # with_scope(:find => where(:blog_id => 1).limit(1), :create => { :blog_id => 1 }) do
+ # with_scope(:find => limit(10)) do
+ # all # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
# end
- # with_scope(:find => { :conditions => "author_id = 3" }) do
- # find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
+ # with_scope(:find => where(:author_id => 3)) do
+ # all # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
# end
# end
# end
@@ -1233,9 +1229,9 @@ module ActiveRecord #:nodoc:
#
# class Article < ActiveRecord::Base
# def self.find_with_exclusive_scope
- # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }) do
- # with_exclusive_scope(:find => { :limit => 10 })
- # find(:all) # => SELECT * from articles LIMIT 10
+ # with_scope(:find => where(:blog_id => 1).limit(1)) do
+ # with_exclusive_scope(:find => limit(10))
+ # all # => SELECT * from articles LIMIT 10
# end
# end
# end
@@ -1318,9 +1314,9 @@ module ActiveRecord #:nodoc:
modularized_name = type_name_with_module(type_name)
silence_warnings do
begin
- class_eval(modularized_name, __FILE__, __LINE__)
+ class_eval(modularized_name, __FILE__)
rescue NameError
- class_eval(type_name, __FILE__, __LINE__)
+ class_eval(type_name, __FILE__)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 1e095110f2..e5d100b51b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -15,7 +15,7 @@ module ActiveRecord
def dirties_query_cache(base, *method_names)
method_names.each do |method_name|
- base.class_eval <<-end_code, __FILE__, __LINE__
+ base.class_eval <<-end_code, __FILE__, __LINE__ + 1
def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args)
clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
#{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2395a744a3..68ee88bba4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -658,27 +658,6 @@ module ActiveRecord
end
end
- # Creates a schema for the given user
- #
- # Example:
- # create_schema('products', 'postgres')
- def create_schema(schema_name, pg_username)
- execute("CREATE SCHEMA \"#{schema_name}\" AUTHORIZATION \"#{pg_username}\"")
- end
-
- # Drops a schema
- #
- # Example:
- # drop_schema('products')
- def drop_schema(schema_name)
- execute("DROP SCHEMA \"#{schema_name}\"")
- end
-
- # Returns an array of all schemas in the database
- def all_schemas
- query('SELECT schema_name FROM information_schema.schemata').flatten
- end
-
# Returns the list of all tables in the schema search path or a specified schema.
def tables(name = nil)
query(<<-SQL, name).map { |row| row[0] }
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index a107befef3..2b53afc68b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -84,15 +84,9 @@ namespace :db do
end
end
when 'postgresql'
- @encoding = config['encoding'] || ENV['CHARSET'] || 'utf8'
- schema_search_path = config['schema_search_path'] || 'public'
- first_in_schema_search_path = schema_search_path.split(',').first.strip
+ @encoding = config[:encoding] || ENV['CHARSET'] || 'utf8'
begin
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
- unless ActiveRecord::Base.connection.all_schemas.include?(first_in_schema_search_path)
- ActiveRecord::Base.connection.create_schema(first_in_schema_search_path, config['username'])
- $stderr.puts "Schema #{first_in_schema_search_path} has been created."
- end
ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding))
ActiveRecord::Base.establish_connection(config)
rescue
@@ -452,6 +446,8 @@ namespace :db do
end
end
+task 'test:prepare' => 'db:test:prepare'
+
def drop_database(config)
case config['adapter']
when 'mysql'
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index cf78ec6e12..bf6b995a37 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -3,7 +3,7 @@ module ActiveRecord
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta2"
+ BUILD = "beta3"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activerecord/test/cases/active_schema_test_postgresql.rb b/activerecord/test/cases/active_schema_test_postgresql.rb
index 67c662d694..af80f724f2 100644
--- a/activerecord/test/cases/active_schema_test_postgresql.rb
+++ b/activerecord/test/cases/active_schema_test_postgresql.rb
@@ -17,13 +17,6 @@ class PostgresqlActiveSchemaTest < Test::Unit::TestCase
assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1)
end
- def test_create_schema
- assert_equal %(CREATE SCHEMA "rizwan" AUTHORIZATION "postgres"), create_schema(:rizwan, :postgres)
- end
-
- def test_drop_schema
- assert_equal %(DROP SCHEMA "rizwan"), drop_schema(:rizwan)
- end
private
def method_missing(method_symbol, *arguments)
ActiveRecord::Base.connection.send(method_symbol, *arguments)
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 9f78ae008c..9b28766405 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -81,12 +81,6 @@ class AdapterTest < ActiveRecord::TestCase
def test_encoding
assert_not_nil @connection.encoding
end
-
- def test_all_schemas
- @connection.create_schema(:test_schema, :postgres)
- assert @connection.all_schemas.include?('test_schema')
- @connection.drop_schema(:test_schema)
- end
end
def test_table_alias
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 62121b93cb..be77ee4bf3 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -35,11 +35,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_with_primary_key_joins_on_correct_column
sql = Client.joins(:firm_with_primary_key).to_sql
if current_adapter?(:MysqlAdapter)
- assert_no_match /`firm_with_primary_keys_companies`\.`id`/, sql
- assert_match /`firm_with_primary_keys_companies`\.`name`/, sql
+ assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql)
+ assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql)
else
- assert_no_match /"firm_with_primary_keys_companies"\."id"/, sql
- assert_match /"firm_with_primary_keys_companies"\."name"/, sql
+ assert_no_match(/"firm_with_primary_keys_companies"\."id"/, sql)
+ assert_match(/"firm_with_primary_keys_companies"\."name"/, sql)
end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index a64ccb2120..7a17ef1ee0 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -54,6 +54,89 @@ class DirtyTest < ActiveRecord::TestCase
assert_nil pirate.catchphrase_change
end
+ def test_time_attributes_changes_with_time_zone
+ in_time_zone 'Paris' do
+ target = Class.new(ActiveRecord::Base)
+ target.table_name = 'pirates'
+
+ # New record - no changes.
+ pirate = target.new
+ assert !pirate.created_on_changed?
+ assert_nil pirate.created_on_change
+
+ # Saved - no changes.
+ pirate.catchphrase = 'arrrr, time zone!!'
+ pirate.save!
+ assert !pirate.created_on_changed?
+ assert_nil pirate.created_on_change
+
+ # Change created_on.
+ old_created_on = pirate.created_on
+ pirate.created_on = Time.now - 1.day
+ assert pirate.created_on_changed?
+ assert_kind_of ActiveSupport::TimeWithZone, pirate.created_on_was
+ assert_equal old_created_on, pirate.created_on_was
+ end
+ end
+
+ def test_time_attributes_changes_without_time_zone_by_skip
+ in_time_zone 'Paris' do
+ target = Class.new(ActiveRecord::Base)
+ target.table_name = 'pirates'
+
+ target.skip_time_zone_conversion_for_attributes = [:created_on]
+
+ # New record - no changes.
+ pirate = target.new
+ assert !pirate.created_on_changed?
+ assert_nil pirate.created_on_change
+
+ # Saved - no changes.
+ pirate.catchphrase = 'arrrr, time zone!!'
+ pirate.save!
+ assert !pirate.created_on_changed?
+ assert_nil pirate.created_on_change
+
+ # Change created_on.
+ old_created_on = pirate.created_on
+ pirate.created_on = Time.now + 1.day
+ assert pirate.created_on_changed?
+ # kind_of does not work because
+ # ActiveSupport::TimeWithZone.name == 'Time'
+ assert_equal Time, pirate.created_on_was.class
+ assert_equal old_created_on, pirate.created_on_was
+ end
+ end
+
+ def test_time_attributes_changes_without_time_zone
+
+ target = Class.new(ActiveRecord::Base)
+ target.table_name = 'pirates'
+
+ target.time_zone_aware_attributes = false
+
+ # New record - no changes.
+ pirate = target.new
+ assert !pirate.created_on_changed?
+ assert_nil pirate.created_on_change
+
+ # Saved - no changes.
+ pirate.catchphrase = 'arrrr, time zone!!'
+ pirate.save!
+ assert !pirate.created_on_changed?
+ assert_nil pirate.created_on_change
+
+ # Change created_on.
+ old_created_on = pirate.created_on
+ pirate.created_on = Time.now + 1.day
+ assert pirate.created_on_changed?
+ # kind_of does not work because
+ # ActiveSupport::TimeWithZone.name == 'Time'
+ assert_equal Time, pirate.created_on_was.class
+ assert_equal old_created_on, pirate.created_on_was
+ end
+
+
def test_aliased_attribute_changes
# the actual attribute here is name, title is an
# alias setup via alias_attribute
@@ -406,4 +489,16 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal %w(parrot_id), pirate.changed
assert_nil pirate.parrot_id_was
end
+
+ def in_time_zone(zone)
+ old_zone = Time.zone
+ old_tz = ActiveRecord::Base.time_zone_aware_attributes
+
+ Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil
+ ActiveRecord::Base.time_zone_aware_attributes = !zone.nil?
+ yield
+ ensure
+ Time.zone = old_zone
+ ActiveRecord::Base.time_zone_aware_attributes = old_tz
+ end
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index e213986ede..7a26ee072d 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1136,21 +1136,6 @@ if ActiveRecord::Base.connection.supports_migrations?
load(MIGRATIONS_ROOT + "/valid/1_people_have_last_names.rb")
end
- def test_migrator_interleaved_migrations
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1")
-
- assert_nothing_raised do
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2")
- end
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
-
- assert_nothing_raised do
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/interleaved/pass_3")
- end
- end
-
def test_migrator_db_has_no_schema_migrations_table
# Oracle adapter raises error if semicolon is present as last character
if current_adapter?(:OracleAdapter)
@@ -1362,16 +1347,6 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
- def test_migration_should_be_run_without_logger
- previous_logger = ActiveRecord::Base.logger
- ActiveRecord::Base.logger = nil
- assert_nothing_raised do
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
- end
- ensure
- ActiveRecord::Base.logger = previous_logger
- end
-
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
@@ -1457,6 +1432,45 @@ if ActiveRecord::Base.connection.supports_migrations?
end # SexyMigrationsTest
+ class MigrationLoggerTest < ActiveRecord::TestCase
+ def setup
+ Object.send(:remove_const, :InnocentJointable)
+ end
+
+ def test_migration_should_be_run_without_logger
+ previous_logger = ActiveRecord::Base.logger
+ ActiveRecord::Base.logger = nil
+ assert_nothing_raised do
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
+ end
+ ensure
+ ActiveRecord::Base.logger = previous_logger
+ end
+ end
+
+ class InterleavedMigrationsTest < ActiveRecord::TestCase
+ def setup
+ Object.send(:remove_const, :PeopleHaveLastNames)
+ end
+
+ def test_migrator_interleaved_migrations
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1")
+
+ assert_nothing_raised do
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2")
+ end
+
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name)
+
+ Object.send(:remove_const, :PeopleHaveLastNames)
+ Object.send(:remove_const, :InnocentJointable)
+ assert_nothing_raised do
+ ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/interleaved/pass_3")
+ end
+ end
+ end
+
class ChangeTableMigrationsTest < ActiveRecord::TestCase
def setup
@connection = Person.connection
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 3d5ebb6cb8..e4cafad11e 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -422,6 +422,16 @@ class NamedScopeTest < ActiveRecord::TestCase
post.comments.containing_the_letter_e.all # force load
assert_no_queries { post.comments.containing_the_letter_e.all }
end
+
+ def test_named_scopes_are_reset_on_association_reload
+ post = posts(:welcome)
+
+ [:destroy_all, :reset, :delete_all].each do |method|
+ before = post.comments.containing_the_letter_e
+ post.comments.send(method)
+ assert before.object_id != post.comments.containing_the_letter_e.object_id, "AssociationCollection##{method} should reset the named scopes cache"
+ end
+ end
end
class DynamicScopeMatchTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index e1ad5c1685..2849ff11b7 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -130,10 +130,20 @@ class NilXmlSerializationTest < ActiveRecord::TestCase
end
class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
- fixtures :authors, :posts
+ fixtures :authors, :posts, :projects
+
# to_xml used to mess with the hash the user provided which
# caused the builder to be reused. This meant the document kept
# getting appended to.
+
+ def test_modules
+ projects = MyApplication::Business::Project.all
+ xml = projects.to_xml
+ root = projects.first.class.to_s.underscore.pluralize.tr('/','_').dasherize
+ assert_match "<#{root} type=\"array\">", xml
+ assert_match "#{root}>", xml
+ end
+
def test_passing_hash_shouldnt_reuse_builder
options = {:include=>:posts}
david = authors(:david)
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 1e81fc099c..1dd5af8098 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -588,11 +588,11 @@ module ActiveResource
@prefix_parameters = nil
# Redefine the new methods.
- code = <<-end_code
+ code, line = <<-end_code, __LINE__ + 1
def prefix_source() "#{value}" end
def prefix(options={}) "#{prefix_call}" end
end_code
- silence_warnings { instance_eval code, __FILE__, __LINE__ }
+ silence_warnings { instance_eval code, __FILE__, line }
rescue
logger.error "Couldn't set prefix: #{$!}\n #{code}" if logger
raise
diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb
index e5891300a6..1ed3804017 100644
--- a/activeresource/lib/active_resource/http_mock.rb
+++ b/activeresource/lib/active_resource/http_mock.rb
@@ -57,7 +57,7 @@ module ActiveResource
# def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
# @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
# end
- module_eval <<-EOE, __FILE__, __LINE__
+ module_eval <<-EOE, __FILE__, __LINE__ + 1
def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
@responses << [Request.new(:#{method}, path, nil, request_headers), Response.new(body || "", status, response_headers)]
end
@@ -125,7 +125,7 @@ module ActiveResource
# self.class.requests << request
# self.class.responses.assoc(request).try(:second) || raise(InvalidRequestError.new("No response recorded for #{request}"))
# end
- module_eval <<-EOE, __FILE__, __LINE__
+ module_eval <<-EOE, __FILE__, __LINE__ + 1
def #{method}(path, #{'body, ' if has_body}headers)
request = ActiveResource::Request.new(:#{method}, path, #{has_body ? 'body, ' : 'nil, '}headers)
self.class.requests << request
diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb
index d01ebd017f..6b9b8d3a9d 100644
--- a/activeresource/lib/active_resource/version.rb
+++ b/activeresource/lib/active_resource/version.rb
@@ -3,7 +3,7 @@ module ActiveResource
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta2"
+ BUILD = "beta3"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index b9f565c71d..a5a7a9b904 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,3 +1,8 @@
+*Rails 3.0.0 [beta 3] (pending)*
+
+* Speed up and add Ruby 1.9 support for ActiveSupport::Multibyte::Chars#tidy_bytes. #4350 [Norman Clarke]
+
+
*Rails 3.0.0 [beta 2] (April 1st, 2010)*
* Reduced load time by deferring configuration of classes using
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index 6fab565646..8465bc1e10 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -4,7 +4,9 @@ module ActiveSupport
# context, so only the relevant lines are included.
#
# If you need to reconfigure an existing BacktraceCleaner, like the one in Rails, to show as much as possible, you can always
- # call BacktraceCleaner#remove_silencers!
+ # call BacktraceCleaner#remove_silencers! Also, if you need to reconfigure an existing BacktraceCleaner so that it does not
+ # filter or modify the paths of any lines of the backtrace, you can call BacktraceCleaner#remove_filters! These two methods
+ # will give you a completely untouched backtrace.
#
# Example:
#
@@ -60,6 +62,10 @@ module ActiveSupport
@silencers = []
end
+ def remove_filters!
+ @filters = []
+ end
+
private
def filter(backtrace)
@filters.each do |f|
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index d84a62ca2d..c56fedc12e 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -64,7 +64,7 @@ module ActiveSupport
@data.get(key, raw?(options))
end
rescue MemCache::MemCacheError => e
- logger.error("MemCacheError (#{e}): #{e.message}")
+ logger.error("MemCacheError (#{e}): #{e.message}") if logger
nil
end
@@ -85,7 +85,7 @@ module ActiveSupport
response == Response::STORED
end
rescue MemCache::MemCacheError => e
- logger.error("MemCacheError (#{e}): #{e.message}")
+ logger.error("MemCacheError (#{e}): #{e.message}") if logger
false
end
@@ -95,7 +95,7 @@ module ActiveSupport
response == Response::DELETED
end
rescue MemCache::MemCacheError => e
- logger.error("MemCacheError (#{e}): #{e.message}")
+ logger.error("MemCacheError (#{e}): #{e.message}") if logger
false
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index 86c7703c27..bbbd643736 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -18,7 +18,7 @@ module ActiveSupport
def middleware
@middleware ||= begin
klass = Class.new
- klass.class_eval(<<-EOS, __FILE__, __LINE__)
+ klass.class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def initialize(app)
@app = app
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index c669630e47..5a7b94ead7 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -387,7 +387,7 @@ module ActiveSupport
send("_update_#{symbol}_superclass_callbacks")
body = send("_#{symbol}_callbacks").compile(nil)
- body, line = <<-RUBY_EVAL, __LINE__
+ body, line = <<-RUBY_EVAL, __LINE__ + 1
def _run_#{symbol}_callbacks(key = nil, &blk)
if self.class.send("_update_#{symbol}_superclass_callbacks")
self.class.__define_runner(#{symbol.inspect})
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 2119322bfe..5d8e78e6e5 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -131,7 +131,7 @@ class Array
require 'builder' unless defined?(Builder)
options = options.dup
- options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? ActiveSupport::Inflector.pluralize(ActiveSupport::Inflector.underscore(first.class.name)) : "records"
+ options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? ActiveSupport::Inflector.pluralize(ActiveSupport::Inflector.underscore(first.class.name)).tr('/', '_') : "records"
options[:children] ||= options[:root].singularize
options[:indent] ||= 2
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index 3cad164148..ce481f0e84 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -61,7 +61,7 @@ class Module
# e.subject = "Megastars"
# e.title # => "Megastars"
def alias_attribute(new_name, old_name)
- module_eval <<-STR, __FILE__, __LINE__+1
+ module_eval <<-STR, __FILE__, __LINE__ + 1
def #{new_name}; self.#{old_name}; end # def subject; self.title; end
def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end
def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end
diff --git a/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb b/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb
index 4d0198f028..28ac89dab9 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_accessor_with_default.rb
@@ -21,7 +21,7 @@ class Module
def attr_accessor_with_default(sym, default = nil, &block)
raise 'Default value or block required' unless !default.nil? || block
define_method(sym, block_given? ? block : Proc.new { default })
- module_eval(<<-EVAL, __FILE__, __LINE__)
+ module_eval(<<-EVAL, __FILE__, __LINE__ + 1)
def #{sym}=(value) # def age=(value)
class << self; attr_reader :#{sym} end # class << self; attr_reader :age end
@#{sym} = value # @age = value
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index b73f4c2b59..40a1866428 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -126,7 +126,7 @@ class Module
%(raise "#{self}##{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
end
- module_eval(<<-EOS, file, line)
+ module_eval(<<-EOS, file, line - 5)
if instance_methods(false).map(&:to_s).include?("#{prefix}#{method}")
remove_possible_method("#{prefix}#{method}")
end
diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb
index 115b8abd4e..de76a069d6 100644
--- a/activesupport/lib/active_support/core_ext/module/synchronization.rb
+++ b/activesupport/lib/active_support/core_ext/module/synchronization.rb
@@ -28,7 +28,7 @@ class Module
raise ArgumentError, "#{method} is already synchronized. Double synchronization is not currently supported."
end
- module_eval(<<-EOS, __FILE__, __LINE__)
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{aliased_method}_with_synchronization#{punctuation}(*args, &block) # def expire_with_synchronization(*args, &block)
#{with}.synchronize do # @@lock.synchronize do
#{aliased_method}_without_synchronization#{punctuation}(*args, &block) # expire_without_synchronization(*args, &block)
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 3eb0bf31f8..38007fd4e7 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -19,7 +19,7 @@ module ActiveSupport #:nodoc:
# bad.explicit_checking_method "T".mb_chars.downcase.to_s
#
# The default Chars implementation assumes that the encoding of the string is UTF-8, if you want to handle different
- # encodings you can write your own multibyte string handler and configure it through
+ # encodings you can write your own multibyte string handler and configure it through
# ActiveSupport::Multibyte.proxy_class.
#
# class CharsForUTF32
@@ -458,8 +458,10 @@ module ActiveSupport #:nodoc:
end
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.
- def tidy_bytes
- chars(self.class.tidy_bytes(@wrapped_string))
+ #
+ # Passing +true+ will forcibly tidy all bytes, assuming that the string's encoding is entirely CP1252 or ISO-8859-1.
+ def tidy_bytes(force = false)
+ chars(self.class.tidy_bytes(@wrapped_string, force))
end
%w(lstrip rstrip strip reverse upcase downcase tidy_bytes capitalize).each do |method|
@@ -528,7 +530,7 @@ module ActiveSupport #:nodoc:
unpacked << codepoints[marker..pos-1]
marker = pos
end
- end
+ end
unpacked
end
@@ -644,33 +646,80 @@ module ActiveSupport #:nodoc:
codepoints
end
- # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.
- def tidy_bytes(string)
- string.split(//u).map do |c|
- c.force_encoding(Encoding::ASCII) if c.respond_to?(:force_encoding)
+ def tidy_byte(byte)
+ if byte < 160
+ [UCD.cp1252[byte] || byte].pack("U").unpack("C*")
+ elsif byte < 192
+ [194, byte]
+ else
+ [195, byte - 64]
+ end
+ end
+ private :tidy_byte
- if !ActiveSupport::Multibyte::VALID_CHARACTER['UTF-8'].match(c)
- n = c.unpack('C')[0]
- n < 128 ? n.chr :
- n < 160 ? [UCD.cp1252[n] || n].pack('U') :
- n < 192 ? "\xC2" + n.chr : "\xC3" + (n-64).chr
+ # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.
+ #
+ # Passing +true+ will forcibly tidy all bytes, assuming that the string's encoding is entirely CP-1252 or ISO-8859-1.
+ def tidy_bytes(string, force = false)
+ if force
+ return string.unpack("C*").map do |b|
+ tidy_byte(b)
+ end.flatten.compact.pack("C*").unpack("U*").pack("U*")
+ end
+
+ bytes = string.unpack("C*")
+ conts_expected = 0
+ last_lead = 0
+
+ bytes.each_index do |i|
+
+ byte = bytes[i]
+ is_ascii = byte < 128
+ is_cont = byte > 127 && byte < 192
+ is_lead = byte > 191 && byte < 245
+ is_unused = byte > 240
+ is_restricted = byte > 244
+
+ # Impossible or highly unlikely byte? Clean it.
+ if is_unused || is_restricted
+ bytes[i] = tidy_byte(byte)
+ elsif is_cont
+ # Not expecting contination byte? Clean up. Otherwise, now expect one less.
+ conts_expected == 0 ? bytes[i] = tidy_byte(byte) : conts_expected -= 1
else
- c
+ if conts_expected > 0
+ # Expected continuation, but got ASCII or leading? Clean backwards up to
+ # the leading byte.
+ (1..(i - last_lead)).each {|j| bytes[i - j] = tidy_byte(bytes[i - j])}
+ conts_expected = 0
+ end
+ if is_lead
+ # Final byte is leading? Clean it.
+ if i == bytes.length - 1
+ bytes[i] = tidy_byte(bytes.last)
+ else
+ # Valid leading byte? Expect continuations determined by position of
+ # first zero bit, with max of 3.
+ conts_expected = byte < 224 ? 1 : byte < 240 ? 2 : 3
+ last_lead = i
+ end
+ end
end
- end.join
+ end
+ bytes.empty? ? "" : bytes.flatten.compact.pack("C*").unpack("U*").pack("U*")
end
end
protected
-
+
def translate_offset(byte_offset) #:nodoc:
return nil if byte_offset.nil?
return 0 if @wrapped_string == ''
-
+
if @wrapped_string.respond_to?(:force_encoding)
@wrapped_string = @wrapped_string.dup.force_encoding(Encoding::ASCII_8BIT)
end
-
+
begin
@wrapped_string[0...byte_offset].unpack('U*').length
rescue ArgumentError => e
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 57ead35827..e1a2866863 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -1,13 +1,28 @@
require 'yaml'
+YAML.add_builtin_type("omap") do |type, val|
+ ActiveSupport::OrderedHash[val.map(&:to_a).map(&:first)]
+end
+
# OrderedHash is namespaced to prevent conflicts with other implementations
module ActiveSupport
- # Hash is ordered in Ruby 1.9!
- if RUBY_VERSION >= '1.9'
- class OrderedHash < ::Hash #:nodoc:
+ class OrderedHash < ::Hash #:nodoc:
+ def to_yaml_type
+ "!tag:yaml.org,2002:omap"
end
- else
- class OrderedHash < Hash #:nodoc:
+
+ def to_yaml(opts = {})
+ YAML.quick_emit(self, opts) do |out|
+ out.seq(taguri, to_yaml_style) do |seq|
+ each do |k, v|
+ seq.add(k => v)
+ end
+ end
+ end
+ end
+
+ # Hash is ordered in Ruby 1.9!
+ if RUBY_VERSION < '1.9'
def initialize(*args, &block)
super
@keys = []
@@ -55,7 +70,7 @@ module ActiveSupport
end
super
end
-
+
def delete_if
super
sync_keys!
@@ -134,31 +149,10 @@ module ActiveSupport
"#
"
end
- private
-
- def sync_keys!
- @keys.delete_if {|k| !has_key?(k)}
- end
- end
- end
-
- class OrderedHash #:nodoc:
- def to_yaml_type
- "!tag:yaml.org,2002:omap"
- end
-
- def to_yaml(opts = {})
- YAML.quick_emit(self, opts) do |out|
- out.seq(taguri, to_yaml_style) do |seq|
- each do |k, v|
- seq.add(k => v)
- end
+ private
+ def sync_keys!
+ @keys.delete_if {|k| !has_key?(k)}
end
- end
end
end
-
- YAML.add_builtin_type("omap") do |type, val|
- ActiveSupport::OrderedHash[val.map(&:to_a).map(&:first)]
- end
end
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index 3ce11e59d2..538a8b87c6 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -3,7 +3,7 @@ module ActiveSupport
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta2"
+ BUILD = "beta3"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb
index ddbc258df1..86838a7f9a 100644
--- a/activesupport/test/clean_backtrace_test.rb
+++ b/activesupport/test/clean_backtrace_test.rb
@@ -9,6 +9,11 @@ class BacktraceCleanerFilterTest < ActiveSupport::TestCase
test "backtrace should not contain prefix when it has been filtered out" do
assert_equal "/my/class.rb", @bc.clean([ "/my/prefix/my/class.rb" ]).first
end
+
+ test "backtrace cleaner should allow removing filters" do
+ @bc.remove_filters!
+ assert_equal "/my/prefix/my/class.rb", @bc.clean(["/my/prefix/my/class.rb"]).first
+ end
test "backtrace should contain unaltered lines if they dont match a filter" do
assert_equal "/my/other_prefix/my/class.rb", @bc.clean([ "/my/other_prefix/my/class.rb" ]).first
@@ -44,4 +49,4 @@ class BacktraceCleanerFilterAndSilencerTest < ActiveSupport::TestCase
test "backtrace should not silence lines that has first had their silence hook filtered out" do
assert_equal [ "/class.rb" ], @bc.clean([ "/mongrel/class.rb" ])
end
-end
\ No newline at end of file
+end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 0e489c10e1..1b8d13c024 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -107,7 +107,7 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
# Ruby 1.9 only supports basic whitespace
@whitespace = "\n\t ".force_encoding(Encoding::UTF_8)
end
-
+
@byte_order_mark = [65279].pack('U')
end
@@ -468,14 +468,6 @@ end
class MultibyteCharsExtrasTest < Test::Unit::TestCase
include MultibyteTestHelpers
- if RUBY_VERSION >= '1.9'
- def test_tidy_bytes_is_broken_on_1_9_0
- assert_raise(ArgumentError) do
- assert_equal_codepoints [0xfffd].pack('U'), chars("\xef\xbf\xbd").tidy_bytes
- end
- end
- end
-
def test_upcase_should_be_unicode_aware
assert_equal "АБВГД\0F", chars("аБвгд\0f").upcase
assert_equal 'こにちわ', chars('こにちわ').upcase
@@ -504,7 +496,7 @@ class MultibyteCharsExtrasTest < Test::Unit::TestCase
def test_limit_should_work_on_a_multibyte_string
example = chars(UNICODE_STRING)
bytesize = UNICODE_STRING.respond_to?(:bytesize) ? UNICODE_STRING.bytesize : UNICODE_STRING.size
-
+
assert_equal UNICODE_STRING, example.limit(bytesize)
assert_equal '', example.limit(0)
assert_equal '', example.limit(1)
@@ -531,7 +523,7 @@ class MultibyteCharsExtrasTest < Test::Unit::TestCase
assert example.limit(limit).to_s.length <= limit
end
end
-
+
def test_composition_exclusion_is_set_up_properly
# Normalization of DEVANAGARI LETTER QA breaks when composition exclusion isn't used correctly
qa = [0x915, 0x93c].pack('U*')
@@ -607,28 +599,57 @@ class MultibyteCharsExtrasTest < Test::Unit::TestCase
end
def test_tidy_bytes_should_tidy_bytes
+
+ single_byte_cases = {
+ "\x21" => "!", # Valid ASCII byte, low
+ "\x41" => "A", # Valid ASCII byte, mid
+ "\x7E" => "~", # Valid ASCII byte, high
+ "\x80" => "€", # Continuation byte, low (cp125)
+ "\x94" => "”", # Continuation byte, mid (cp125)
+ "\x9F" => "Ÿ", # Continuation byte, high (cp125)
+ "\xC0" => "À", # Overlong encoding, start of 2-byte sequence, but codepoint < 128
+ "\xC1" => "Á", # Overlong encoding, start of 2-byte sequence, but codepoint < 128
+ "\xC2" => "Â", # Start of 2-byte sequence, low
+ "\xC8" => "È", # Start of 2-byte sequence, mid
+ "\xDF" => "ß", # Start of 2-byte sequence, high
+ "\xE0" => "à", # Start of 3-byte sequence, low
+ "\xE8" => "è", # Start of 3-byte sequence, mid
+ "\xEF" => "ï", # Start of 3-byte sequence, high
+ "\xF0" => "ð", # Start of 4-byte sequence
+ "\xF1" => "ñ", # Unused byte
+ "\xFF" => "ÿ", # Restricted byte
+ "\x00" => "\x00" # null char
+ }
+
+ single_byte_cases.each do |bad, good|
+ assert_equal good, chars(bad).tidy_bytes.to_s
+ assert_equal "#{good}#{good}", chars("#{bad}#{bad}").tidy_bytes
+ assert_equal "#{good}#{good}#{good}", chars("#{bad}#{bad}#{bad}").tidy_bytes
+ assert_equal "#{good}a", chars("#{bad}a").tidy_bytes
+ assert_equal "#{good}á", chars("#{bad}á").tidy_bytes
+ assert_equal "a#{good}a", chars("a#{bad}a").tidy_bytes
+ assert_equal "á#{good}á", chars("á#{bad}á").tidy_bytes
+ assert_equal "a#{good}", chars("a#{bad}").tidy_bytes
+ assert_equal "á#{good}", chars("á#{bad}").tidy_bytes
+ end
+
byte_string = "\270\236\010\210\245"
tidy_string = [0xb8, 0x17e, 0x8, 0x2c6, 0xa5].pack('U*')
- ascii_padding = 'aa'
- utf8_padding = 'éé'
-
assert_equal_codepoints tidy_string, chars(byte_string).tidy_bytes
-
- assert_equal_codepoints ascii_padding.dup.insert(1, tidy_string),
- chars(ascii_padding.dup.insert(1, byte_string)).tidy_bytes
- assert_equal_codepoints utf8_padding.dup.insert(2, tidy_string),
- chars(utf8_padding.dup.insert(2, byte_string)).tidy_bytes
assert_nothing_raised { chars(byte_string).tidy_bytes.to_s.unpack('U*') }
- assert_equal_codepoints "\xC3\xA7", chars("\xE7").tidy_bytes # iso_8859_1: small c cedilla
- assert_equal_codepoints "\xE2\x80\x9C", chars("\x93").tidy_bytes # win_1252: left smart quote
- assert_equal_codepoints "\xE2\x82\xAC", chars("\x80").tidy_bytes # win_1252: euro
- assert_equal_codepoints "\x00", chars("\x00").tidy_bytes # null char
- assert_equal_codepoints [0xfffd].pack('U'), chars("\xef\xbf\xbd").tidy_bytes # invalid char
- rescue ArgumentError => e
- raise e if RUBY_VERSION < '1.9'
+ # UTF-8 leading byte followed by too few continuation bytes
+ assert_equal_codepoints "\xc3\xb0\xc2\xa5\xc2\xa4\x21", chars("\xf0\xa5\xa4\x21").tidy_bytes
end
+ def test_tidy_bytes_should_forcibly_tidy_bytes_if_specified
+ byte_string = "\xF0\xA5\xA4\xA4" # valid as both CP-1252 and UTF-8, but with different interpretations.
+ assert_not_equal "𥤤", chars(byte_string).tidy_bytes
+ # Forcible conversion to UTF-8
+ assert_equal "𥤤", chars(byte_string).tidy_bytes(true)
+ end
+
+
private
def string_from_classes(classes)
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 261333628d..82684e4614 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,3 @@
-* A new application now comes with a layout and a stylesheet. [JV]
-
* Renamed config.cookie_secret to config.secret_token and pass it as env key. [JV]
*Rails 3.0.0 [beta 2] (April 1st, 2010)*
diff --git a/railties/guides/assets/stylesheets/main.css b/railties/guides/assets/stylesheets/main.css
index 7ccae2c87e..bab0b7a9d9 100644
--- a/railties/guides/assets/stylesheets/main.css
+++ b/railties/guides/assets/stylesheets/main.css
@@ -437,7 +437,7 @@ div.code_container, div.important, div.caution, div.warning, div.note, div.info
/* Remove bottom margin of paragraphs in special boxes, otherwise they get a
spurious blank area below with the box background. */
div.important p, div.caution p, div.warning p, div.note p, div.info p {
- margin-bottom: 0px;
+ margin-bottom: 1em;
}
/* Edge Badge
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index cf71e700dc..32b1e30502 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -2,180 +2,111 @@ h2. Rails Routing from the Outside In
This guide covers the user-facing features of Rails routing. By referring to this guide, you will be able to:
-* Understand the purpose of routing
-* Decipher the code in +routes.rb+
-* Construct your own routes, using either the @match@ method or the preferred RESTful style
-* Identify how a route will map to a controller and action
+* Understand the code in +routes.rb+
+* Construct your own routes, using either the preferred resourceful style or with the @match@ method
+* Identify what parameters to expect an action to receive
+* Automatically create URLs using route helpers
+* Use advanced techniques such as constraints and Rack endpoints
endprologue.
-h3. The Dual Purpose of Routing
+h3. The Purpose of the Rails Router
-Rails routing is a two-way piece of machinery - rather as if you could turn trees into paper, and then turn paper back into trees. Specifically, it both connects incoming HTTP requests to the code in your application's controllers, and helps you generate URLs without having to hard-code them as strings.
+The Rails router recognizes URLs and dispatches them to a controller's action. It can also generate URLs, avoiding the need to hardcode URL strings in your views.
h4. Connecting URLs to Code
-When your Rails application receives an incoming HTTP request, say
+When your Rails application receives an incoming request
-
+
GET /patients/17
-
+
-the routing engine within Rails is the piece of code that dispatches the request to the appropriate spot in your application. In this case, the application would most likely end up running the +show+ action within the +patients+ controller, displaying the details of the patient whose ID is 17.
+it asks the router to match it to a controller action. If the first matching route is
+
+
+match "/patients/:id" => "patients#show"
+
+
+the request is dispatched to the +patients+ controller's +show+ action with { :id => "17" } in +params+.
h4. Generating URLs from Code
-Routing also works in reverse. If your application contains this code:
+You can also generate routes. If your application contains this code:
@patient = Patient.find(17)
-<%= link_to "Patient Record", patient_path(@patient) %>
+<%= link_to "Patient Record", patients_path(@patient.id) %>
-Then the routing engine is the piece that translates that to a link to a URL such as +http://example.com/patients/17+. By using routing in this way, you can reduce the brittleness of your application as compared to one with hard-coded URLs, and make your code easier to read and understand.
+The router will generate the path +/patients/17+. This reduces the brittleness of your view and makes your code easier to understand.
-NOTE: Patient needs to be declared as a Restful resource for this style of translation to be available.
+h3. Resource Routing: the Rails Default
-h3. Quick Tour of +routes.rb+
+Resource routing allows you to quickly declare all of the common routes for a given resourceful controller. Instead of declaring separate routes for your +index+, +show+, +new+, +edit+, +create+, +update+ and +destroy+ actions, a resourceful route declares them in a single line of code.
-There are two components to routing in Rails: the routing engine itself, which is supplied as part of Rails, and the file +config/routes.rb+, which contains the actual routes that will be used by your application. Learning exactly what you can put in +routes.rb+ is the main topic of this guide, but before we dig in let's get a quick overview.
+h4. Resources on the Web
-h4. Processing the File
+Browsers request pages from Rails by making a request for a URL using a specific HTTP method, such as +GET+, +POST+, +PUT+ and +DELETE+. Each method is a request to perform an operation on the resource. A resource route maps a number of related request to the actions in a single controller.
-In format, +routes.rb+ is nothing more than one big block sent to +ApplicationName::Application.routes.draw+. Within this block, you can have comments, but it's likely that most of your content will be individual lines of code - each line being a route in your application. You'll find five main types of content in this file:
+When your Rails application receives an incoming request for
-* RESTful Routes
-* Named Routes
-* Nested Routes
-* Regular Routes
-* Default Routes
-
-Each of these types of route is covered in more detail later in this guide.
-
-The +routes.rb+ file is processed from top to bottom when a request comes in. The request will be dispatched to the first matching route, and then proceeds to the next. If there is no matching route, then Rails returns HTTP status 404 to the caller.
-
-h4. RESTful Routes
-
-RESTful routes take advantage of the built-in REST orientation of Rails to wrap up a lot of routing information with a single declaration. A RESTful route looks like this:
-
-
-resources :books
-
-
-h4(#quick-tour-named-routes). Named Routes
-
-Named routes give you very readable links in your code, as well as handling incoming requests. Here's a typical named route:
-
-
-match 'login' => 'sessions#new', :as => 'login'
-
-
-If you're coming from Rails 2, this route will be equivalent to:
-
-
-map.login '/login', :controller => 'sessions', :action => 'new'
-
-
-You will also notice that +sessions#new+ is a shorthand for +:controller => 'sessions', :action => 'new'+. By declaring a named route such as this, you can use +login_path+ or +login_url+ in your controllers and views to generate the URLs for this route. A RESTful generates named routes without the need to explicitly generate a named route via +as+ key.
-
-h4. Nested Routes
-
-Nested routes let you declare that one resource is contained within another resource. You'll see later on how this translates to URLs and paths in your code. For example, if your application includes parts, each of which belongs to an assembly, you might have this nested route declaration:
-
-
-resources :assemblies do
- resources :parts
-end
-
-
-h4(#quick-tour-regular-routes). Regular Routes
-
-In many applications, you'll also see non-RESTful routing, which explicitly connects the parts of a URL to a particular action. For example,
-
-
-match 'parts/:number' => 'inventory#show'
-
-
-h4. Default Routes
-
-The default route is a safety net that catches otherwise-unrouted requests. Many Rails applications will contain this default route:
-
-
-match ':controller(/:action(/:id(.:format)))'
-
-
-In Rails 3, this route is commented out advising to use RESTful routes as much as possible. So if you're using RESTful routing for everything in your application, you will probably want to leave it like that.
-
-h3. RESTful Routing: the Rails Default
-
-RESTful routing is the current standard for routing in Rails, and it's the one that you should prefer for new applications. It can take a little while to understand how RESTful routing works, but it's worth the effort; your code will be easier to read and you'll be working with Rails, rather than fighting against it, when you use this style of routing.
-
-h4. What is REST?
-
-The foundation of RESTful routing is generally considered to be Roy Fielding's doctoral thesis, "Architectural Styles and the Design of Network-based Software Architectures":http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm. Fortunately, you need not read this entire document to understand how REST works in Rails. REST, an acronym for Representational State Transfer, boils down to two main principles for our purposes:
-
-* Using resource identifiers (which, for the purposes of discussion, you can think of as URLs) to represent resources
-* Transferring representations of the state of that resource between system components.
-
-For example, to a Rails application a request such as this:
-
-
+
DELETE /photos/17
-
+
-would be understood to refer to a photo resource with the ID of 17, and to indicate a desired action - deleting that resource. REST is a natural style for the architecture of web applications, and Rails makes it even more natural by using conventions to shield you from some of the RESTful complexities.
-
-h4. CRUD, Verbs, and Actions
-
-In Rails, a RESTful route provides a mapping between HTTP verbs, controller actions, and (implicitly) CRUD operations in a database. A single entry in the routing file, such as
+it asks the router to map it to a controller action. If the first matching route is
resources :photos
-creates seven different routes in your application:
+Rails would dispatch that request to the +destroy+ method on the +photos+ controller with { :id => "17" } in +params+.
-|_.HTTP verb|_.URL |_.controller|_.action |_.used for|
-|GET |/photos |Photos |index |display a list of all photos|
-|GET |/photos/new |Photos |new |return an HTML form for creating a new photo|
-|POST |/photos |Photos |create |create a new photo|
-|GET |/photos/1 |Photos |show |display a specific photo|
-|GET |/photos/1/edit |Photos |edit |return an HTML form for editing a photo|
-|PUT |/photos/1 |Photos |update |update a specific photo|
-|DELETE |/photos/1 |Photos |destroy |delete a specific photo|
+h4. CRUD, Verbs, and Actions
-For the specific routes (those that reference just a single resource), the identifier for the resource will be available within the corresponding controller action as +params[:id]+.
+In Rails, a resourceful route provides a mapping between HTTP verbs and URLs and controller actions. By convention, each action also maps to particular CRUD operations in a database. A single entry in the routing file, such as
+
+
+resources :photos
+
+
+creates seven different routes in your application, all mapping to the +Photos+ controller:
+
+|_. Verb |_.URL |_.action |_.used for|
+|GET |/photos |index |display a list of all photos|
+|GET |/photos/new |new |return an HTML form for creating a new photo|
+|POST |/photos |create |create a new photo|
+|GET |/photos/:id |show |display a specific photo|
+|GET |/photos/:id/edit |edit |return an HTML form for editing a photo|
+|PUT |/photos/:id |update |update a specific photo|
+|DELETE |/photos/:id |destroy |delete a specific photo|
h4. URLs and Paths
-Creating a RESTful route will also make available a pile of helpers within your application, something that requires explicit mention otherwise:
+Creating a resourceful route will also expose a number of helpers to the controllers in your application. In the case of +resources :photos+:
-* +photos_url+ and +photos_path+ map to the path for the index and create actions
-* +new_photo_url+ and +new_photo_path+ map to the path for the new action
-* +edit_photo_url+ and +edit_photo_path+ map to the path for the edit action
-* +photo_url+ and +photo_path+ map to the path for the show, update, and destroy actions
+* +photos_path+ returns +/photos+
+* +new_photo_path+ returns +/photos/new+
+* +edit_photo_path+ returns +/photos/edit+
+* +photo_path(id)+ returns +/photos/:id+ (for instance, +photo_path(10)+ returns +/photos/10+)
-NOTE: Because routing makes use of the HTTP verb as well as the path in the request to dispatch requests, the seven routes generated by a RESTful routing entry only give rise to four pairs of helpers.
+Each of these helpers has a corresponding +_url+ helper (such as +photos_url+) which returns the same path prefixed with the current host, port and path prefix.
-In each case, the +_url+ helper generates a string containing the entire URL that the application will understand, while the +_path+ helper generates a string containing the relative path from the root of the application. For example:
-
-
-photos_url # => "http://www.example.com/photos"
-photos_path # => "/photos"
-
+NOTE: Because the router uses the HTTP verb and URL to match inbound requests, four URLs map to seven different actions.
h4. Defining Multiple Resources at the Same Time
-If you need to create routes for more than one RESTful resource, you can save a bit of typing by defining them all with a single call to +resources+:
+If you need to create routes for more than one resource, you can save a bit of typing by defining them all with a single call to +resources+:
resources :photos, :books, :videos
-This has exactly the same effect as
+This works exactly the same as
resources :photos
@@ -185,205 +116,91 @@ resources :videos
h4. Singular Resources
-You can also apply RESTful routing to singleton resources within your application. In this case, you use +resource+ instead of +resources+ and the route generation is slightly different. For example, a routing entry of
+Sometimes, you have a resource that clients always look up without referencing an ID. A common example, +/profile+ always shows the profile of the currently logged in user. In this case, you can use a singular resource to map +/profile+ (rather than +/profile/:id+) to the +show+ action.
resource :geocoder
-creates six different routes in your application:
+creates six different routes in your application, all mapping to the +Geocoders+ controller:
-|_.HTTP verb|_.URL |_.controller|_.action |_.used for|
-|GET |/geocoder/new |Geocoders |new |return an HTML form for creating the new geocoder|
-|POST |/geocoder |Geocoders |create |create the new geocoder|
-|GET |/geocoder |Geocoders |show |display the one and only geocoder resource|
-|GET |/geocoder/edit |Geocoders |edit |return an HTML form for editing the geocoder|
-|PUT |/geocoder |Geocoders |update |update the one and only geocoder resource|
-|DELETE |/geocoder |Geocoders |destroy |delete the geocoder resource|
+|_. Verb |_.URL |_.action |_.used for|
+|GET |/geocoder/new |new |return an HTML form for creating the geocoder|
+|POST |/geocoder |create |create the new geocoder|
+|GET |/geocoder |show |display the one and only geocoder resource|
+|GET |/geocoder/edit |edit |return an HTML form for editing the geocoder|
+|PUT |/geocoder |update |update the one and only geocoder resource|
+|DELETE |/geocoder |destroy |delete the geocoder resource|
-NOTE: Even though the name of the resource is singular in +routes.rb+, the matching controller is still plural.
+NOTE: Because you might want to use the same controller for a singular route (+/account+) and a plural route (+/accounts/45+), singular resources map to plural controllers.
-A singular RESTful route generates an abbreviated set of helpers:
+A singular resourceful route generates these helpers:
-* +new_geocoder_url+ and +new_geocoder_path+ map to the path for the new action
-* +edit_geocoder_url+ and +edit_geocoder_path+ map to the path for the edit action
-* +geocoder_url+ and +geocoder_path+ map to the path for the create, show, update, and destroy actions
+* +new_geocoder_path+ returns +/geocoder/new+
+* +edit_geocoder_path+ returns +/geocoder/edit+
+* +geocoder_path+ returns +/geocoder+
-h4. Customizing Resources
-
-Although the conventions of RESTful routing are likely to be sufficient for many applications, there are a number of ways to customize the way that RESTful routes work. These options include:
-
-* +:controller+
-* +:singular+
-* +:constraints+
-* +:as+
-* +:path_names+
-* +:only+
-* +:except+
-
-You can also add additional routes via the +member+ and +collection+ blocks, which are discussed later in this guide.
-
-h5. Using +:controller+
-
-The +:controller+ option lets you use a controller name that is different from the public-facing resource name. For example, this routing entry:
-
-
-resources :photos, :controller => "images"
-
-
-will recognize incoming URLs containing +photo+ but route the requests to the Images controller:
-
-|_.HTTP verb|_.URL |_.controller|_.action |_.used for|
-|GET |/photos |Images |index |display a list of all images|
-|GET |/photos/new |Images |new |return an HTML form for creating a new image|
-|POST |/photos |Images |create |create a new image|
-|GET |/photos/1 |Images |show |display a specific image|
-|GET |/photos/1/edit |Images |edit |return an HTML form for editing an image|
-|PUT |/photos/1 |Images |update |update a specific image|
-|DELETE |/photos/1 |Images |destroy |delete a specific image|
-
-NOTE: The helpers will be generated with the name of the resource, not the name of the controller. So in this case, you'd still get +photos_path+, +new_photo_path+, and so on.
+As with plural resources, the same helpers ending in +_url+ will also include the host, port and path prefix.
h4. Controller Namespaces and Routing
-Rails allows you to group your controllers into namespaces by saving them in folders underneath +app/controllers+. The +:controller+ option provides a convenient way to use these routes. For example, you might have a resource whose controller is purely for admin users in the +admin+ folder:
+You may wish to organize groups of controllers under a namespace. Most commonly, you might group a number of administrative controllers under an +Admin::+ namespace. You would place these controllers under the +app/controllers/admin+ directory, and you can group them together in your router:
-resources :photos, :controller => "admin/photos"
-
-
-If you use controller namespaces, you need to be aware of a subtlety in the Rails routing code: it always tries to preserve as much of the namespace from the previous request as possible. For example, if you are on a view generated from the +photo_path+ helper, and you follow a link generated with +<%= link_to "show", photo_path(1) %>+ you will end up on the view generated by +admin/photos/show+, but you will also end up in the same place if you have +<%= link_to "show", {:controller => "photos", :action => "show"} %>+ because Rails will generate the show URL relative to the current URL.
-
-TIP: If you want to guarantee that a link goes to a top-level controller, use a preceding slash to anchor the controller name: +<%= link_to "show", {:controller => "/photos", :action => "show"} %>+
-
-You can also specify a controller namespace with the +namespace+ method instead of a path. This can be especially useful when mapping multiple namespaced routes together:
-
-
-namespace :admin do
- resources :photos, :videos
+namespace "admin" do
+ resources :posts, :comments
end
-That would give you routing for +admin/photos+ and +admin/videos+ controllers.
+This will create a number of routes for each of the +posts+ and +comments+ controller. For +Admin::PostsController+, Rails will create:
-The difference between generating routes through +namespace+ and the +:controller+ key is that the +namespace+ will add +admin+ to the generated helpers as well, so the above route generates +admin_photos_path+.
+|_. Verb |_.URL |_.action |_. helper |
+|GET |/admin/photos |index | admin_photos_path |
+|GET |/admin/photos/new |new | new_admin_photos_path |
+|POST |/admin/photos |create | admin_photos_path |
+|GET |/admin/photos/1 |show | admin_photo_path(id) |
+|GET |/admin/photos/1/edit |edit | edit_admin_photo_path(id) |
+|PUT |/admin/photos/1 |update | admin_photo_path(id) |
+|DELETE |/admin/photos/1 |destroy | admin_photo_path(id) |
-h5. Using +:singular+
-
-If for some reason Rails isn't doing what you want in converting the plural resource name to a singular name in member routes, you can override its judgment with the +:singular+ option:
+If you want to route +/photos+ (without the prefix +/admin+) to +Admin::PostsController+, you could use
-resources :teeth, :singular => "tooth"
-
-
-TIP: Depending on the other code in your application, you may prefer to add additional rules to the +Inflector+ class instead.
-
-h5. Using +:constraints+
-
-You can use the +:constraints+ option in a RESTful route to impose a format on the implied parameter in routes. For example:
-
-
-resources :photos, :constraints => {:id => /[A-Z][A-Z][0-9]+/}
-
-
-This declaration constrains the +:id+ parameter to match the supplied regular expression. So, in this case, +/photos/1+ would no longer be recognized by this route, but +/photos/RR27+ would.
-
-h5. Using +:as+
-
-The +:as+ option lets you override the normal naming for the actual generated paths. For example:
-
-
-resources :photos, :as => "images"
-
-
-will recognize incoming URLs containing +image+ but route the requests to the Photos controller:
-
-|_.HTTP verb|_.URL |_.controller|_.action |_:used for|
-|GET |/images |Photos |index |display a list of all photos|
-|GET |/images/new |Photos |new |return an HTML form for creating a new photo|
-|POST |/images |Photos |create |create a new photo|
-|GET |/images/1 |Photos |show |display a specific photo|
-|GET |/images/1/edit |Photos |edit |return an HTML form for editing a photo|
-|PUT |/images/1 |Photos |update |update a specific photo|
-|DELETE |/images/1 |Photos |destroy |delete a specific photo|
-
-NOTE: The helpers will be generated with the name of the resource, not the path name. So in this case, you'd still get +photos_path+, +new_photo_path+, and so on.
-
-h5. Using +:path_names+
-
-The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs:
-
-
-resources :photos, :path_names => { :new => 'make', :edit => 'change' }
-
-
-This would cause the routing to recognize URLs such as
-
-
-/photos/make
-/photos/1/change
-
-
-NOTE: The actual action names aren't changed by this option; the two URLs shown would still route to the new and edit actions.
-
-TIP: If you find yourself wanting to change this option uniformly for all of your routes, you can set a default in your environment:
-
-
-config.action_controller.resources_path_names = { :new => 'make', :edit => 'change' }
-
-
-h5. Using +:name_prefix+
-
-You can use the :name_prefix option to avoid collisions between routes. This is most useful when you have two resources with the same name that use +:path_prefix+ to map differently. For example:
-
-
-resources :photos :name_prefix => 'photographer'
-
-
-This combination will give you route helpers such as +photographer_photos_path+ to use in your code.
-
-NOTE: You can also use +:name_prefix+ with non-RESTful routes.
-
-h5. Using +:only+ and +:except+
-
-By default, Rails creates routes for all seven of the default actions (index, show, new, create, edit, update, and destroy) for every RESTful route in your application. You can use the +:only+ and +:except+ options to fine-tune this behavior. The +:only+ option specifies that only certain routes should be generated:
-
-
-resources :photos, :only => [:index, :show]
-
-
-With this declaration, a +GET+ request to +/photos+ would succeed, but a +POST+ request to +/photos+ (which would ordinarily be routed to the create action) will fail.
-
-The +:except+ option specifies a route or list of routes that should _not_ be generated:
-
-
-resources :photos, :except => :destroy
-
-
-In this case, all of the normal routes except the route for +destroy+ (a +DELETE+ request to +/photos/id +) will be generated.
-
-TIP: If your application has many RESTful routes, using +:only+ and +:except+ to generate only the routes that you actually need can cut down on memory use and speed up the routing process.
-
-h5. Changing Path Names for Resources
-
-Using +scope+, we can alter path names generated by resources:
-
-
-scope(:resources_path_names => { :new => "neu", :edit => "bearbeiten" }) do
- resources :categories, :path => "kategorien"
+scope :module => "admin" do
+ resources :posts, :comments
end
-With +scope+ defined, it now generates routes with customized path names.
+or, for a single case
-|_.HTTP verb|_.URL |_.controller |_.action |_:used for|
-|GET |/kategorien |Categories |index |display a list of all categories|
-|GET |/kategorien/neu |Categories |new |return an HTML form for creating a new category|
-|POST |/kategorien |Categories |create |create a new category|
-|GET |/kategorien/1 |Categories |show |display a specific category|
-|GET |/kategorien/:id/bearbeiten |Categories |edit |return an HTML form for editing a category|
-|PUT |/kategorien/1 |Categories |update |update a specific category|
-|DELETE |/kategorien/1 |Categories |destroy |delete a specific category|
+
+resources :posts, :module => "admin"
+
+
+If you want to route +/admin/photos+ to +PostsController+ (without the +Admin::+ module prefix), you could use
+
+
+scope "/admin" do
+ resources :posts, :comments
+end
+
+
+or, for a single case
+
+
+resources :posts, :path => "/admin"
+
+
+In each of these cases, the named routes remain the same as if you did not use +scope+. In the last case, the following URLs map to +PostsController+:
+
+|_. Verb |_.URL |_.action |_. helper |
+|GET |photos |index | photos_path |
+|GET |photos/new |new | photos_path |
+|POST |photos |create | photos_path |
+|GET |photos/1 |show | photo_path(id) |
+|GET |photos/1/edit |edit | dmin_photo_path(id) |
+|PUT |photos/1 |update | photo_path(id) |
+|DELETE |photos/1 |destroy | photo_path(id) |
h4. Nested Resources
@@ -399,7 +216,7 @@ class Ad < ActiveRecord::Base
end
-Each ad is logically subservient to one magazine. Nested routes allow you to capture this relationship in your routing. In this case, you might include this route declaration:
+Nested routes allow you to capture this relationship in your routing. In this case, you could include this route declaration:
resources :magazines do
@@ -407,31 +224,19 @@ resources :magazines do
end
-In addition to the routes for magazines, this declaration will also create routes for ads, each of which requires the specification of a magazine in the URL:
+In addition to the routes for magazines, this declaration will also route ads to an +AdsController+. The ad URLs require a magazine:
-|_.HTTP verb|_.URL |_.controller|_.action |_.used for|
-|GET |/magazines/1/ads |Ads |index |display a list of all ads for a specific magazine|
-|GET |/magazines/1/ads/new |Ads |new |return an HTML form for creating a new ad belonging to a specific magazine|
-|POST |/magazines/1/ads |Ads |create |create a new ad belonging to a specific magazine|
-|GET |/magazines/1/ads/1 |Ads |show |display a specific ad belonging to a specific magazine|
-|GET |/magazines/1/ads/1/edit |Ads |edit |return an HTML form for editing an ad belonging to a specific magazine|
-|PUT |/magazines/1/ads/1 |Ads |update |update a specific ad belonging to a specific magazine|
-|DELETE |/magazines/1/ads/1 |Ads |destroy |delete a specific ad belonging to a specific magazine|
+|_.Verb |_.URL |_.action |_.used for|
+|GET |/magazines/1/ads |index |display a list of all ads for a specific magazine|
+|GET |/magazines/1/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine|
+|POST |/magazines/1/ads |create |create a new ad belonging to a specific magazine|
+|GET |/magazines/1/ads/1 |show |display a specific ad belonging to a specific magazine|
+|GET |/magazines/1/ads/1/edit |edit |return an HTML form for editing an ad belonging to a specific magazine|
+|PUT |/magazines/1/ads/1 |update |update a specific ad belonging to a specific magazine|
+|DELETE |/magazines/1/ads/1 |destroy |delete a specific ad belonging to a specific magazine|
-This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+.
-
-h5(#nested-name-prefix). Using +:name_prefix+
-
-The +:name_prefix+ option overrides the automatically-generated prefix in nested route helpers. For example,
-
-
-resources :magazines do
- resources :ads, :name_prefix => 'periodical'
-end
-
-
-This will create routing helpers such as +periodical_ads_url+ and +periodical_edit_ad_path+.
+This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+. These helpers take an instance of Magazine as the first parameter (+magazine_ads_url(@magazine)+).
h5. Limits to Nesting
@@ -455,33 +260,9 @@ The corresponding route helper would be +publisher_magazine_photo_url+, requirin
TIP: _Resources should never be nested more than 1 level deep._
-h5. Shallow Nesting
+h4. Creating URLs From Objects
-The +:shallow+ option provides an elegant solution to the difficulties of deeply-nested routes. If you specify this option at any level of routing, then paths for nested resources which reference a specific member (that is, those with an +:id+ parameter) will not use the parent path prefix or name prefix. To see what this means, consider this set of routes:
-
-
-resources :publishers, :shallow => true do
- resources :magazines do
- resources :photos
- end
-end
-
-
-This will enable recognition of (among others) these routes:
-
-
-/publishers/1 ==> publisher_path(1)
-/publishers/1/magazines ==> publisher_magazines_path(1)
-/magazines/2 ==> magazine_path(2)
-/magazines/2/photos ==> magazines_photos_path(2)
-/photos/3 ==> photo_path(3)
-
-
-With shallow nesting, you need only supply enough information to uniquely identify the resource that you want to work with.
-
-h4. Route Generation from Arrays
-
-In addition to using the generated routing helpers, Rails can also generate RESTful routes from an array of parameters. For example, suppose you have a set of routes generated with these entries in routes.rb:
+In addition to using the routing helpers, Rails can also create URLs from an array of parameters. For example, suppose you have this set of routes:
resources :magazines do
@@ -489,49 +270,35 @@ resources :magazines do
end
-Rails will generate helpers such as magazine_ad_path that you can use in building links:
+When using +magazine_ad_path+, you can pass in instances of +Magazine+ and +Ad+ instead of the numeric IDs.
-
+
<%= link_to "Ad details", magazine_ad_path(@magazine, @ad) %>
-
+
-Another way to refer to the same route is with an array of objects:
+You can also use +url_for+ with a set of objects, and Rails will automatically determine which route you want:
-
+
+<%= link_to "Ad details", url_for(@magazine, @ad) %>
+
+
+In this case, Rails will see that +@magazine+ is a +Magazine+ and +@ad+ is an +Ad+ and will therefore use the +magazine_ad_path+ helper. In helpers like +link_to+, you can specify just the object in place of the full +url_for+ call:
+
+
<%= link_to "Ad details", [@magazine, @ad] %>
-
+
-This format is especially useful when you might not know until runtime which of several types of object will be used in a particular link.
+If you wanted to link to just a magazine, you could leave out the +Array+:
-h4. Namespaced Resources
+
+<%= link_to "Magazine details", @magazine %>
+
-It's possible to do some quite complex things by combining +scope+ and +:name_prefix+. For example, you can use the combination of these two options to move administrative resources to their own folder in your application:
-
-
-scope 'admin' do
- resources :photos, :name_prefix => "admin", :controller => 'admin/photos'
- scope 'photos' do
- resources :tags, :name_prefix => 'admin_photo', :controller => 'admin/photo_tags'
- resources :ratings, :name_prefix => 'admin_photo', :controller => 'admin/photo_ratings'
- end
-end
-
-
-The good news is that if you find yourself using this level of complexity, you can stop. Rails supports _namespaced resources_ to make placing resources in their own folder a snap. Here's the namespaced version of those same three routes:
-
-
-namespace :admin do
- resources :photos do
- resources :tags, :ratings
- end
-end
-
-
-As you can see, the namespaced version is much more succinct than the one that spells everything out - but it still creates the same routes. For example, you'll get +admin_photos_url+ that expects to find an +Admin::PhotosController+ and that matches +admin/photos+, and +admin_photos_ratings_path+ that matches +/admin/photos/_photo_id_/ratings+, expecting to use +Admin::RatingsController+. Even though you're not specifying +path_prefix+ explicitly, the routing code will calculate the appropriate +path_prefix+ from the route nesting.
+This allows you to treat instances of your models as URLs, and is a key advantage to using the resourceful style.
h4. Adding More RESTful Actions
-You are not limited to the seven routes that RESTful routing creates by default. If you like, you may add additional member routes (those which apply to a single instance of the resource), additional new routes (those that apply to creating a new resource), or additional collection routes (those which apply to the collection of resources as a whole).
+You are not limited to the seven routes that RESTful routing creates by default. If you like, you may add additional routes that apply to the collection or individual members of the collection.
h5. Adding Member Routes
@@ -545,9 +312,9 @@ resources :photos do
end
-This will enable Rails to recognize URLs such as +/photos/1/preview+ using the GET HTTP verb, and route them to the preview action of the Photos controller. It will also create the +preview_photo_url+ and +preview_photo_path+ route helpers.
+This will recognize +/photos/1/preview+ with GET, and route to the +preview+ action of +PhotosController+. It will also create the +preview_photo_url+ and +preview_photo_path+ helpers.
-Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use +get+, +put+, +post+, or +delete+ here. If you don't have multiple +member+ route, you can also passing +:on+ to the routing.
+Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use +get+, +put+, +post+, or +delete+ here. If you don't have multiple +member+ routes, you can also passing +:on+ to a route.
resources :photos do
@@ -557,7 +324,7 @@ end
h5. Adding Collection Routes
-To add a collection route, use the +:collection+ option:
+To add a route to the collection:
resources :photos do
@@ -567,9 +334,9 @@ resources :photos do
end
-This will enable Rails to recognize URLs such as +/photos/search+ using the GET HTTP verb, and route them to the search action of the Photos controller. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
+This will enable Rails to recognize URLs such as +/photos/search+ with GET, and route to the +search+ action of +PhotosController+. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
-Just as with member routes, you can passing +:on+ to the routing.
+Just as with member routes, you can pass +:on+ to a route.
resources :photos do
@@ -577,24 +344,17 @@ resources :photos do
end
-h5. Adding New Routes
-
-As of writing, Rails 3 has deprecated +:new+ option from routing. You will need to explicit define the route using +match+ method
-
-
-resources :photos
-match 'photos/new/upload' => 'photos#upload', :as => 'upload_new_photos'
-
-
h5. A Note of Caution
-If you find yourself adding many extra actions to a RESTful route, it's time to stop and ask yourself whether you're disguising the presence of another resource that would be better split off on its own. When the +member+ and +collection+ hashes become a dumping-ground, RESTful routes lose the advantage of easy readability that is one of their strongest points.
+If you find yourself adding many extra actions to a resourceful route, it's time to stop and ask yourself whether you're disguising the presence of another resource.
-h3. Regular Routes
+h3. Non-Resourceful Routes
-In addition to RESTful routing, Rails supports regular routing - a way to map URLs to controllers and actions. With regular routing, you don't get the masses of routes automatically generated by RESTful routing. Instead, you must set up each route within your application separately.
+In addition to resource routing, Rails has powerful support for routing arbitrary URLs to actions. Here, you don't get groups of routes automatically generated by resourceful routing. Instead, you set up each route within your application separately.
-While RESTful routing has become the Rails standard, there are still plenty of places where the simpler regular routing works fine. You can even mix the two styles within a single application. In general, you should prefer RESTful routing _when possible_, because it will make parts of your application easier to write. But there's no need to try to shoehorn every last piece of your application into a RESTful framework if that's not a good fit.
+While you should usually use resourceful routing, there are still many places where the simpler routing is more appropriate. There's no need to try to shoehorn every last piece of your application into a resourceful framework if that's not a good fit.
+
+In particular, simple routing makes it very easy to map legacy URLs to new Rails actions.
h4. Bound Parameters
@@ -604,80 +364,121 @@ When you set up a regular route, you supply a series of symbols that Rails maps
match ':controller(/:action(/:id))'
-If an incoming request of +/photos/show/1+ is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the +show+ action of the +Photos+ controller, and to make the final parameter (1) available as +params[:id]+. This route will also route the incoming request of +/photos+ to PhotosController, since +:action+ and +:id+ are optional parameters, denoted by parenthesis.
+If an incoming request of +/photos/show/1+ is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the +show+ action of the +PhotosController+, and to make the final parameter +"1"+ available as +params[:id]+. This route will also route the incoming request of +/photos+ to +PhotosController+, since +:action+ and +:id+ are optional parameters, denoted by parentheses.
-h4. Wildcard Components
+h4. Dynamic Segments
-You can set up as many wildcard symbols within a regular route as you like. Anything other than +:controller+ or +:action+ will be available to the matching action as part of the params hash. So, if you set up this route:
+You can set up as many dynamic segments within a regular route as you like. Anything other than +:controller+ or +:action+ will be available to the action as part of +params+. If you set up this route:
match ':controller/:action/:id/:user_id'
-An incoming URL of +/photos/show/1/2+ will be dispatched to the +show+ action of the +Photos+ controller. +params[:id]+ will be set to 1, and +params[:user_id]+ will be set to 2.
+An incoming URL of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
-h4. Static Text
+h4. Static Segments
-You can specify static text when creating a route. In this case, the static text is used only for matching the incoming requests:
+You can specify static segments when creating a route.
match ':controller/:action/:id/with_user/:user_id'
-This route would respond to URLs such as +/photos/show/1/with_user/2+.
+This route would respond to URLs such as +/photos/show/1/with_user/2+. In this case, +params+ would be { :controller => "photos", :action => "show", :id => "1", :user_id => "2" } .
-h4. Querystring Parameters
+h4. The Query String
-Rails routing automatically picks up querystring parameters and makes them available in the +params+ hash. For example, with this route:
+The +params+ will also include any parameters from the query string. For example, with this route:
match ':controller/:action/:id
-An incoming URL of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params[:id]+ will be set to 1, and +params[:user_id]+ will be equal to 2.
+An incoming URL of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be { :controller => "photos", :action => "show", :id => "1", :user_id => "2" } .
h4. Defining Defaults
-You do not need to explicitly use the +:controller+ and +:action+ symbols within a route. You can supply defaults for these two parameters by putting it after +=>+:
+You do not need to explicitly use the +:controller+ and +:action+ symbols within a route. You can supply them as defaults:
match 'photos/:id' => 'photos#show'
-With this route, an incoming URL of +/photos/12+ would be dispatched to the +show+ action within the +Photos+ controller.
+With this route, Rails will match an incoming URL of +/photos/12+ to the +show+ action of +PhotosController+.
-You can also define other defaults in a route by supplying a hash for the +:defaults+ option. This even applies to parameters that are not explicitly defined elsewhere in the route. For example:
+You can also define other defaults in a route by supplying a hash for the +:defaults+ option. This even applies to parameters that you do not specify as dynamic segments. For example:
match 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' }
-With this route, an incoming URL of +photos/12+ would be dispatched to the +show+ action within the +Photos+ controller, and +params[:format]+ will be set to +jpg+.
+Rails would match +photos/12+ to the +show+ action of +PhotosController+, and set +params[:format]+ to +"jpg"+.
-h4. Named Routes
+h4. Naming Routes
-Regular routes need not use the +connect+ method. You can use any other name here to create a _named route_. For example,
+You can specify a name for any route using the +:as+ option.
match 'logout' => 'sessions#destroy', :as => :logout
-This will do two things. First, requests to +/logout+ will be sent to the +destroy+ action of the +Sessions+ controller. Second, Rails will maintain the +logout_path+ and +logout_url+ helpers for use within your code.
+This will create +logout_path+ and +logout_url+ as named helpers in your application. Calling +logout_path+ will return +/logout+
-h4. Route Constraints
+h4. Segment Constraints
-You can use the +:constraints+ option to enforce a format for any parameter in a route:
+You can use the +:constraints+ option to enforce a format for a dynamic segment:
match 'photo/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
-This route would respond to URLs such as +/photo/A12345+. You can more succinctly express the same route this way:
+This route would match URLs such as +/photo/A12345+. You can more succinctly express the same route this way:
match 'photo/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+h4. Request-Based Constraints
+
+You can also constrain a route based on any method on the Request object that returns a +String+.
+
+You specify a request-based constraint the same way that you specify a segment constraint:
+
+
+match "photo", :constraints => {:subdomain => "admin"}
+
+
+You can also specify constrains in a block form:
+
+
+namespace "admin" do
+ constraints :subdomain => "admin" do
+ resources :photos
+ end
+end
+
+
+h4. Advanced Constraints
+
+If you have a more advanced constraint, you can provide an object that responds to +matches?+ that Rails should use. Let's say you wanted to route all users on a blacklist to the +BlacklistController+. You could do:
+
+
+class BlacklistConstraint
+ def initialize
+ @ips = Blacklist.retrieve_ips
+ end
+
+ def matches?(request)
+ @ips.include?(request.remote_ip)
+ end
+end
+
+TwitterClone::Application.routes.draw do
+ match "*path" => "blacklist#index",
+ :constraints => BlacklistConstraint.new
+end
+
+
h4. Route Globbing
Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example
@@ -686,89 +487,244 @@ Route globbing is a way to specify that a particular parameter should be matched
match 'photo/*other' => 'photos#unknown'
-This route would match +photo/12+ or +/photo/long/path/to/12+ equally well, creating an array of path segments as the value of +params[:other]+.
+This route would match +photo/12+ or +/photo/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+.
-h3. Formats and +respond_to+
+h4. Redirection
-There's one more way in which routing can do different things depending on differences in the incoming HTTP request: by issuing a response that corresponds to what the request specifies that it will accept. In Rails routing, you can control this with the special +:format+ parameter in the route.
-
-For instance, consider the second of the default routes in the boilerplate +routes.rb+ file:
+You can redirect any path to another path using the +redirect+ helper in your router:
-match ':controller(/:action(/:id(.:format)))'
+match "/stories" => redirect("/posts")
-This route matches requests such as +/photo/edit/1.xml+ or +/photo/show/2.rss+. Within the appropriate action code, you can issue different responses depending on the requested format:
+You can also reuse dynamic segments from the match in the path to redirect to:
-respond_to do |format|
- format.html # return the default template for HTML
- format.xml { render :xml => @photo.to_xml }
-end
+match "/stories/:name" => redirect("/posts/%{name}")
-h4. Specifying the Format with an HTTP Header
-
-If there is no +:format+ parameter in the route, Rails will automatically look at the HTTP Accept header to determine the desired format.
-
-h4. Recognized MIME types
-
-By default, Rails recognizes +html+, +text+, +json+, +csv+, +xml+, +rss+, +atom+, and +yaml+ as acceptable response types. If you need types beyond this, you can register them in your environment:
+You can also provide a block to redirect, which receives the params and (optionally) the request object:
-Mime::Type.register "image/jpg", :jpg
+match "/stories/:name" => redirect {|params| "/posts/#{params[:name].pluralize}" }
+match "/stories" => redirect {|p, req| "/posts/#{req.subdomain}" }
-h3. The Default Routes
+In all of these cases, if you don't provide the leading host (+http://www.example.com+), Rails will take those details from the current request.
-When you create a new Rails application, +routes.rb+ is initialized with a default route:
+h4. Routing to Rack Applications
+
+Instead of a String, like +"posts#index"+, which corresponds to the +index+ action in the +PostsController+, you can specify any Rack application as the endpoint for a matcher.
-match ':controller(/:action(/:id(.:format)))'
+match "/application.js" => Sprockets
-These routes provide reasonable defaults for many URLs, if you're not using RESTful routing.
+As long as +Sprockets+ responds to +call+ and returns a [status, headers, body] , the router won't know the difference between the Rack application and an action.
-NOTE: The default routes will make every action of every controller in your application accessible to GET requests. If you've designed your application to make consistent use of RESTful and named routes, you should comment out the default routes to prevent access to your controllers through the wrong verbs. If you've had the default routes enabled during development, though, you need to be sure that you haven't unwittingly depended on them somewhere in your application - otherwise you may find mysterious failures when you disable them.
-
-h3. The Empty Route
-
-Don't confuse the default routes with the empty route. The empty route has one specific purpose: to route requests that come in to the root of the web site. For example, if your site is example.com, then requests to +http://example.com+ or +http://example.com/+ will be handled by the empty route.
+NOTE: For the curious, +"posts#index"+ actually expands out to +PostsController.action(:index)+, which returns a valid Rack application.
h4. Using +root+
-The preferred way to set up the empty route is with the +root+ command:
+You can specify what Rails should route +"/"+ to with the +root+ method:
root :to => 'pages#main'
-The use of the +root+ method tells Rails that this route applies to requests for the root of the site.
+You should put the +root+ route at the end of the file.
-Because of the top-down processing of the file, the named route must be specified _before_ the call to +root+.
+h3. Customizing Resourceful Routes
-h4. Connecting the Empty String
+While the default routes and helpers generated by +resources :posts+ will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers.
-You can also specify an empty route by explicitly connecting the empty string:
+h4. Specifying a Controller to Use
+
+The +:controller+ option lets you explicitly specify a controller to use for the resource. For example:
-match '' => 'pages#main'
+resources :photos, :controller => "images"
-TIP: If the empty route does not seem to be working in your application, make sure that you have deleted the file +public/index.html+ from your Rails tree.
+will recognize incoming URLs beginning with +/photo+ but route to the +Images+ controller:
+
+|_. Verb |_.URL |_.action |
+|GET |/photos |index |
+|GET |/photos/new |new |
+|POST |/photos |create |
+|GET |/photos/1 |show |
+|GET |/photos/1/edit |edit |
+|PUT |/photos/1 |update |
+|DELETE |/photos/1 |destroy |
+
+NOTE: Use +photos_path+, +new_photos_path+, etc. to generate URLs for this resource.
+
+h4. Specifying Constraints
+
+You can use the +:constraints+ option to specify a required format on the implicit +id+. For example:
+
+
+resources :photos, :constraints => {:id => /[A-Z][A-Z][0-9]+/}
+
+
+This declaration constrains the +:id+ parameter to match the supplied regular expression. So, in this case, the router would no longer match +/photos/1+ to this route. Instead, +/photos/RR27+ would match.
+
+You can specify a single constraint to a apply to a number of routes by using the block form:
+
+
+constraints(:id => /[A-Z][A-Z][0-9]+/) do
+ resources :photos
+ resources :accounts
+end
+
+
+NOTE: Of course, you can use the more advanced constraints available in non-resourceful routes in this context
+
+h4. Overriding the Named Helpers
+
+The +:as+ option lets you override the normal naming for the named route helpers. For example:
+
+
+resources :photos, :as => "images"
+
+
+will recognize incoming URLs beginning with +/photos+ and route the requests to +PhotosController+:
+
+|_.HTTP verb|_.URL |_.action |_.named helper |
+|GET |/photos |index | images_path_ |
+|GET |/photos/new |new | new_image_path |
+|POST |/photos |create | images_path |
+|GET |/photos/1 |show | image_path |
+|GET |/photos/1/edit |edit | edit_image_path |
+|PUT |/photos/1 |update | image_path |
+|DELETE |/photos/1 |destroy | image_path |
+
+h4. Overriding the +new+ and +edit+ Segments
+
+The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs:
+
+
+resources :photos, :path_names => { :new => 'make', :edit => 'change' }
+
+
+This would cause the routing to recognize URLs such as
+
+
+/photos/make
+/photos/1/change
+
+
+NOTE: The actual action names aren't changed by this option. The two URLs shown would still route to the new and edit actions.
+
+TIP: If you find yourself wanting to change this option uniformly for all of your routes, you can use a scope:
+
+
+scope :path_names => { :new => "make" } do
+ # rest of your routes
+end
+
+
+h4. Overriding the Named Helper Prefix
+
+You can use the :name_prefix option to add a prefix to the named route helpers that Rails generates for a route. You can use this option to prevent collisions between routes using a path scope.
+
+
+scope "admin" do
+ resources :photos, :name_prefix => "admin"
+end
+
+resources :photos
+
+
+This will provide route helpers such as +photographer_photos_path+.
+
+You could specify a name prefix to use for a group of routes in the scope:
+
+
+scope "admin", :name_prefix => "admin" do
+ resources :photos, :accounts
+end
+
+resources :photos, :accounts
+
+
+NOTE: The +namespace+ scope will automatically add a +:name_prefix+ as well as +:module+ and +:path+ prefixes.
+
+h4. Restricting the Routes Created
+
+By default, Rails creates routes for all seven of the default actions (index, show, new, create, edit, update, and destroy) for every RESTful route in your application. You can use the +:only+ and +:except+ options to fine-tune this behavior. The +:only+ option tells Rails to create only the specified routes:
+
+
+resources :photos, :only => [:index, :show]
+
+
+Now, a +GET+ request to +/photos+ would succeed, but a +POST+ request to +/photos+ (which would ordinarily be routed to the +create+ action) will fail.
+
+The +:except+ option specifies a route or list of routes that Rails should _not_ create:
+
+
+resources :photos, :except => :destroy
+
+
+In this case, Rails will create all of the normal routes except the route for +destroy+ (a +DELETE+ request to +/photos/:id+).
+
+TIP: If your application has many RESTful routes, using +:only+ and +:except+ to generate only the routes that you actually need can cut down on memory use and speed up the routing process.
+
+h4. Translated Paths
+
+Using +scope+, we can alter path names generated by resources:
+
+
+scope(:path_names => { :new => "neu", :edit => "bearbeiten" }) do
+ resources :categories, :path => "kategorien"
+end
+
+
+Rails now creates routes to the +CategoriesControlleR+.
+
+|_.HTTP verb|_.URL |_.action |
+|GET |/kategorien |index |
+|GET |/kategorien/neu |new |
+|POST |/kategorien |create |
+|GET |/kategorien/1 |show |
+|GET |/kategorien/:id/bearbeiten |edit |
+|PUT |/kategorien/1 |update |
+|DELETE |/kategorien/1 |destroy |
+
+h4. Overriding the Singular Form
+
+If you want to customize the singular name of the route in the named helpers, you can use the +:singular+ option.
+
+
+resources :teeth, :singular => "tooth"
+
+
+TIP: If you want to define the singular form of a word for your entire application, you should add additional rules to the +Inflector+ instead.
+
+h4(#nested-name-prefix). Using +:name_prefix+ in Nested Resources
+
+The +:name_prefix+ option overrides the automatically-generated prefix for the parent resource in nested route helpers. For example,
+
+
+resources :magazines do
+ resources :ads, :name_prefix => 'periodical'
+end
+
+
+This will create routing helpers such as +periodical_ads_url+ and +periodical_edit_ad_path+.
h3. Inspecting and Testing Routes
-Routing in your application should not be a "black box" that you never open. Rails offers built-in tools for both inspecting and testing routes.
+Rails offers facilities for inspecting and testing your routes.
h4. Seeing Existing Routes with +rake+
-If you want a complete list of all of the available routes in your application, run the +rake routes+ command. This will dump all of your routes to the console, in the same order that they appear in +routes.rb+. For each route, you'll see:
+If you want a complete list of all of the available routes in your application, run +rake routes+ command. This will print all of your routes, in the same order that they appear in +routes.rb+. For each route, you'll see:
* The route name (if any)
* The HTTP verb used (if the route doesn't respond to all verbs)
-* The URL pattern
-* The routing parameters that will be generated by this URL
+* The URL pattern to match
+* The routing parameters for the route
For example, here's a small section of the +rake routes+ output for a RESTful route:
@@ -812,7 +768,7 @@ You can supply a +:method+ argument to specify the HTTP verb:
assert_recognizes({ :controller => "photos", :action => "create" }, { :path => "photos", :method => :post })
-You can also use the RESTful helpers to test recognition of a RESTful route:
+You can also use the resourceful helpers to test recognition of a RESTful route:
assert_recognizes new_photo_url, { :path => "photos", :method => :post }
@@ -830,6 +786,7 @@ h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/3
+* April 10, 2010: Updated guide to remove outdated and superfluous information, and to provide information about new features, by "Yehuda Katz":http://www.yehudakatz.com
* April 2, 2010: Updated guide to match new Routing DSL in Rails 3, by "Rizwan Reza":http://www.rizwanreza.com/
* Febuary 1, 2010: Modifies the routing documentation to match new routing DSL in Rails 3, by Prem Sichanugrist
* October 4, 2008: Added additional detail on specifying verbs for resource member/collection routes, by "Mike Gunderloy":credits.html#mgunderloy
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 9d02da104d..0611b2a9f5 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -79,6 +79,10 @@ module Rails
@_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development")
end
+ def env=(environment)
+ @_env = ActiveSupport::StringInquirer.new(environment)
+ end
+
def cache
RAILS_CACHE
end
@@ -88,11 +92,12 @@ module Rails
end
def public_path
- @@public_path ||= self.root ? File.join(self.root, "public") : "public"
+ application && application.paths.public.to_a.first
end
def public_path=(path)
- @@public_path = path
+ ActiveSupport::Deprecation.warn "Setting Rails.public_path= is deprecated. " <<
+ "Please set paths.public = in config/application.rb instead.", caller
end
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index d3e4742e8a..7ce3494fa6 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -6,21 +6,29 @@ module Rails
include ::Rails::Configuration::Deprecated
attr_accessor :allow_concurrency, :cache_classes, :cache_store,
- :secret_token, :consider_all_requests_local, :dependency_loading,
+ :encoding, :consider_all_requests_local, :dependency_loading,
:filter_parameters, :log_level, :logger, :metals,
:plugins, :preload_frameworks, :reload_engines, :reload_plugins,
- :serve_static_assets, :time_zone, :whiny_nils
+ :secret_token, :serve_static_assets, :time_zone, :whiny_nils
def initialize(*)
super
- @allow_concurrency = false
- @filter_parameters = []
- @dependency_loading = true
+ @allow_concurrency = false
+ @consider_all_requests_local = false
+ @encoding = "utf-8"
+ @filter_parameters = []
+ @dependency_loading = true
@serve_static_assets = true
- @time_zone = "UTC"
- @consider_all_requests_local = true
@session_store = :cookie_store
@session_options = {}
+ @time_zone = "UTC"
+ end
+
+ def encoding=(value)
+ @encoding = value
+ if defined?(Encoding) && Encoding.respond_to?(:default_external=)
+ Encoding.default_external = value
+ end
end
def middleware
@@ -129,7 +137,7 @@ module Rails
def default_middleware_stack
ActionDispatch::MiddlewareStack.new.tap do |middleware|
- middleware.use('::ActionDispatch::Static', lambda { Rails.public_path }, :if => lambda { serve_static_assets })
+ middleware.use('::ActionDispatch::Static', lambda { paths.public.to_a.first }, :if => lambda { serve_static_assets })
middleware.use('::Rack::Lock', :if => lambda { !allow_concurrency })
middleware.use('::Rack::Runtime')
middleware.use('::Rails::Rack::Logger')
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 28e7ef947d..c5411a0331 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -43,7 +43,7 @@ module Rails
end
def load_once_paths
- @eager_load_paths ||= paths.load_once
+ @load_once_paths ||= paths.load_once
end
def load_paths
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 5f93dbf18f..9bc019b152 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -26,6 +26,7 @@ module Rails
:orm => '-o',
:resource_controller => '-c',
:scaffold_controller => '-c',
+ :stylesheets => '-y',
:template_engine => '-e',
:test_framework => '-t'
},
@@ -50,6 +51,7 @@ module Rails
:resource_controller => :controller,
:scaffold_controller => :scaffold_controller,
:singleton => false,
+ :stylesheets => true,
:test_framework => nil,
:template_engine => :erb
},
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index 01ec58c615..0615a34a85 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -1,5 +1,14 @@
<%%= form_for(@<%= singular_name %>) do |f| %>
- <%%= f.error_messages %>
+ <%% if @<%= singular_name %>.errors.any? %>
+
+
<%%= pluralize(@<%= singular_name %>.errors.count, "error") %> prohibited this <%= singular_name %> from being saved:
+
+ <%% @<%= singular_name %>.errors.full_messages.each do |msg| %>
+ <%%= msg %>
+ <%% end %>
+
+
+ <%% end %>
<% for attribute in attributes -%>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
index 24f13fc0f8..4dd2e6bf8c 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
@@ -1,3 +1,5 @@
+
<%%= notice %>
+
<% for attribute in attributes -%>
<%= attribute.human_name %>:
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index df6e98f38d..bb2a080286 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -78,7 +78,7 @@ module Rails::Generators
end
def create_app_files
- directory "app"
+ directory 'app'
end
def create_config_files
@@ -137,7 +137,7 @@ module Rails::Generators
end
def create_public_stylesheets_files
- directory "public/stylesheets"
+ empty_directory_with_gitkeep "public/stylesheets"
end
def create_prototype_files
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb
deleted file mode 100644
index 6b87d9d3ec..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
<%= controller_name.humanize %>: <%= action_name %>
- <%= stylesheet_link_tag 'application' %>
- <%= javascript_include_tag :defaults %>
- <%= csrf_meta_tag %>
-
-
-
-
<%= notice %>
-
<%= alert %>
-
-<%=raw yield %>
-
-
-
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
new file mode 100644
index 0000000000..1dd112b4a6
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -0,0 +1,14 @@
+
+
+
+
<%= app_const_base %>
+ <%%= stylesheet_link_tag :all %>
+ <%%= javascript_include_tag :defaults %>
+ <%%= csrf_meta_tag %>
+
+
+
+<%%= yield %>
+
+
+
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index bd4fedcdec..0066e2b0c2 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -46,7 +46,10 @@ module <%= app_const_base %>
# g.test_framework :test_unit, :fixture => true
# end
+ # Configure the default encoding used in templates for Ruby 1.9.
+ config.encoding = "utf-8"
+
# Configure sensitive parameters which will be filtered from the log file.
- config.filter_parameters << :password
+ config.filter_parameters += [:password]
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js
index 7342e1b830..c5fa02ae35 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js
+++ b/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js
@@ -1,15 +1,8 @@
document.observe("dom:loaded", function() {
- var authToken = $$('meta[name=csrf-token]').first().readAttribute('content'),
- authParam = $$('meta[name=csrf-param]').first().readAttribute('content'),
- formTemplate = '
\
- #{realmethod} \
- ',
- realmethodTemplate = '
';
-
function handleRemote(element) {
var method, url, params;
- if (element.tagName.toLowerCase() == 'form') {
+ if (element.tagName.toLowerCase() === 'form') {
method = element.readAttribute('method') || 'post';
url = element.readAttribute('action');
params = element.serialize(true);
@@ -39,65 +32,81 @@ document.observe("dom:loaded", function() {
element.fire("ajax:after");
}
+ function handleMethod(element) {
+ var method, url, token_name, token;
+
+ method = element.readAttribute('data-method');
+ url = element.readAttribute('href');
+ csrf_param = $$('meta[name=csrf-param]').first();
+ csrf_token = $$('meta[name=csrf-token]').first();
+
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
+ element.parentNode.appendChild(form);
+
+ if (method != 'post') {
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
+ form.appendChild(field);
+ }
+
+ if (csrf_param) {
+ var param = csrf_param.readAttribute('content');
+ var token = csrf_token.readAttribute('content');
+ var field = new Element('input', { type: 'hidden', name: param, value: token });
+ form.appendChild(field);
+ }
+
+ form.submit();
+ }
+
$(document.body).observe("click", function(event) {
- var message = event.element().readAttribute('data-confirm');
+ var message = event.findElement().readAttribute('data-confirm');
if (message && !confirm(message)) {
event.stop();
return false;
}
- var element = event.findElement("a[data-remote=true]");
+ var element = event.findElement("a[data-remote]");
if (element) {
handleRemote(element);
event.stop();
+ return true;
}
var element = event.findElement("a[data-method]");
- if (element && element.readAttribute('data-remote') != 'true') {
- var method = element.readAttribute('data-method'),
- piggyback = method.toLowerCase() != 'post',
- formHTML = formTemplate.interpolate({
- method: 'POST',
- realmethod: piggyback ? realmethodTemplate.interpolate({ method: method }) : '',
- action: element.readAttribute('href'),
- token: authToken,
- param: authParam
- });
-
- var form = new Element('div').update(formHTML).down().hide();
- this.insert({ bottom: form });
-
- form.submit();
+ if (element) {
+ handleMethod(element);
event.stop();
+ return true;
}
});
// TODO: I don't think submit bubbles in IE
$(document.body).observe("submit", function(event) {
- var message = event.element().readAttribute('data-confirm');
+ var element = event.findElement(),
+ message = element.readAttribute('data-confirm');
if (message && !confirm(message)) {
event.stop();
return false;
}
- var inputs = event.element().select("input[type=submit][data-disable-with]");
+ var inputs = element.select("input[type=submit][data-disable-with]");
inputs.each(function(input) {
input.disabled = true;
input.writeAttribute('data-original-value', input.value);
input.value = input.readAttribute('data-disable-with');
});
- var element = event.findElement("form[data-remote=true]");
+ var element = event.findElement("form[data-remote]");
if (element) {
handleRemote(element);
event.stop();
}
});
- $(document.body).observe("ajax:complete", function(event) {
- var element = event.element();
+ $(document.body).observe("ajax:after", function(event) {
+ var element = event.findElement();
- if (element.tagName.toLowerCase() == 'form') {
+ if (element.tagName.toLowerCase() === 'form') {
var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
inputs.each(function(input) {
input.value = input.readAttribute('data-original-value');
@@ -106,4 +115,4 @@ document.observe("dom:loaded", function() {
});
}
});
-});
+});
\ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory b/railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
similarity index 89%
rename from railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
rename to railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
index 8bf1192ffe..86564031f5 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
@@ -3,11 +3,13 @@ require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
+<% unless options[:skip_activerecord] -%>
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
fixtures :all
+<% end -%>
# Add more helper methods to be used by all tests here...
end
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index bd156f399c..779f933785 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -7,6 +7,7 @@ module Rails
remove_class_option :actions
hook_for :scaffold_controller, :required => true
+ hook_for :stylesheets
end
end
end
diff --git a/railties/lib/rails/generators/rails/stylesheets/USAGE b/railties/lib/rails/generators/rails/stylesheets/USAGE
new file mode 100644
index 0000000000..59e5495d0b
--- /dev/null
+++ b/railties/lib/rails/generators/rails/stylesheets/USAGE
@@ -0,0 +1,5 @@
+Description:
+ Copies scaffold stylesheets to public/stylesheets/.
+
+Examples:
+ `rails generate stylesheets`
diff --git a/railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb b/railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb
new file mode 100644
index 0000000000..ce68443c39
--- /dev/null
+++ b/railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb
@@ -0,0 +1,9 @@
+module Rails
+ module Generators
+ class StylesheetsGenerator < Base
+ def copy_stylesheets_file
+ template "scaffold.css", "public/stylesheets/scaffold.css" if behavior == :invoke
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/app/templates/public/stylesheets/application.css b/railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css
similarity index 88%
rename from railties/lib/rails/generators/rails/app/templates/public/stylesheets/application.css
rename to railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css
index ea3dc9b8b5..f3f46d8b98 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css
@@ -35,10 +35,10 @@ div.field, div.actions {
}
#errorExplanation {
- width: 400px;
+ width: 450px;
border: 2px solid red;
padding: 7px;
- padding-bottom: 12px;
+ padding-bottom: 0;
margin-bottom: 20px;
background-color: #f0f0f0;
}
@@ -49,16 +49,11 @@ div.field, div.actions {
padding: 5px 5px 5px 15px;
font-size: 12px;
margin: -7px;
+ margin-bottom: 0px;
background-color: #c00;
color: #fff;
}
-#errorExplanation p {
- color: #333;
- margin-bottom: 0;
- padding: 5px;
-}
-
#errorExplanation ul li {
font-size: 12px;
list-style: square;
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 57857fb911..23b8f92abd 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -52,7 +52,11 @@ task :test do
end
namespace :test do
- Rake::TestTask.new(:recent => "db:test:prepare") do |t|
+ task :prepare do
+ # Placeholder task for other Railtie and plugins to enhance. See Active Record for an example.
+ end
+
+ Rake::TestTask.new(:recent => "test:prepare") do |t|
since = TEST_CHANGES_SINCE
touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } +
recent_tests('app/models/**/*.rb', 'test/unit', since) +
@@ -63,7 +67,7 @@ namespace :test do
end
Rake::Task['test:recent'].comment = "Test recent changes"
- Rake::TestTask.new(:uncommitted => "db:test:prepare") do |t|
+ Rake::TestTask.new(:uncommitted => "test:prepare") do |t|
def t.file_list
if File.directory?(".svn")
changed_since_checkin = silence_stderr { `svn status` }.map { |path| path.chomp[7 .. -1] }
@@ -86,32 +90,32 @@ namespace :test do
end
Rake::Task['test:uncommitted'].comment = "Test changes since last checkin (only Subversion and Git)"
- Rake::TestTask.new(:units => "db:test:prepare") do |t|
+ Rake::TestTask.new(:units => "test:prepare") do |t|
t.libs << "test"
t.pattern = 'test/unit/**/*_test.rb'
end
Rake::Task['test:units'].comment = "Run the unit tests in test/unit"
- Rake::TestTask.new(:functionals => "db:test:prepare") do |t|
+ Rake::TestTask.new(:functionals => "test:prepare") do |t|
t.libs << "test"
t.pattern = 'test/functional/**/*_test.rb'
end
Rake::Task['test:functionals'].comment = "Run the functional tests in test/functional"
- Rake::TestTask.new(:integration => "db:test:prepare") do |t|
+ Rake::TestTask.new(:integration => "test:prepare") do |t|
t.libs << "test"
t.pattern = 'test/integration/**/*_test.rb'
end
Rake::Task['test:integration'].comment = "Run the integration tests in test/integration"
- Rake::TestTask.new(:benchmark => 'db:test:prepare') do |t|
+ Rake::TestTask.new(:benchmark => 'test:prepare') do |t|
t.libs << 'test'
t.pattern = 'test/performance/**/*_test.rb'
t.options = '-- --benchmark'
end
Rake::Task['test:benchmark'].comment = 'Benchmark the performance tests'
- Rake::TestTask.new(:profile => 'db:test:prepare') do |t|
+ Rake::TestTask.new(:profile => 'test:prepare') do |t|
t.libs << 'test'
t.pattern = 'test/performance/**/*_test.rb'
end
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index 7c47cbeabd..c10876134a 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta2"
+ BUILD = "beta3"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 90f2e2b370..8bf0f09d6b 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -172,6 +172,27 @@ module ApplicationTests
assert $prepared
end
+ test "config.encoding sets the default encoding" do
+ add_to_config <<-RUBY
+ config.encoding = "utf-8"
+ RUBY
+
+ require "#{app_path}/config/application"
+
+ unless RUBY_VERSION < '1.9'
+ assert_equal Encoding.find("utf-8"), Encoding.default_external
+ end
+ end
+
+ test "config.paths.public sets Rails.public_path" do
+ add_to_config <<-RUBY
+ config.paths.public = "somewhere"
+ RUBY
+
+ require "#{app_path}/config/application"
+ assert_equal File.join(app_path, "somewhere"), Rails.public_path
+ end
+
def make_basic_app
require "rails"
require "action_controller/railtie"
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 64579c1205..24e6d541c2 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -54,8 +54,8 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_application_controller_and_layout_files
run_generator
assert_file "app/controllers/application_controller.rb", /layout 'application'/
- assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag 'application'/
- assert_file "public/stylesheets/application.css"
+ assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag :all/
+ assert_no_file "public/stylesheets/application.css"
end
def test_name_collision_raises_an_error
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index f5af137ced..8040b22fe6 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -66,6 +66,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
new
show
).each { |view| assert_file "app/views/users/#{view}.html.erb" }
+ assert_no_file "app/views/layouts/users.html.erb"
end
def test_functional_tests
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 3cad65f503..e8e622fe5c 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -70,10 +70,14 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
show
_form
).each { |view| assert_file "app/views/product_lines/#{view}.html.erb" }
+ assert_no_file "app/views/layouts/product_lines.html.erb"
# Helpers
assert_file "app/helpers/product_lines_helper.rb"
assert_file "test/unit/helpers/product_lines_helper_test.rb"
+
+ # Stylesheets
+ assert_file "public/stylesheets/scaffold.css"
end
def test_scaffold_on_revoke
@@ -97,9 +101,13 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
# Views
assert_no_file "app/views/product_lines"
+ assert_no_file "app/views/layouts/product_lines.html.erb"
# Helpers
assert_no_file "app/helpers/product_lines_helper.rb"
assert_no_file "test/unit/helpers/product_lines_helper_test.rb"
+
+ # Stylesheets (should not be removed)
+ assert_file "public/stylesheets/scaffold.css"
end
end
diff --git a/railties/test/generators/stylesheets_generator_test.rb b/railties/test/generators/stylesheets_generator_test.rb
new file mode 100644
index 0000000000..aaeb686daa
--- /dev/null
+++ b/railties/test/generators/stylesheets_generator_test.rb
@@ -0,0 +1,17 @@
+require 'generators/generators_test_helper'
+require 'rails/generators/rails/stylesheets/stylesheets_generator'
+
+class StylesheetsGeneratorTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+
+ def test_copy_stylesheets
+ run_generator
+ assert_file "public/stylesheets/scaffold.css"
+ end
+
+ def test_stylesheets_are_not_deleted_on_revoke
+ run_generator
+ run_generator [], :behavior => :revoke
+ assert_file "public/stylesheets/scaffold.css"
+ end
+end
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index 546bf5e143..2accaca855 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -125,5 +125,16 @@ module RailtiesTest
require "#{app_path}/config/environment"
assert $ran_block
end
+
+ test "we can change our environment if we want to" do
+ begin
+ original_env = Rails.env
+ Rails.env = 'foo'
+ assert_equal('foo', Rails.env)
+ ensure
+ Rails.env = original_env
+ assert_equal(original_env, Rails.env)
+ end
+ end
end
end
diff --git a/version.rb b/version.rb
index 7c47cbeabd..c10876134a 100644
--- a/version.rb
+++ b/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta2"
+ BUILD = "beta3"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end