From 68fdfde0039f44019b6967a5565b9d390f747395 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 18 Jan 2009 19:21:34 +0000 Subject: Improve HTTP Basic authentication tests --- .../test/controller/http_authentication_test.rb | 54 ------------- .../controller/http_basic_authentication_test.rb | 88 ++++++++++++++++++++++ 2 files changed, 88 insertions(+), 54 deletions(-) delete mode 100644 actionpack/test/controller/http_authentication_test.rb create mode 100644 actionpack/test/controller/http_basic_authentication_test.rb diff --git a/actionpack/test/controller/http_authentication_test.rb b/actionpack/test/controller/http_authentication_test.rb deleted file mode 100644 index c0069e8032..0000000000 --- a/actionpack/test/controller/http_authentication_test.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'abstract_unit' - -class HttpBasicAuthenticationTest < Test::Unit::TestCase - include ActionController::HttpAuthentication::Basic - - class DummyController - attr_accessor :headers, :renders, :request - - def initialize - @headers, @renders = {}, [] - @request = ActionController::TestRequest.new - end - - def render(options) - self.renders << options - end - end - - def setup - @controller = DummyController.new - @credentials = ActionController::HttpAuthentication::Basic.encode_credentials("dhh", "secret") - end - - def test_successful_authentication - login = Proc.new { |user_name, password| user_name == "dhh" && password == "secret" } - set_headers - assert authenticate(@controller, &login) - - set_headers '' - assert_nothing_raised do - assert !authenticate(@controller, &login) - end - - set_headers nil - set_headers @credentials, 'REDIRECT_X_HTTP_AUTHORIZATION' - assert authenticate(@controller, &login) - end - - def test_failing_authentication - set_headers - assert !authenticate(@controller) { |user_name, password| user_name == "dhh" && password == "incorrect" } - end - - def test_authentication_request - authentication_request(@controller, "Megaglobalapp") - assert_equal 'Basic realm="Megaglobalapp"', @controller.headers["WWW-Authenticate"] - assert_equal :unauthorized, @controller.renders.first[:status] - end - - private - def set_headers(value = @credentials, name = 'HTTP_AUTHORIZATION') - @controller.request.env[name] = value - end -end diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb new file mode 100644 index 0000000000..08a25bfdb8 --- /dev/null +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -0,0 +1,88 @@ +require 'abstract_unit' + +class DummyController < ActionController::Base + before_filter :authenticate, :only => :index + before_filter :authenticate_with_request, :only => :display + + def index + render :text => "Hello Secret" + end + + def display + render :text => 'Definitely Maybe' + end + + private + + def authenticate + authenticate_or_request_with_http_basic do |username, password| + username == 'lifo' && password == 'world' + end + end + + def authenticate_with_request + if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' } + @logged_in = true + else + request_http_basic_authentication("SuperSecret") + end + end +end + +class HttpBasicAuthenticationTest < ActionController::TestCase + AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'] + + tests DummyController + + AUTH_HEADERS.each do |header| + test "successful authentication with #{header.downcase}" do + @request.env[header] = encode_credentials('lifo', 'world') + get :index + + assert_response :success + assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}" + end + end + + AUTH_HEADERS.each do |header| + test "unsuccessful authentication with #{header.downcase}" do + @request.env[header] = encode_credentials('h4x0r', 'world') + get :index + + assert_response :unauthorized + assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}" + end + end + + test "authentication request without credential" do + get :display + + assert_response :unauthorized + assert_equal "HTTP Basic: Access denied.\n", @response.body + assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate'] + end + + test "authentication request with invalid credential" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials('pretty', 'foo') + get :display + + assert_response :unauthorized + assert_equal "HTTP Basic: Access denied.\n", @response.body + assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate'] + end + + test "authentication request with valid credential" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials('pretty', 'please') + get :display + + assert_response :success + assert assigns(:logged_in) + assert_equal 'Definitely Maybe', @response.body + end + + private + + def encode_credentials(username, password) + "Basic #{ActiveSupport::Base64.encode64("#{username}:#{password}")}" + end +end -- cgit v1.2.3 From 9cefd5ea0c21595d73762b5d60a760a3ed9fe8bf Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 19 Jan 2009 18:53:14 +0000 Subject: Deprecate ActionController::Base#session_enabled? --- actionpack/lib/action_controller/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 7a380bd1fb..50b965ce4c 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -644,7 +644,7 @@ module ActionController #:nodoc: end def session_enabled? - request.session_options && request.session_options[:disabled] != false + ActiveSupport::Deprecation.warn("Sessions are now lazy loaded. So if you don't access them, consider them disabled.", caller) end self.view_paths = [] -- cgit v1.2.3 From c090e5e0755bea3a7cd7135329f8dae6094810b6 Mon Sep 17 00:00:00 2001 From: Cody Fauser Date: Tue, 20 Jan 2009 11:50:43 -0600 Subject: Restore cookie store httponly default to true. Remove extraneous dup of options on initialization [#1784 state:resolved] Signed-off-by: Joshua Peek --- actionpack/lib/action_controller/session/cookie_store.rb | 4 +--- actionpack/test/controller/session/cookie_store_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb index e061c4d4a1..6ad6369950 100644 --- a/actionpack/lib/action_controller/session/cookie_store.rb +++ b/actionpack/lib/action_controller/session/cookie_store.rb @@ -45,7 +45,7 @@ module ActionController :domain => nil, :path => "/", :expire_after => nil, - :httponly => false + :httponly => true }.freeze ENV_SESSION_KEY = "rack.session".freeze @@ -56,8 +56,6 @@ module ActionController class CookieOverflow < StandardError; end def initialize(app, options = {}) - options = options.dup - # Process legacy CGI options options = options.symbolize_keys if options.has_key?(:session_path) diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb index d349c18d1d..b6a38f47aa 100644 --- a/actionpack/test/controller/session/cookie_store_test.rb +++ b/actionpack/test/controller/session/cookie_store_test.rb @@ -94,7 +94,7 @@ class CookieStoreTest < ActionController::IntegrationTest with_test_route_set do get '/set_session_value' assert_response :success - assert_equal ["_myapp_session=#{response.body}; path=/"], + assert_equal ["_myapp_session=#{response.body}; path=/; httponly"], headers['Set-Cookie'] end end @@ -148,7 +148,7 @@ class CookieStoreTest < ActionController::IntegrationTest get '/set_session_value' assert_response :success session_payload = response.body - assert_equal ["_myapp_session=#{response.body}; path=/"], + assert_equal ["_myapp_session=#{response.body}; path=/; httponly"], headers['Set-Cookie'] get '/call_reset_session' -- cgit v1.2.3 From 01f06fc7f4dda52035d5a2273d402d8555a897a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Tue, 20 Jan 2009 12:38:25 -0600 Subject: Don't let empty Tempfiles come through as uploaded files [#1785 state:resolved] Signed-off-by: Joshua Peek --- actionpack/lib/action_controller/url_encoded_pair_parser.rb | 2 +- .../test/controller/request/multipart_params_parsing_test.rb | 7 +++++++ actionpack/test/fixtures/multipart/empty | 9 +++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 actionpack/test/fixtures/multipart/empty diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb index b63dca987d..93ab2255da 100644 --- a/actionpack/lib/action_controller/url_encoded_pair_parser.rb +++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb @@ -46,7 +46,7 @@ module ActionController when Array value.map { |v| get_typed_value(v) } when Hash - if value.has_key?(:tempfile) + if value.has_key?(:tempfile) && value[:tempfile].size > 0 upload = value[:tempfile] upload.extend(UploadedFile) upload.original_path = value[:filename] diff --git a/actionpack/test/controller/request/multipart_params_parsing_test.rb b/actionpack/test/controller/request/multipart_params_parsing_test.rb index d7ade40f71..18235845f3 100644 --- a/actionpack/test/controller/request/multipart_params_parsing_test.rb +++ b/actionpack/test/controller/request/multipart_params_parsing_test.rb @@ -101,6 +101,13 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest assert_equal 19756, files.size end + test "skips empty upload field" do + params = parse_multipart('empty') + assert_equal %w(files submit-name), params.keys.sort + assert_equal 'Larry', params['submit-name'] + assert_equal nil, params['file'] + end + test "uploads and reads binary file" do with_test_routing do fixture = FIXTURE_PATH + "/mona_lisa.jpg" diff --git a/actionpack/test/fixtures/multipart/empty b/actionpack/test/fixtures/multipart/empty new file mode 100644 index 0000000000..d66f4730f1 --- /dev/null +++ b/actionpack/test/fixtures/multipart/empty @@ -0,0 +1,9 @@ +--AaB03x +Content-Disposition: form-data; name="submit-name" + +Larry +--AaB03x +Content-Disposition: form-data; name="files"; filename="" + + +--AaB03x-- -- cgit v1.2.3 From 7e4d13d357b1e8bdf42e80359de0e480ec9f5008 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 20 Jan 2009 20:19:52 -0600 Subject: Add MiddlewareStack#swap config.middleware.swap ActionController::Session::CookieStore, MySessionStore --- actionpack/lib/action_controller/middleware_stack.rb | 15 ++++++++++----- actionpack/test/controller/middleware_stack_test.rb | 6 ++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_controller/middleware_stack.rb b/actionpack/lib/action_controller/middleware_stack.rb index b94bf6ec4a..dbc2fda41e 100644 --- a/actionpack/lib/action_controller/middleware_stack.rb +++ b/actionpack/lib/action_controller/middleware_stack.rb @@ -75,17 +75,22 @@ module ActionController block.call(self) if block_given? end - def insert(index, *objs) + def insert(index, *args, &block) index = self.index(index) unless index.is_a?(Integer) - objs = objs.map { |obj| Middleware.new(obj) } - super(index, *objs) + middleware = Middleware.new(*args, &block) + super(index, middleware) end alias_method :insert_before, :insert - def insert_after(index, *objs) + def insert_after(index, *args, &block) index = self.index(index) unless index.is_a?(Integer) - insert(index + 1, *objs) + insert(index + 1, *args, &block) + end + + def swap(target, *args, &block) + insert_before(target, *args, &block) + delete(target) end def use(*args, &block) diff --git a/actionpack/test/controller/middleware_stack_test.rb b/actionpack/test/controller/middleware_stack_test.rb index 5029f5f609..2a141697da 100644 --- a/actionpack/test/controller/middleware_stack_test.rb +++ b/actionpack/test/controller/middleware_stack_test.rb @@ -60,6 +60,12 @@ class MiddlewareStackTest < ActiveSupport::TestCase assert_equal BazMiddleware, @stack[2].klass end + test "swaps one middleware out for another" do + assert_equal FooMiddleware, @stack[0].klass + @stack.swap(FooMiddleware, BazMiddleware) + assert_equal BazMiddleware, @stack[0].klass + end + test "active returns all only enabled middleware" do assert_no_difference "@stack.active.size" do assert_difference "@stack.size" do -- cgit v1.2.3 From a8ad6568f9fe21668df9b6b631c0cd9783cb5ab3 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 20 Jan 2009 20:34:35 -0600 Subject: Allow empty files to be uploaded --- actionpack/lib/action_controller/url_encoded_pair_parser.rb | 2 +- .../test/controller/request/multipart_params_parsing_test.rb | 12 ++++++++++-- actionpack/test/fixtures/multipart/empty | 3 ++- actionpack/test/fixtures/multipart/none | 9 +++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 actionpack/test/fixtures/multipart/none diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb index 93ab2255da..57594c4259 100644 --- a/actionpack/lib/action_controller/url_encoded_pair_parser.rb +++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb @@ -46,7 +46,7 @@ module ActionController when Array value.map { |v| get_typed_value(v) } when Hash - if value.has_key?(:tempfile) && value[:tempfile].size > 0 + if value.has_key?(:tempfile) && value[:filename].any? upload = value[:tempfile] upload.extend(UploadedFile) upload.original_path = value[:filename] diff --git a/actionpack/test/controller/request/multipart_params_parsing_test.rb b/actionpack/test/controller/request/multipart_params_parsing_test.rb index 18235845f3..054519d0d2 100644 --- a/actionpack/test/controller/request/multipart_params_parsing_test.rb +++ b/actionpack/test/controller/request/multipart_params_parsing_test.rb @@ -101,11 +101,19 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest assert_equal 19756, files.size end - test "skips empty upload field" do + test "does not create tempfile if no file has been selected" do + params = parse_multipart('none') + assert_equal %w(files submit-name), params.keys.sort + assert_equal 'Larry', params['submit-name'] + assert_equal nil, params['files'] + end + + test "parses empty upload file" do params = parse_multipart('empty') assert_equal %w(files submit-name), params.keys.sort assert_equal 'Larry', params['submit-name'] - assert_equal nil, params['file'] + assert params['files'] + assert_equal "", params['files'].read end test "uploads and reads binary file" do diff --git a/actionpack/test/fixtures/multipart/empty b/actionpack/test/fixtures/multipart/empty index d66f4730f1..f0f79835c9 100644 --- a/actionpack/test/fixtures/multipart/empty +++ b/actionpack/test/fixtures/multipart/empty @@ -3,7 +3,8 @@ Content-Disposition: form-data; name="submit-name" Larry --AaB03x -Content-Disposition: form-data; name="files"; filename="" +Content-Disposition: form-data; name="files"; filename="file1.txt" +Content-Type: text/plain --AaB03x-- diff --git a/actionpack/test/fixtures/multipart/none b/actionpack/test/fixtures/multipart/none new file mode 100644 index 0000000000..d66f4730f1 --- /dev/null +++ b/actionpack/test/fixtures/multipart/none @@ -0,0 +1,9 @@ +--AaB03x +Content-Disposition: form-data; name="submit-name" + +Larry +--AaB03x +Content-Disposition: form-data; name="files"; filename="" + + +--AaB03x-- -- cgit v1.2.3 From ae3a93ad897fa9c481ee16454e9e29fe0a9e3493 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 21 Jan 2009 12:29:41 -0600 Subject: Missed RequestParser in ff0a267 --- actionpack/lib/action_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index dca07a0afc..3e77970367 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -63,7 +63,6 @@ module ActionController autoload :RecordIdentifier, 'action_controller/record_identifier' autoload :Request, 'action_controller/request' autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection' - autoload :RequestParser, 'action_controller/request_parser' autoload :Rescue, 'action_controller/rescue' autoload :Resources, 'action_controller/resources' autoload :Response, 'action_controller/response' -- cgit v1.2.3 From 82334a74311a3e0a8a1454d0a4a2ebf3c1138cea Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 21 Jan 2009 12:37:03 -0600 Subject: Only insert metal middleware if any exist --- railties/lib/initializer.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index f6b8899d58..dd4d483233 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -542,7 +542,9 @@ Run `rake gems:install` to install the missing gems. end def initialize_metal - configuration.middleware.insert_before(:"ActionController::RewindableInput", Rails::Rack::Metal) + configuration.middleware.insert_before( + :"ActionController::RewindableInput", + Rails::Rack::Metal, :if => Rails::Rack::Metal.metals.any?) end # Initializes framework-specific settings for each of the loaded frameworks -- cgit v1.2.3 From 73cc5f270a5c2a2eab76c6c02615fec608822494 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 21 Jan 2009 12:44:07 -0600 Subject: Setup ActiveRecord QueryCache middleware in the initializer --- actionpack/lib/action_controller/middlewares.rb | 8 +++----- railties/lib/initializer.rb | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_controller/middlewares.rb b/actionpack/lib/action_controller/middlewares.rb index 30dbc26f11..f9cfc2b18e 100644 --- a/actionpack/lib/action_controller/middlewares.rb +++ b/actionpack/lib/action_controller/middlewares.rb @@ -4,8 +4,6 @@ use "Rack::Lock", :if => lambda { use "ActionController::Failsafe" -use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) } - ["ActionController::Session::CookieStore", "ActionController::Session::MemCacheStore", "ActiveRecord::SessionStore"].each do |store| @@ -18,6 +16,6 @@ use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) } ) end -use ActionController::RewindableInput -use ActionController::ParamsParser -use Rack::MethodOverride +use "ActionController::RewindableInput" +use "ActionController::ParamsParser" +use "Rack::MethodOverride" diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index dd4d483233..be04873855 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -408,6 +408,7 @@ Run `rake gems:install` to install the missing gems. if configuration.frameworks.include?(:active_record) ActiveRecord::Base.configurations = configuration.database_configuration ActiveRecord::Base.establish_connection + configuration.middleware.use ActiveRecord::QueryCache end end -- cgit v1.2.3 From ccda96093a3bf3fb360f7c6d61bbbf341b2ae034 Mon Sep 17 00:00:00 2001 From: Josh Susser Date: Thu, 1 Jan 2009 21:30:42 -0800 Subject: Minor refactoring of validates_associated to replace #inject with #collect + #all? [#1686 state:committed] --- activerecord/lib/active_record/validations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 6a9690ba85..6d750accb0 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -904,7 +904,7 @@ module ActiveRecord configuration.update(attr_names.extract_options!) validates_each(attr_names, configuration) do |record, attr_name, value| - unless (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v } + unless (value.is_a?(Array) ? value : [value]).collect { |r| r.nil? || r.valid? }.all? record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) end end -- cgit v1.2.3 From 74fd2d34f2aeb45063545fcfff9675e2dd866b5c Mon Sep 17 00:00:00 2001 From: Rich Sturim Date: Wed, 21 Jan 2009 23:39:33 -0500 Subject: fixed a few filenames and paths --- .../creating_plugins/creating_plugins/lib/main.rb | 4 ++++ .../source/creating_plugins/generator_commands.txt | 2 +- .../guides/source/creating_plugins/migrations.txt | 23 ++-------------------- .../doc/guides/source/creating_plugins/models.txt | 2 +- .../doc/guides/source/creating_plugins/tasks.txt | 4 ++-- .../guides/source/creating_plugins/test_setup.txt | 3 --- 6 files changed, 10 insertions(+), 28 deletions(-) create mode 100644 railties/doc/guides/source/creating_plugins/creating_plugins/lib/main.rb diff --git a/railties/doc/guides/source/creating_plugins/creating_plugins/lib/main.rb b/railties/doc/guides/source/creating_plugins/creating_plugins/lib/main.rb new file mode 100644 index 0000000000..b0919153f1 --- /dev/null +++ b/railties/doc/guides/source/creating_plugins/creating_plugins/lib/main.rb @@ -0,0 +1,4 @@ +# To change this template, choose Tools | Templates +# and open the template in the editor. + +puts "Hello World" diff --git a/railties/doc/guides/source/creating_plugins/generator_commands.txt b/railties/doc/guides/source/creating_plugins/generator_commands.txt index f60ea3d8f1..6ded0dde59 100644 --- a/railties/doc/guides/source/creating_plugins/generator_commands.txt +++ b/railties/doc/guides/source/creating_plugins/generator_commands.txt @@ -120,7 +120,7 @@ Rails::Generator::Commands::List.send :include, Yaffle::Generator::Commands Rails::Generator::Commands::Update.send :include, Yaffle::Generator::Commands::Update ----------------------------------------------------------- -*vendor/plugins/yaffle/generators/yaffle/yaffle_route_generator.rb* +*vendor/plugins/yaffle/generators/yaffle_route/yaffle_route_generator.rb* [source, ruby] ----------------------------------------------------------- diff --git a/railties/doc/guides/source/creating_plugins/migrations.txt b/railties/doc/guides/source/creating_plugins/migrations.txt index e7d2e09069..2589169b38 100644 --- a/railties/doc/guides/source/creating_plugins/migrations.txt +++ b/railties/doc/guides/source/creating_plugins/migrations.txt @@ -28,26 +28,7 @@ Here are a few possibilities for how to allow developers to use your plugin migr === Create a custom rake task === -*vendor/plugins/yaffle/lib/db/migrate/20081116181115_create_birdhouses.rb:* - -[source, ruby] ----------------------------------------------- -class CreateBirdhouses < ActiveRecord::Migration - def self.up - create_table :birdhouses, :force => true do |t| - t.string :name - t.timestamps - end - end - - def self.down - drop_table :birdhouses - end -end ----------------------------------------------- - - -*vendor/plugins/yaffle/tasks/yaffle.rake:* +*vendor/plugins/yaffle/tasks/yaffle_tasks.rake:* [source, ruby] ---------------------------------------------- @@ -153,7 +134,7 @@ NOTE: the migration generator checks to see if a migation already exists, and it After running the test with 'rake' you can make it pass with: -*vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb* +*vendor/plugins/yaffle/generators/yaffle_migration/yaffle_migration_generator.rb* [source, ruby] ------------------------------------------------------------------ diff --git a/railties/doc/guides/source/creating_plugins/models.txt b/railties/doc/guides/source/creating_plugins/models.txt index 505ab44a71..1498b8e26c 100644 --- a/railties/doc/guides/source/creating_plugins/models.txt +++ b/railties/doc/guides/source/creating_plugins/models.txt @@ -20,7 +20,7 @@ vendor/plugins/yaffle/ As always, start with a test: -*vendor/plugins/yaffle/yaffle/woodpecker_test.rb:* +*vendor/plugins/yaffle/test/woodpecker_test.rb:* [source, ruby] ---------------------------------------------- diff --git a/railties/doc/guides/source/creating_plugins/tasks.txt b/railties/doc/guides/source/creating_plugins/tasks.txt index d848c2cfa1..edaffec194 100644 --- a/railties/doc/guides/source/creating_plugins/tasks.txt +++ b/railties/doc/guides/source/creating_plugins/tasks.txt @@ -1,10 +1,10 @@ == Rake tasks == -When you created the plugin with the built-in rails generator, it generated a rake file for you in 'vendor/plugins/yaffle/tasks/yaffle.rake'. Any rake task you add here will be available to the app. +When you created the plugin with the built-in rails generator, it generated a rake file for you in 'vendor/plugins/yaffle/tasks/yaffle_tasks.rake'. Any rake task you add here will be available to the app. Many plugin authors put all of their rake tasks into a common namespace that is the same as the plugin, like so: -*vendor/plugins/yaffle/tasks/yaffle.rake* +*vendor/plugins/yaffle/tasks/yaffle_tasks.rake* [source, ruby] --------------------------------------------------------- diff --git a/railties/doc/guides/source/creating_plugins/test_setup.txt b/railties/doc/guides/source/creating_plugins/test_setup.txt index 64236ff110..9ee98c9935 100644 --- a/railties/doc/guides/source/creating_plugins/test_setup.txt +++ b/railties/doc/guides/source/creating_plugins/test_setup.txt @@ -116,9 +116,6 @@ ActiveRecord::Schema.define(:version => 0) do t.string :last_tweet t.datetime :last_tweeted_at end - create_table :woodpeckers, :force => true do |t| - t.string :name - end end ---------------------------------------------- -- cgit v1.2.3 From 0eccdd9fb6a0e4df2904b28906fd5e7598266aa9 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 22 Jan 2009 11:47:06 +0100 Subject: Extended and clarified intro section of i18n guide. --- railties/doc/guides/source/i18n.txt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index 2638807761..415de9bdec 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -1,9 +1,16 @@ The Rails Internationalization (I18n) API ========================================= -The Ruby I18n (shorthand for _internationalization_) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for translating your application to a single custom language other than English or providing multi-language support in your application. +The Ruby I18n (shorthand for _internationalization_) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for *translating your application to a single custom language* other than English or for *providing multi-language support* in your application. -NOTE: The Ruby I18n framework provides you with all neccessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available. See Rails http://rails-i18n.org/wiki[I18n Wiki] for more information. +In the process of _localizing_ your application you'll probably want to do following two things: + +* Replace or supplement Rail's default locale -- eg. date and time formats, month names, ActiveRecord model names, etc +* Abstract texts in your application into keyed dictionaries -- eg. flash messages, static texts in your views, etc + +This guide will walk you through the I18n API and contains a tutorial how to internationalize a Rails application from the start. + +NOTE: The Ruby I18n framework provides you with all neccessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See Rails http://rails-i18n.org/wiki[I18n Wiki] for more information. == How I18n in Ruby on Rails works -- cgit v1.2.3 From 97d9585efa5c2ddff688fca22ef0db1fcfbe9fe9 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 22 Jan 2009 12:31:20 +0100 Subject: Complete "Setting locale from the domain name" in i18n guide --- railties/doc/guides/source/i18n.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index 415de9bdec..e44acea504 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -220,6 +220,17 @@ def extract_locale_from_subdomain end ------------------------------------------------------- +If your application includes a locale switching menu, you would then have something like this in it: + +[source, ruby] +------------------------------------------------------- +link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['REQUEST_URI']}") +------------------------------------------------------- + +assuming you would set +APP_CONFIG[:deutsch_website_url]+ to some value like +http://www.application.de+. + +This solution has aforementioned advantages, however, you may not be able or may not want to provide different localizations ("language versions") on different domains. The most obvious solution would be to include locale code in the URL params (or request path). + === Setting locale from the URL params * TODO : Based on *+default_url options+*, http://github.com/karmi/test_default_url_options/blob/master/app/controllers/application.rb#L22-26 -- cgit v1.2.3 From 6cd4fed61303426dbaf8cfa20c9c7738ea5e7305 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 22 Jan 2009 14:25:39 +0100 Subject: Finished section "Setting locale from the URL params" in i18n guide (+ various typos/formatting fixes) --- railties/doc/guides/source/i18n.txt | 64 +++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index e44acea504..b20e2172eb 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -123,7 +123,7 @@ If you want to translate your Rails application to a *single language other than However, you would probably like to *provide support for more locales* in your application. In such case, you need to set and pass the locale between requests. -WARNING: You may be tempted to store choosed locale in a _session_ or a _cookie_. *Do not do so*. The locale should be transparent and a part of the URL. This way you don't break people's basic assumptions about the web itself: if you send a URL of some page to a friend, she should see the same page, same content. A fancy word for this would be that you're being http://en.wikipedia.org/wiki/Representational_State_Transfer[_RESTful_]. There may be some exceptions to this rule, which are discussed below. +WARNING: You may be tempted to store choosed locale in a _session_ or a _cookie_. *Do not do so*. The locale should be transparent and a part of the URL. This way you don't break people's basic assumptions about the web itself: if you send a URL of some page to a friend, she should see the same page, same content. A fancy word for this would be that you're being http://en.wikipedia.org/wiki/Representational_State_Transfer[_RESTful_]. Read more about RESTful approach in http://www.infoq.com/articles/rest-introduction[Stefan Tilkov's articles]. There may be some exceptions to this rule, which are discussed below. The _setting part_ is easy. You can set locale in a +before_filter+ in the ApplicationController like this: @@ -132,11 +132,11 @@ The _setting part_ is easy. You can set locale in a +before_filter+ in the Appli before_filter :set_locale def set_locale # if params[:locale] is nil then I18n.default_locale will be used - I18n.locale = params[:locale] + I18n.locale = params[:locale] end ------------------------------------------------------- -This requires you to pass the locale as a URL query parameter as in +http://example.com/books?locale=pt+. (This is eg. Google's approach). So +http://localhost:3000?locale=pt+ will load the Portugese localization, whereas +http://localhost:3000?locale=de+ would load the German localization, and so on. +This requires you to pass the locale as a URL query parameter as in +http://example.com/books?locale=pt+. (This is eg. Google's approach). So +http://localhost:3000?locale=pt+ will load the Portugese localization, whereas +http://localhost:3000?locale=de+ would load the German localization, and so on. You may skip the next section and head over to the *Internationalize your application* section, if you want to try things out by manually placing locale in the URL and reloading the page. Of course, you probably don't want to manually include locale in every URL all over your application, or want the URLs look differently, eg. the usual +http://example.com/pt/books+ versus +http://example.com/en/books+. Let's discuss the different options you have. @@ -179,7 +179,7 @@ end === Setting locale from the domain name -One option you have is to set the locale from the domain name, where your application runs. For example, we want +www.example.com+ to load English (or default) locale, and +www.example.es+ to load Spanish locale. Thus the _top-level domain name_ is used for locale setting. This has several advantages: +One option you have is to set the locale from the domain name where your application runs. For example, we want +www.example.com+ to load English (or default) locale, and +www.example.es+ to load Spanish locale. Thus the _top-level domain name_ is used for locale setting. This has several advantages: * Locale is an _obvious_ part of the URL * People intuitively grasp in which language the content will be displayed @@ -233,14 +233,58 @@ This solution has aforementioned advantages, however, you may not be able or may === Setting locale from the URL params -* TODO : Based on *+default_url options+*, http://github.com/karmi/test_default_url_options/blob/master/app/controllers/application.rb#L22-26 +Most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the +I18n.locale = params[:locale]+ _before_filter_ in the first example. We would like to have URLs like +www.example.com/books?locale=ja+ or +www.example.com/ja/books+ in this case. -* TODO : Discussion of plugins (translate_routes and routing_filter) +This approach has almost the same set of advantages as setting the locale from domain name: namely that it's RESTful and in accord with rest of the World Wide Web. It does require a little bit more work to implement, though. +Getting the locale from +params+ and setting it accordingly is not hard; including it in every URL and thus *passing it through the requests* is. To include an explicit option in every URL (eg. +link_to( books_url(:locale => I18n.locale) )+) would be tedious and probably impossible, of course. -TIP: For setting locale from URL see http://rails-i18n.org/wiki/pages/how-to-encode-the-current-locale-in-the-url[How to encode the current locale in the URL] in the Rails i18n Wiki. +Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its http://api.rubyonrails.org/classes/ActionController/Base.html#M000515[+*ApplicationController#default_url_options*+], which is useful precisely in this scenario: it enables us to set "defaults" for http://api.rubyonrails.org/classes/ActionController/Base.html#M000503[+url_for+] and helper methods dependent on it (by implementing/overriding this method). -Now you've initialized I18n support for your application and told it which locale should be used. With that in place you're now ready for the really interesting stuff. +We can include something like this in our ApplicationController then: + +[source, ruby] +------------------------------------------------------- +# app/controllers/application_controller.rb +def default_url_options(options={}) + logger.debug "default_url_options is passed options: #{options.inspect}\n" + { :locale => I18n.locale } +end +------------------------------------------------------- + +Every helper method dependent on +url_for+ (eg. helpers for named routes like +root_path+ or +root_url+, resource routes like +books_path+ or +books_url+, etc.) will now *automatically include the locale in the query string*, like this: +http://localhost:3001/?locale=ja+. + +You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of application domain: and URLs should reflect this. + +You probably want URLs look like this: +www.example.com/en/books+ versus +www.example.com/nl/books+. This is achievable with the over-riding +default_url_options+ strategy: you just have to set up your routes with http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354[+path_prefix+] option in this way: + +[source, ruby] +------------------------------------------------------- +# config/routes.rb +map.resources :books, :path_prefix => '/:locale' +------------------------------------------------------- + +Now, when you call +books_path+ method you should get +"/en/books"+ (for the default locale). An URL like +http://localhost:3001/nl/books+ should load the Netherlands locale, then, and so on. + +Of course, you need to take special care of root URL (usually "homepage" or "dashboard") of your application. An URL like +http://localhost:3001/nl+ will not work automatically, because the +map.root :controller => "dashboard"+ declaration in your +routes.rb+ doesn't take locale into account. (And rightly so. There's only one "root" URL.) + +You would probably need to map URLs like these: + +[source, ruby] +------------------------------------------------------- +# config/routes.rb +map.dashboard '/:locale', :controller => "dashboard" +------------------------------------------------------- + +Do take special care about the *order of your routes*, so this route declaration does not "eat" other ones. (You may want to add it directly before the +map.root+ declaration.) + +IMPORTANT: This solution has currently one rather big *downside*. Due to the _default_url_options_ implementation, you have to pass the +:id+ option explicitely, like this: +link_to 'Show', book_url(:id => book)+ and not depend on Rails' magic in code like +link_to 'Show', book+. If this should be a problem, have a look on two plugins which simplify working with routes in this way: Sven Fuchs's http://github.com/svenfuchs/routing-filter/tree/master[_routing_filter_] and Raul Murciano's http://github.com/raul/translate_routes/tree/master[_translate_routes_]. See also the page http://rails-i18n.org/wiki/pages/how-to-encode-the-current-locale-in-the-url[How to encode the current locale in the URL] in the Rails i18n Wiki. + +=== Setting locale from the client supplied information + +# TODO: Accept-Language, GeoIP, etc. Explain why it is not such a good idea in most cases. + +OK! Now you've initialized I18n support for your application and told it which locale should be used and how to preserve it between requests. With that in place you're now ready for the really interesting stuff. == Internationalize your application @@ -795,8 +839,8 @@ If you find your own locale (language) missing from our http://github.com/svenfu == Authors -* Sven Fuchs[http://www.workingwithrails.com/person/9963-sven-fuchs] (initial author) -* Karel Minarik[http://www.workingwithrails.com/person/7476-karel-mina-k] +* http://www.workingwithrails.com/person/9963-sven-fuchs[Sven Fuchs] (initial author) +* http://www.workingwithrails.com/person/7476-karel-mina-k[Karel Minarik] If you found this guide useful please consider recommending its authors on http://www.workingwithrails.com[workingwithrails]. -- cgit v1.2.3 From ca250bdce1a36e9842aff37ee6daa7eae2c26a77 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Thu, 22 Jan 2009 14:44:00 +0100 Subject: Basic formatting fixes for the rest of the i18n guide (+ some basic cleanups) --- railties/doc/guides/source/i18n.txt | 86 ++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index b20e2172eb..ca8bf2f783 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -315,7 +315,7 @@ image:images/i18n/demo_untranslated.png[rails i18n demo untranslated] === Adding Translations -Obviously there are two strings that are localized to English. In order to internationalize this code replace these strings with calls to Rails' #t helper with a key that makes sense for the translation: +Obviously there are *two strings that are localized to English*. In order to internationalize this code replace these strings with calls to Rails' +#t+ helper with a key that makes sense for the translation: [source, ruby] ------------------------------------------------------- @@ -352,7 +352,7 @@ pirate: hello_flash: Ahoy Flash ------------------------------------------------------- -There you go. Because you haven't changed the default_locale I18n will use English. Your application now shows: +There you go. Because you haven't changed the default_locale, I18n will use English. Your application now shows: image:images/i18n/demo_translated_en.png[rails i18n demo translated to english] @@ -360,11 +360,11 @@ And when you change the URL to pass the pirate locale you get: image:images/i18n/demo_translated_pirate.png[rails i18n demo translated to pirate] -NOTE You need to restart the server when you add new locale files. +NOTE: You need to restart the server when you add new locale files. === Adding Date/Time formats -Ok, let's add a timestamp to the view so we can demo the date/time localization feature as well. To localize the time format you pass the Time object to I18n.l or (preferably) use Rails' #l helper. You can pick a format by passing the :format option, by default the :default format is used. +OK! Now let's add a timestamp to the view, so we can demo the *date/time localization* feature as well. To localize the time format you pass the Time object to +I18n.l+ or (preferably) use Rails' +#l+ helper. You can pick a format by passing the +:format+ option -- by default the +:default+ format is used. [source, ruby] ------------------------------------------------------- @@ -389,7 +389,7 @@ So that would give you: image:images/i18n/demo_localized_pirate.png[rails i18n demo localized time to pirate] -NOTE Right now you might need to add some more date/time formats in order to make the I18n backend work as expected. See the http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale[rails-i18n repository] for starting points. +TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected. Of course, there's a great chance that somebody already did all the work by *translating Rails's defaults for your locale*. See the http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale[rails-i18n repository at Github] for an archive of various locale files. When you put such file(s) in +config/locale/+ directory, they will automatically ready for use. == Overview of the I18n API features @@ -413,14 +413,14 @@ I18n.t :message I18n.t 'message' ------------------------------------------------------- -translate also takes a :scope option which can contain one or many additional keys that will be used to specify a “namespace” or scope for a translation key: ++translate+ also takes a +:scope+ option which can contain one or many additional keys that will be used to specify a “namespace” or scope for a translation key: [source, ruby] ------------------------------------------------------- I18n.t :invalid, :scope => [:active_record, :error_messages] ------------------------------------------------------- -This looks up the :invalid message in the Active Record error messages. +This looks up the +:invalid+ message in the Active Record error messages. Additionally, both the key and scopes can be specified as dot separated keys as in: @@ -451,7 +451,7 @@ I18n.t :missing, :default => 'Not here' If the default value is a Symbol it will be used as a key and translated. One can provide multiple values as default. The first one that results in a value will be returned. -E.g. the following first tries to translate the key :missing and then the key :also_missing. As both do not yield a result the string "Not here" will be returned: +E.g. the following first tries to translate the key +:missing+ and then the key +:also_missing.+ As both do not yield a result the string "Not here" will be returned: [source, ruby] ------------------------------------------------------- @@ -479,9 +479,9 @@ I18n.t 'active_record.error_messages' === Interpolation -In many cases you want to abstract your translations so that variables can be interpolated into the translation. For this reason the I18n API provides an interpolation feature. +In many cases you want to abstract your translations so that *variables can be interpolated into the translation*. For this reason the I18n API provides an interpolation feature. -All options besides :default and :scope that are passed to #translate will be interpolated to the translation: +All options besides +:default+ and +:scope+ that are passed to +#translate+ will be interpolated to the translation: [source, ruby] ------------------------------------------------------- @@ -490,14 +490,14 @@ I18n.translate :thanks, :name => 'Jeremy' # => 'Thanks Jeremy!' ------------------------------------------------------- -If a translation uses :default or :scope as a interpolation variable an I18n::ReservedInterpolationKey exception is raised. If a translation expects an interpolation variable but it has not been passed to #translate an I18n::MissingInterpolationArgument exception is raised. +If a translation uses +:default+ or +:scope+ as a interpolation variable an I+18n::ReservedInterpolationKey+ exception is raised. If a translation expects an interpolation variable but it has not been passed to +#translate+ an +I18n::MissingInterpolationArgument+ exception is raised. === Pluralization In English there's only a singular and a plural form for a given string, e.g. "1 message" and "2 messages". Other languages (http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ar[Arabic], http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ja[Japanese], http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ru[Russian] and many more) have different grammars that have additional or less http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html[plural forms]. Thus, the I18n API provides a flexible pluralization feature. -The :count interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR: +The +:count+ interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR: [source, ruby] ------------------------------------------------------- @@ -509,22 +509,22 @@ I18n.translate :inbox, :count => 2 # => '2 messages' ------------------------------------------------------- -The algorithm for pluralizations in :en is as simple as: +The algorithm for pluralizations in +:en+ is as simple as: [source, ruby] ------------------------------------------------------- entry[count == 1 ? 0 : 1] ------------------------------------------------------- -I.e. the translation denoted as :one is regarded as singular, the other is used as plural (including the count being zero). +I.e. the translation denoted as +:one+ is regarded as singular, the other is used as plural (including the count being zero). -If the lookup for the key does not return an Hash suitable for pluralization an I18n::InvalidPluralizationData exception is raised. +If the lookup for the key does not return an Hash suitable for pluralization an +18n::InvalidPluralizationData+ exception is raised. === Setting and passing a locale -The locale can be either set pseudo-globally to I18n.locale (which uses Thread.current like, e.g., Time.zone) or can be passed as an option to #translate and #localize. +The locale can be either set pseudo-globally to +I18n.locale+ (which uses +Thread.current+ like, e.g., +Time.zone+) or can be passed as an option to +#translate+ and +#localize+. -If no locale is passed I18n.locale is used: +If no locale is passed +I18n.locale+ is used: [source, ruby] ------------------------------------------------------- @@ -541,7 +541,7 @@ I18n.t :foo, :locale => :de I18n.l Time.now, :locale => :de ------------------------------------------------------- -I18n.locale defaults to I18n.default_locale which defaults to :en. The default locale can be set like this: ++I18n.locale+ defaults to +I18n.default_locale+ which defaults to :+en+. The default locale can be set like this: [source, ruby] ------------------------------------------------------- @@ -574,9 +574,9 @@ pt: bar: baz ------------------------------------------------------- -As you see in both cases the toplevel key is the locale. :foo is a namespace key and :bar is the key for the translation "baz". +As you see in both cases the toplevel key is the locale. +:foo+ is a namespace key and +:bar+ is the key for the translation "baz". -Here is a "real" example from the ActiveSupport en.yml translations YAML file: +Here is a "real" example from the ActiveSupport +en.yml+ translations YAML file: [source, ruby] ------------------------------------------------------- @@ -588,7 +588,7 @@ en: long: "%B %d, %Y" ------------------------------------------------------- -So, all of the following equivalent lookups will return the :short date format "%B %d": +So, all of the following equivalent lookups will return the +:short+ date format +"%B %d"+: [source, ruby] ------------------------------------------------------- @@ -598,11 +598,11 @@ I18n.t :short, :scope => 'date.formats' I18n.t :short, :scope => [:date, :formats] ------------------------------------------------------- -Generally we recommend using YAML as a format for storing translations. There are cases though where you want to store Ruby lambdas as part of your locale data, e.g. for special date +Generally we recommend using YAML as a format for storing translations. There are cases though where you want to store Ruby lambdas as part of your locale data, e.g. for special date. === Translations for Active Record models -You can use the methods Model.human_name and Model.human_attribute_name(attribute) to transparently lookup translations for your model and attribute names. +You can use the methods +Model.human_name+ and +Model.human_attribute_name(attribute)+ to transparently lookup translations for your model and attribute names. For example when you add the following translations: @@ -618,7 +618,7 @@ en: # will translate User attribute "login" as "Handle" ------------------------------------------------------- -Then User.human_name will return "Dude" and User.human_attribute_name(:login) will return "Handle". +Then +User.human_name+ will return "Dude" and +User.human_attribute_name(:login)+ will return "Handle". ==== Error message scopes @@ -626,7 +626,7 @@ Active Record validation error messages can also be translated easily. Active Re This gives you quite powerful means to flexibly adjust your messages to your application's needs. -Consider a User model with a validates_presence_of validation for the name attribute like this: +Consider a User model with a +validates_presence_of+ validation for the name attribute like this: [source, ruby] ------------------------------------------------------- @@ -635,7 +635,7 @@ class User < ActiveRecord::Base end ------------------------------------------------------- -The key for the error message in this case is :blank. Active Record will lookup this key in the namespaces: +The key for the error message in this case is +:blank+. Active Record will lookup this key in the namespaces: [source, ruby] ------------------------------------------------------- @@ -681,9 +681,9 @@ This way you can provide special translations for various error messages at diff The translated model name, translated attribute name, and value are always available for interpolation. -So, for example, instead of the default error message "can not be blank" you could use the attribute name like this: "Please fill in your {{attribute}}". +So, for example, instead of the default error message +"can not be blank"+ you could use the attribute name like this:+ "Please fill in your {{attribute}}"+. -count, where available, can be used for pluralization if present: ++count+, where available, can be used for pluralization if present: |===================================================================================================== | validation | with option | message | interpolation @@ -713,7 +713,7 @@ count, where available, can be used for pluralization if present: ==== Translations for the Active Record error_messages_for helper -If you are using the Active Record error_messages_for helper you will want to add translations for it. +If you are using the Active Record +error_messages_for+ helper you will want to add translations for it. Rails ships with the following translations: @@ -736,23 +736,23 @@ Rails uses fixed strings and other localizations, such as format strings and oth ==== ActionView helper methods -* distance_of_time_in_words translates and pluralizes its result and interpolates the number of seconds, minutes, hours and so on. See http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L51[datetime.distance_in_words] translations. +* +distance_of_time_in_words+ translates and pluralizes its result and interpolates the number of seconds, minutes, hours and so on. See http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L51[datetime.distance_in_words] translations. -* datetime_select and select_month use translated month names for populating the resulting select tag. See http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15[date.month_names] for translations. datetime_select also looks up the order option from http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18[date.order] (unless you pass the option explicitely). All date select helpers translate the prompt using the translations in the http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L83[datetime.prompts] scope if applicable. +* +datetime_select+ and +select_month+ use translated month names for populating the resulting select tag. See http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15[date.month_names] for translations. +datetime_select+ also looks up the order option from http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18[date.order] (unless you pass the option explicitely). All date select helpers translate the prompt using the translations in the http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L83[datetime.prompts] scope if applicable. -* The number_to_currency, number_with_precision, number_to_percentage, number_with_delimiter and humber_to_human_size helpers use the number format settings located in the http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2[number] scope. +* The +number_to_currency+, +number_with_precision+, +number_to_percentage+, +number_with_delimiter+ and +humber_to_human_size+ helpers use the number format settings located in the http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2[number] scope. ==== Active Record methods -* human_name and human_attribute_name use translations for model names and attribute names if available in the http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L43[activerecord.models] scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes". +* +human_name+ and +human_attribute_name+ use translations for model names and attribute names if available in the http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L43[activerecord.models] scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes". -* ActiveRecord::Errors#generate_message (which is used by Active Record validations but may also be used manually) uses human_name and human_attribute_name (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes". +* +ActiveRecord::Errors#generate_message+ (which is used by Active Record validations but may also be used manually) uses +human_name+ and +human_attribute_name+ (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes". -* ActiveRecord::Errors#full_messages prepends the attribute name to the error message using a separator that will be looked up from http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91[activerecord.errors.format.separator] (and defaults to ' '). +*+ ActiveRecord::Errors#full_messages+ prepends the attribute name to the error message using a separator that will be looked up from http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91[activerecord.errors.format.separator] (and defaults to +' '+). ==== ActiveSupport methods -* Array#to_sentence uses format settings as given in the http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L30[support.array] scope. +* +Array#to_sentence+ uses format settings as given in the http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L30[support.array] scope. == Customize your I18n setup @@ -782,7 +782,7 @@ ReservedInterpolationKey # the translation contains a reserved interpolation UnknownFileType # the backend does not know how to handle a file type that was added to I18n.load_path ------------------------------------------------------- -The I18n API will catch all of these exceptions when they were thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for MissingTranslationData exceptions. When a MissingTranslationData exception has been caught it will return the exception’s error message string containing the missing key/scope. +The I18n API will catch all of these exceptions when they were thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for +MissingTranslationData+ exceptions. When a +MissingTranslationData+ exception has been caught it will return the exception’s error message string containing the missing key/scope. The reason for this is that during development you'd usually want your views to still render even though a translation is missing. @@ -799,11 +799,11 @@ end I18n.exception_handler = :just_raise_that_exception ------------------------------------------------------- -This would re-raise all caught exceptions including MissingTranslationData. +This would re-raise all caught exceptions including +MissingTranslationData+. -Another example where the default behaviour is less desirable is the Rails TranslationHelper which provides the method #t (as well as #translate). When a MissingTranslationData exception occurs in this context the helper wraps the message into a span with the css class translation_missing. +Another example where the default behaviour is less desirable is the Rails TranslationHelper which provides the method +#t+ (as well as +#translate+). When a +MissingTranslationData+ exception occurs in this context the helper wraps the message into a span with the CSS class +translation_missing+. -To do so the helper forces I18n#translate to raise exceptions no matter what exception handler is defined by setting the :raise option: +To do so the helper forces +I18n#translate+ to raise exceptions no matter what exception handler is defined by setting the +:raise+ option: [source, ruby] ------------------------------------------------------- @@ -824,13 +824,13 @@ I18n support in Ruby on Rails was introduced in the release 2.2 and is still evo Thus we encourage everybody to experiment with new ideas and features in plugins or other libraries and make them available to the community. (Don't forget to announce your work on our http://groups.google.com/group/rails-i18n[mailinglist]!) -If you find your own locale (language) missing from our http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale[example translations data] repository for Ruby on Rails +If you find your own locale (language) missing from our http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale[example translations data] repository for Ruby on Rails, please http://github.com/guides/fork-a-project-and-submit-your-modifications[_fork_] the repository, add your data and send a http://github.com/guides/pull-requests[pull request]. == Resources * http://rails-i18n.org[rails-i18n.org] - Homepage of the rails-i18n project. You can find lots of useful resources on the http://rails-i18n.org/wiki[wiki]. -* http://groups.google.com/group/rails-i18n[rails-i18n Google group] - The project's mailinglist. +* http://groups.google.com/group/rails-i18n[rails-i18n Google group] - The project's mailing list. * http://github.com/svenfuchs/rails-i18n/tree/master[Github: rails-i18n] - Code repository for the rails-i18n project. Most importantly you can find lots of http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale[example translations] for Rails that should work for your application in most cases. * http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview[Lighthouse: rails-i18n] - Issue tracker for the rails-i18n project. * http://github.com/svenfuchs/i18n/tree/master[Github: i18n] - Code repository for the i18n gem. @@ -840,7 +840,7 @@ If you find your own locale (language) missing from our http://github.com/svenfu == Authors * http://www.workingwithrails.com/person/9963-sven-fuchs[Sven Fuchs] (initial author) -* http://www.workingwithrails.com/person/7476-karel-mina-k[Karel Minarik] +* http://www.workingwithrails.com/person/7476-karel-mina-k[Karel Minařík] If you found this guide useful please consider recommending its authors on http://www.workingwithrails.com[workingwithrails]. -- cgit v1.2.3 From 741bf96a424256648f2624959f0da7602e81bf4c Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 22 Jan 2009 16:07:57 +0000 Subject: Regenerate guides --- railties/doc/guides/html/action_mailer_basics.html | 30 +++ railties/doc/guides/html/creating_plugins.html | 32 +-- railties/doc/guides/html/i18n.html | 223 +++++++++++++-------- 3 files changed, 179 insertions(+), 106 deletions(-) diff --git a/railties/doc/guides/html/action_mailer_basics.html b/railties/doc/guides/html/action_mailer_basics.html index 56451818eb..c59012ec22 100644 --- a/railties/doc/guides/html/action_mailer_basics.html +++ b/railties/doc/guides/html/action_mailer_basics.html @@ -50,6 +50,9 @@
  • Mailer Testing
  • +
  • + Epilogue +
  • @@ -189,6 +192,33 @@ http://www.gnu.org/software/src-highlite -->

    3. Mailer Testing

    +

    Testing mailers involves 2 things. One is that the mail was queued and the other that the body contains what we expect it to contain. With that in mind, we could test our example mailer from above like so:

    +
    +
    +
    class UserMailerTest < ActionMailer::TestCase
    +    tests UserMailer
    +
    +    def test_welcome_email
    +      user = users(:some_user_in_your_fixtures)
    +
    +      # Send the email, then test that it got queued
    +      email = UserMailer.deliver_welcome_email(user)
    +      assert !ActionMailer::Base.deliveries.empty?
    +
    +      # Test the body of the sent email contains what we expect it to
    +      assert_equal [@user.email],                 email.to
    +      assert_equal "Welcome to My Awesome Site",  email.subject
    +      assert       email.body =~ /Welcome to example.com, #{user.first_name}/
    +    end
    +  end
    +

    What have we done? Well, we sent the email and stored the returned object in the email variable. We then ensured that it was sent (the first assert), then, in the second batch of assertion, we ensure that the email does indeed contain the values that we expect.

    +
    +

    4. Epilogue

    +
    +

    This guide presented how to create a mailer and how to test it. In reality, you may find that writing your tests before you actually write your code to be a rewarding experience. It may take some time to get used to TDD (Test Driven Development), but coding this way achieves two major benefits. Firstly, you know that the code does indeed work, because the tests fail (because there’s no code), then they pass, because the code that satisfies the tests was written. Secondly, when you start with the tests, you don’t have to make time AFTER you write the code, to write the tests, then never get around to it. The tests are already there and testing has now become part of your coding regimen.

    diff --git a/railties/doc/guides/html/creating_plugins.html b/railties/doc/guides/html/creating_plugins.html index 3347f77228..1c512519f9 100644 --- a/railties/doc/guides/html/creating_plugins.html +++ b/railties/doc/guides/html/creating_plugins.html @@ -768,7 +768,7 @@ ActiveRecord::Base

    As always, start with a test:

    -

    vendor/plugins/yaffle/yaffle/woodpecker_test.rb:

    +

    vendor/plugins/yaffle/test/woodpecker_test.rb:

    end

    Here are a few possibilities for how to allow developers to use your plugin migrations:

    11.1. Create a custom rake task

    -

    vendor/plugins/yaffle/lib/db/migrate/20081116181115_create_birdhouses.rb:

    -
    -
    -
    class CreateBirdhouses < ActiveRecord::Migration
    -  def self.up
    -    create_table :birdhouses, :force => true do |t|
    -      t.string :name
    -      t.timestamps
    -    end
    -  end
    -
    -  def self.down
    -    drop_table :birdhouses
    -  end
    -end
    -

    vendor/plugins/yaffle/tasks/yaffle.rake:

    +

    vendor/plugins/yaffle/tasks/yaffle_tasks.rake:

    After running the test with rake you can make it pass with:

    -

    vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb

    +

    vendor/plugins/yaffle/generators/yaffle_migration/yaffle_migration_generator.rb

    12. Rake tasks

    -

    When you created the plugin with the built-in rails generator, it generated a rake file for you in vendor/plugins/yaffle/tasks/yaffle.rake. Any rake task you add here will be available to the app.

    +

    When you created the plugin with the built-in rails generator, it generated a rake file for you in vendor/plugins/yaffle/tasks/yaffle_tasks.rake. Any rake task you add here will be available to the app.

    Many plugin authors put all of their rake tasks into a common namespace that is the same as the plugin, like so:

    -

    vendor/plugins/yaffle/tasks/yaffle.rake

    +

    vendor/plugins/yaffle/tasks/yaffle_tasks.rake

    # if params[:locale] is nil then I18n.default_locale will be used I18n.locale = params[:locale] end
    -

    This requires you to pass the locale as a URL query parameter as in http://example.com/books?locale=pt. (This is eg. Google’s approach). So http://localhost:3000?locale=pt will load the Portugese localization, whereas http://localhost:3000?locale=de would load the German localization, and so on.

    +

    This requires you to pass the locale as a URL query parameter as in http://example.com/books?locale=pt. (This is eg. Google’s approach). So http://localhost:3000?locale=pt will load the Portugese localization, whereas http://localhost:3000?locale=de would load the German localization, and so on. You may skip the next section and head over to the Internationalize your application section, if you want to try things out by manually placing locale in the URL and reloading the page.

    Of course, you probably don’t want to manually include locale in every URL all over your application, or want the URLs look differently, eg. the usual http://example.com/pt/books versus http://example.com/en/books. Let’s discuss the different options you have.

    @@ -324,7 +340,7 @@ http://www.gnu.org/software/src-highlite --> def available_locales; AVAILABLE_LOCALES;endend

    2.4. Setting locale from the domain name

    -

    One option you have is to set the locale from the domain name, where your application runs. For example, we want www.example.com to load English (or default) locale, and www.example.es to load Spanish locale. Thus the top-level domain name is used for locale setting. This has several advantages:

    +

    One option you have is to set the locale from the domain name where your application runs. For example, we want www.example.com to load English (or default) locale, and www.example.es to load Spanish locale. Thus the top-level domain name is used for locale setting. This has several advantages:

    • @@ -381,28 +397,63 @@ http://www.gnu.org/software/src-highlite --> parsed_locale = request.subdomains.first (available_locales.include? parsed_locale) ? parsed_locale : nil end

    +

    If your application includes a locale switching menu, you would then have something like this in it:

    +
    +
    +
    link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['REQUEST_URI']}")
    +

    assuming you would set APP_CONFIG[:deutsch_website_url] to some value like http://www.application.de.

    +

    This solution has aforementioned advantages, however, you may not be able or may not want to provide different localizations ("language versions") on different domains. The most obvious solution would be to include locale code in the URL params (or request path).

    2.5. Setting locale from the URL params

    -
    +

    Most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the I18n.locale = params[:locale] before_filter in the first example. We would like to have URLs like www.example.com/books?locale=ja or www.example.com/ja/books in this case.

    +

    This approach has almost the same set of advantages as setting the locale from domain name: namely that it’s RESTful and in accord with rest of the World Wide Web. It does require a little bit more work to implement, though.

    +

    Getting the locale from params and setting it accordingly is not hard; including it in every URL and thus passing it through the requests is. To include an explicit option in every URL (eg. link_to( books_url(:locale => I18n.locale) )) would be tedious and probably impossible, of course.

    +

    Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its ApplicationController#default_url_options, which is useful precisely in this scenario: it enables us to set "defaults" for url_for and helper methods dependent on it (by implementing/overriding this method).

    +

    We can include something like this in our ApplicationController then:

    +
    +
    +
    # app/controllers/application_controller.rb
    +def default_url_options(options={})
    +  logger.debug "default_url_options is passed options: #{options.inspect}\n"
    +  { :locale => I18n.locale }
    +end
    +

    Every helper method dependent on url_for (eg. helpers for named routes like root_path or root_url, resource routes like books_path or books_url, etc.) will now automatically include the locale in the query string, like this: http://localhost:3001/?locale=ja.

    +

    You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of application domain: and URLs should reflect this.

    +

    You probably want URLs look like this: www.example.com/en/books versus www.example.com/nl/books. This is achievable with the over-riding default_url_options strategy: you just have to set up your routes with path_prefix option in this way:

    +
    +
    +
    # config/routes.rb
    +map.resources :books, :path_prefix => '/:locale'
    +

    Now, when you call books_path method you should get "/en/books" (for the default locale). An URL like http://localhost:3001/nl/books should load the Netherlands locale, then, and so on.

    +

    Of course, you need to take special care of root URL (usually "homepage" or "dashboard") of your application. An URL like http://localhost:3001/nl will not work automatically, because the map.root :controller => "dashboard" declaration in your routes.rb doesn’t take locale into account. (And rightly so. There’s only one "root" URL.)

    +

    You would probably need to map URLs like these:

    +
    +
    +
    # config/routes.rb
    +map.dashboard '/:locale', :controller => "dashboard"
    +

    Do take special care about the order of your routes, so this route declaration does not "eat" other ones. (You may want to add it directly before the map.root declaration.)

    - +
    -Tip +Important For setting locale from URL see How to encode the current locale in the URL in the Rails i18n Wiki.This solution has currently one rather big downside. Due to the default_url_options implementation, you have to pass the :id option explicitely, like this: link_to Show, book_url(:id => book) and not depend on Rails' magic in code like link_to Show, book. If this should be a problem, have a look on two plugins which simplify working with routes in this way: Sven Fuchs’s routing_filter and Raul Murciano’s translate_routes. See also the page How to encode the current locale in the URL in the Rails i18n Wiki.
    -

    Now you’ve initialized I18n support for your application and told it which locale should be used. With that in place you’re now ready for the really interesting stuff.

    +

    2.6. Setting locale from the client supplied information

    +

    # TODO: Accept-Language, GeoIP, etc. Explain why it is not such a good idea in most cases.

    +

    OK! Now you’ve initialized I18n support for your application and told it which locale should be used and how to preserve it between requests. With that in place you’re now ready for the really interesting stuff.

    3. Internationalize your application

    @@ -432,7 +483,7 @@ ActionController::Routing

    3.1. Adding Translations

    -

    Obviously there are two strings that are localized to English. In order to internationalize this code replace these strings with calls to Rails' #t helper with a key that makes sense for the translation:

    +

    Obviously there are two strings that are localized to English. In order to internationalize this code replace these strings with calls to Rails' #t helper with a key that makes sense for the translation:

    I18n.t :message
     I18n.t 'message'
    -

    translate also takes a :scope option which can contain one or many additional keys that will be used to specify a “namespace” or scope for a translation key:

    +

    translate also takes a :scope option which can contain one or many additional keys that will be used to specify a “namespace” or scope for a translation key:

    I18n.t :invalid, :scope => [:active_record, :error_messages]
    -

    This looks up the :invalid message in the Active Record error messages.

    +

    This looks up the :invalid message in the Active Record error messages.

    Additionally, both the key and scopes can be specified as dot separated keys as in:

    I18n.t :missing, :default => 'Not here'
     # => 'Not here'

    If the default value is a Symbol it will be used as a key and translated. One can provide multiple values as default. The first one that results in a value will be returned.

    -

    E.g. the following first tries to translate the key :missing and then the key :also_missing. As both do not yield a result the string "Not here" will be returned:

    +

    E.g. the following first tries to translate the key :missing and then the key :also_missing. As both do not yield a result the string "Not here" will be returned:

    I18n.t 'active_record.error_messages'
     # => { :inclusion => "is not included in the list", :exclusion => ... }

    4.2. Interpolation

    -

    In many cases you want to abstract your translations so that variables can be interpolated into the translation. For this reason the I18n API provides an interpolation feature.

    -

    All options besides :default and :scope that are passed to #translate will be interpolated to the translation:

    +

    In many cases you want to abstract your translations so that variables can be interpolated into the translation. For this reason the I18n API provides an interpolation feature.

    +

    All options besides :default and :scope that are passed to #translate will be interpolated to the translation:

    I18n.backend.store_translations :en, :thanks => 'Thanks {{name}}!'
     I18n.translate :thanks, :name => 'Jeremy'
     # => 'Thanks Jeremy!'
    -

    If a translation uses :default or :scope as a interpolation variable an I18n::ReservedInterpolationKey exception is raised. If a translation expects an interpolation variable but it has not been passed to #translate an I18n::MissingInterpolationArgument exception is raised.

    +

    If a translation uses :default or :scope as a interpolation variable an I+18n::ReservedInterpolationKey+ exception is raised. If a translation expects an interpolation variable but it has not been passed to #translate an I18n::MissingInterpolationArgument exception is raised.

    4.3. Pluralization

    In English there’s only a singular and a plural form for a given string, e.g. "1 message" and "2 messages". Other languages (Arabic, Japanese, Russian and many more) have different grammars that have additional or less plural forms. Thus, the I18n API provides a flexible pluralization feature.

    -

    The :count interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR:

    +

    The :count interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR:

    } I18n.translate :inbox, :count => 2 # => '2 messages'
    -

    The algorithm for pluralizations in :en is as simple as:

    +

    The algorithm for pluralizations in :en is as simple as:

    entry[count == 1 ? 0 : 1]
    -

    I.e. the translation denoted as :one is regarded as singular, the other is used as plural (including the count being zero).

    -

    If the lookup for the key does not return an Hash suitable for pluralization an I18n::InvalidPluralizationData exception is raised.

    +

    I.e. the translation denoted as :one is regarded as singular, the other is used as plural (including the count being zero).

    +

    If the lookup for the key does not return an Hash suitable for pluralization an 18n::InvalidPluralizationData exception is raised.

    4.4. Setting and passing a locale

    -

    The locale can be either set pseudo-globally to I18n.locale (which uses Thread.current like, e.g., Time.zone) or can be passed as an option to #translate and #localize.

    -

    If no locale is passed I18n.locale is used:

    +

    The locale can be either set pseudo-globally to I18n.locale (which uses Thread.current like, e.g., Time.zone) or can be passed as an option to #translate and #localize.

    +

    If no locale is passed I18n.locale is used:

    I18n.t :foo, :locale => :de
     I18n.l Time.now, :locale => :de
    -

    I18n.locale defaults to I18n.default_locale which defaults to :en. The default locale can be set like this:

    +

    I18n.locale defaults to I18n.default_locale which defaults to :en. The default locale can be set like this:

    pt:
       foo:
         bar: baz
    -

    As you see in both cases the toplevel key is the locale. :foo is a namespace key and :bar is the key for the translation "baz".

    -

    Here is a "real" example from the ActiveSupport en.yml translations YAML file:

    +

    As you see in both cases the toplevel key is the locale. :foo is a namespace key and :bar is the key for the translation "baz".

    +

    Here is a "real" example from the ActiveSupport en.yml translations YAML file:

    default: "%Y-%m-%d" short: "%b %d" long: "%B %d, %Y"
    -

    So, all of the following equivalent lookups will return the :short date format "%B %d":

    +

    So, all of the following equivalent lookups will return the :short date format "%B %d":

    I18n.t 'formats.short', :scope => :date I18n.t :short, :scope => 'date.formats' I18n.t :short, :scope => [:date, :formats]
    -

    Generally we recommend using YAML as a format for storing translations. There are cases though where you want to store Ruby lambdas as part of your locale data, e.g. for special date

    +

    Generally we recommend using YAML as a format for storing translations. There are cases though where you want to store Ruby lambdas as part of your locale data, e.g. for special date.

    5.1. Translations for Active Record models

    -

    You can use the methods Model.human_name and Model.human_attribute_name(attribute) to transparently lookup translations for your model and attribute names.

    +

    You can use the methods Model.human_name and Model.human_attribute_name(attribute) to transparently lookup translations for your model and attribute names.

    For example when you add the following translations:

    user: login: "Handle" # will translate User attribute "login" as "Handle"
    -

    Then User.human_name will return "Dude" and User.human_attribute_name(:login) will return "Handle".

    +

    Then User.human_name will return "Dude" and User.human_attribute_name(:login) will return "Handle".

    5.1.1. Error message scopes

    Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes and/or validations. It also transparently takes single table inheritance into account.

    This gives you quite powerful means to flexibly adjust your messages to your application’s needs.

    -

    Consider a User model with a validates_presence_of validation for the name attribute like this:

    +

    Consider a User model with a validates_presence_of validation for the name attribute like this:

    class User < ActiveRecord::Base
       validates_presence_of :name
     end
    -

    The key for the error message in this case is :blank. Active Record will lookup this key in the namespaces:

    +

    The key for the error message in this case is :blank. Active Record will lookup this key in the namespaces:

    • -distance_of_time_in_words translates and pluralizes its result and interpolates the number of seconds, minutes, hours and so on. See datetime.distance_in_words translations. +distance_of_time_in_words translates and pluralizes its result and interpolates the number of seconds, minutes, hours and so on. See datetime.distance_in_words translations.

    • -datetime_select and select_month use translated month names for populating the resulting select tag. See date.month_names for translations. datetime_select also looks up the order option from date.order (unless you pass the option explicitely). All date select helpers translate the prompt using the translations in the datetime.prompts scope if applicable. +datetime_select and select_month use translated month names for populating the resulting select tag. See date.month_names for translations. datetime_select also looks up the order option from date.order (unless you pass the option explicitely). All date select helpers translate the prompt using the translations in the datetime.prompts scope if applicable.

    • -The number_to_currency, number_with_precision, number_to_percentage, number_with_delimiter and humber_to_human_size helpers use the number format settings located in the number scope. +The number_to_currency, number_with_precision, number_to_percentage, number_with_delimiter and humber_to_human_size helpers use the number format settings located in the number scope.

    @@ -976,25 +1041,21 @@ The number_to_currency, number_with_precision, number_to_percentage, number_with
    • -human_name and human_attribute_name use translations for model names and attribute names if available in the activerecord.models scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes". -

      -
    • -
    • -

      -ActiveRecord::Errors#generate_message (which is used by Active Record validations but may also be used manually) uses human_name and human_attribute_name (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes". +human_name and human_attribute_name use translations for model names and attribute names if available in the activerecord.models scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes".

    • -ActiveRecord::Errors#full_messages prepends the attribute name to the error message using a separator that will be looked up from activerecord.errors.format.separator (and defaults to ' '). +ActiveRecord::Errors#generate_message (which is used by Active Record validations but may also be used manually) uses human_name and human_attribute_name (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes".

    +

    * ActiveRecord::Errors#full_messages prepends the attribute name to the error message using a separator that will be looked up from activerecord.errors.format.separator (and defaults to ' ').

    5.2.3. ActiveSupport methods

    • -Array#to_sentence uses format settings as given in the support.array scope. +Array#to_sentence uses format settings as given in the support.array scope.

    @@ -1023,7 +1084,7 @@ InvalidPluralizationData # the translation expects an interpolation argument that has not been passed ReservedInterpolationKey # the translation contains a reserved interpolation variable name (i.e. one of: scope, default) UnknownFileType # the backend does not know how to handle a file type that was added to I18n.load_path
    -

    The I18n API will catch all of these exceptions when they were thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for MissingTranslationData exceptions. When a MissingTranslationData exception has been caught it will return the exception’s error message string containing the missing key/scope.

    +

    The I18n API will catch all of these exceptions when they were thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for MissingTranslationData exceptions. When a MissingTranslationData exception has been caught it will return the exception’s error message string containing the missing key/scope.

    The reason for this is that during development you’d usually want your views to still render even though a translation is missing.

    In other contexts you might want to change this behaviour though. E.g. the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose a different exception handler can be specified. The specified exception handler must be a method on the I18n module:

    @@ -1038,9 +1099,9 @@ http://www.gnu.org/software/src-highlite --> end I18n.exception_handler = :just_raise_that_exception
    -

    This would re-raise all caught exceptions including MissingTranslationData.

    -

    Another example where the default behaviour is less desirable is the Rails TranslationHelper which provides the method #t (as well as #translate). When a MissingTranslationData exception occurs in this context the helper wraps the message into a span with the css class translation_missing.

    -

    To do so the helper forces I18n#translate to raise exceptions no matter what exception handler is defined by setting the :raise option:

    +

    This would re-raise all caught exceptions including MissingTranslationData.

    +

    Another example where the default behaviour is less desirable is the Rails TranslationHelper which provides the method #t (as well as #translate). When a MissingTranslationData exception occurs in this context the helper wraps the message into a span with the CSS class translation_missing.

    +

    To do so the helper forces I18n#translate to raise exceptions no matter what exception handler is defined by setting the :raise option:

    I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first and then cherry-picking the best bread of most widely useful features second for inclusion to the core.

    Thus we encourage everybody to experiment with new ideas and features in plugins or other libraries and make them available to the community. (Don’t forget to announce your work on our mailinglist!)

    -

    If you find your own locale (language) missing from our example translations data repository for Ruby on Rails

    +

    If you find your own locale (language) missing from our example translations data repository for Ruby on Rails, please fork the repository, add your data and send a pull request.

    9. Resources

    @@ -1069,7 +1130,7 @@ http://www.gnu.org/software/src-highlite -->
  • -rails-i18n Google group - The project’s mailinglist. +rails-i18n Google group - The project’s mailing list.

  • @@ -1099,12 +1160,12 @@ http://www.gnu.org/software/src-highlite -->
    • -Sven Fuchs[http://www.workingwithrails.com/person/9963-sven-fuchs] (initial author) +Sven Fuchs (initial author)

    • -Karel Minarik[http://www.workingwithrails.com/person/7476-karel-mina-k] +Karel Minařík

    -- cgit v1.2.3 From 231069e683a3d19c85db3bb53963ccdd666c2530 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Fri, 23 Jan 2009 01:02:46 +0000 Subject: update with some of fxn's corrections --- railties/doc/guides/source/form_helpers.txt | 60 +++++++++++++++-------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index f4039070dd..3fc505adcb 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -6,10 +6,12 @@ Forms in web applications are an essential interface for user input. However, fo In this guide you will: -* Create search forms and similar kind of generic forms not representing any specific model in your application; -* Make model-centric forms for creation and editing of specific database records; -* Generate select boxes from multiple types of data; -* Learn what makes a file upload form different; +* Create search forms and similar kind of generic forms not representing any specific model in your application +* Make model-centric forms for creation and editing of specific database records +* Generate select boxes from multiple types of data +* Understand the date and time helpers Rails provides +* Learn what makes a file upload form different +* Find out where to look for complex forms NOTE: This guide is not intended to be a complete documentation of available form helpers and their arguments. Please visit http://api.rubyonrails.org/[the Rails API documentation] for a complete reference. @@ -25,7 +27,7 @@ The most basic form helper is `form_tag`. <% end %> ---------------------------------------------------------------------------- -When called without arguments like this, it creates a form element that has the current page for action attribute and "POST" as method (some line breaks added for readability): +When called without arguments like this, it creates a form element that has the current page for action attribute and "post" as method (some line breaks added for readability): .Sample output from `form_tag` ---------------------------------------------------------------------------- @@ -37,7 +39,7 @@ When called without arguments like this, it creates a form element that has the ---------------------------------------------------------------------------- -If you carefully observe this output, you can see that the helper generated something you didn't specify: a `div` element with a hidden input inside. This is a security feature of Rails called *cross-site request forgery protection* and form helpers generate it for every form which action isn't "GET" (provided that this security feature is enabled). +If you carefully observe this output, you can see that the helper generated something you didn't specify: a `div` element with a hidden input inside. This is a security feature of Rails called *cross-site request forgery protection* and form helpers generate it for every form whose action is not "get" (provided that this security feature is enabled). NOTE: Throughout this guide, this `div` with the hidden input will be stripped away to have clearer code samples. @@ -51,9 +53,9 @@ Probably the most minimal form often seen on the web is a search form with a sin 3. a text input element, and 4. a submit element. -IMPORTANT: Always use "GET" as the method for search forms. Benefits are many: users are able to bookmark a specific search and get back to it; browsers cache results of "GET" requests, but not "POST"; and others. +IMPORTANT: Always use "GET" as the method for search forms. This allows users are able to bookmark a specific search and get back to it, more generally Rails encourages you to use the right HTTP verb for an action. -To create that, you will use `form_tag`, `label_tag`, `text_field_tag` and `submit_tag`, respectively. +To create this form you will use `form_tag`, `label_tag`, `text_field_tag` and `submit_tag`, respectively. .A basic search form ---------------------------------------------------------------------------- @@ -88,10 +90,10 @@ Besides `text_field_tag` and `submit_tag`, there is a similar helper for _every_ TIP: For every form input, an ID attribute is generated from its name ("q" in the example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript. -Multiple hashes in form helper attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Multiple hashes in form helper calls +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -By now you've seen that the `form_tag` helper accepts 2 arguments: the path for the action and an options hash. This hash specifies the method of form submission and HTML options such as the form element's class. +By now you've seen that the `form_tag` helper accepts 2 arguments: the path for the action and an options hash. This hash specifies the method of form submission and HTML options such as the form element's class. As with the `link_to` helper, the path argument doesn't have to be given a string. It can be a hash of URL parameters that Rails' routing mechanism will turn into a valid URL. Still, you cannot simply write this: @@ -116,7 +118,7 @@ WARNING: Do not delimit the second hash without doing so with the first hash, ot Helpers for generating form elements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Rails provides a series of helpers for generating form elements such as checkboxes, text fields, radio buttons and so. These basic helpers, with names ending in _tag such as `text_field_tag`, `check_box_tag` just generate a single `` element. The first parameter to these is always the name of the input. This is the name under which value will appear in the `params` hash in the controller. For example if the form contains +Rails provides a series of helpers for generating form elements such as checkboxes, text fields, radio buttons and so. These basic helpers, with names ending in _tag such as `text_field_tag`, `check_box_tag` just generate a single `` element. The first parameter to these is always the name of the input. In the controller, this name will be the key in the `params` hash used to get the value entered by the user. For example if the form contains --------------------------- <%= text_field_tag(:query) %> @@ -126,7 +128,7 @@ then the controller code should use --------------------------- params[:query] --------------------------- -to retrieve the value entered by the user. When naming inputs be aware that Rails uses certain conventions that control whether values appear at the top level of the params hash, inside an array or a nested hash and so on. You can read more about them in the <> section. For details on the precise usage of these helpers, please refer to the http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html[API documentation]. +to retrieve the value entered by the user. When naming inputs be aware that Rails uses certain conventions that control whether values are at the top level of the `params` hash, inside an array or a nested hash and so on. You can read more about them in the <> section. For details on the precise usage of these helpers, please refer to the http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html[API documentation]. Checkboxes ^^^^^^^^^^ @@ -146,7 +148,7 @@ output: ---------------------------------------------------------------------------- -The second parameter to `check_box_tag` is the value of the input. This is the value that will be submitted by the browser if the checkbox is ticked (i.e. the value that will be present in the params hash). With the above form you would check the value of `params[:pet_dog]` and `params[:pet_cat]` to see which pets the user owns. +The second parameter to `check_box_tag` is the value of the input. This is the value that will be submitted by the browser if the checkbox is ticked (i.e. the value that will be present in the `params` hash). With the above form you would check the value of `params[:pet_dog]` and `params[:pet_cat]` to see which pets the user owns. Radio buttons ^^^^^^^^^^^^^ @@ -166,7 +168,7 @@ output: ---------------------------------------------------------------------------- -As with `check_box_tag` the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one and `params[:age]` will contain either `child` or `adult`. +As with `check_box_tag` the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one and `params[:age]` will contain either "child" or "adult". IMPORTANT: Always use labels for each checkbox and radio button. They associate text with a specific option and provide a larger clickable region. @@ -258,7 +260,7 @@ The resulting HTML is: ---------------------------------------------------------------------------- -The name passed to `form_for` controls where in the params hash the form values will appear. Here the name is `article` and so all the inputs have names of the form `article[attribute_name]`. Accordingly, in the `create` action `params[:article]` will be a hash with keys `:title` and `:body`. You can read more about the significance of input names in the <> section. +The name passed to `form_for` controls the key used in `params` for form's values. Here the name is `article` and so all the inputs have names of the form `article[attribute_name]`. Accordingly, in the `create` action `params[:article]` will be a hash with keys `:title` and `:body`. You can read more about the significance of input names in the <> section. The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder. @@ -409,7 +411,7 @@ So whenever Rails sees that the internal value of an option being generated matc [TIP] ============================================================================ -The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the internal value is the integer 2 you cannot pass "2" to `options_for_select` -- you must pass 2. Be aware of values extracted from the params hash as they are all strings. +The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the internal value is the integer 2 you cannot pass "2" to `options_for_select` -- you must pass 2. Be aware of values extracted from the `params` hash as they are all strings. ============================================================================ @@ -443,7 +445,7 @@ If you are using `select` (or similar helpers such as `collection_select`, `sele -------- ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got Fixnum(#1138750) -------- -when you pass the params hash to `Person.new` or `update_attributes`. Another way of looking at this is that form helpers only edit attributes. +when you pass the `params` hash to `Person.new` or `update_attributes`. Another way of looking at this is that form helpers only edit attributes. ============================ Option tags from a collection of arbitrary objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -493,7 +495,7 @@ Using Date and Time Form Helpers The date and time helpers differ from all the other form helpers in two important respects: -1. Unlike other attributes you might typically have, dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc...). So in particular, there is no single value in your params hash with your date or time. +1. Unlike other attributes you might typically have, dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc...). So in particular, there is no single value in your `params` hash with your date or time. 2. Other helpers use the _tag suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, `select\_date`, `select\_time` and `select_datetime` are the barebones helpers, `date_select`, `time_select` and `datetime_select` are the equivalent model object helpers Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc...). @@ -515,11 +517,11 @@ The above inputs would result in `params[:start_date]` being a hash with keys :y ----------- Date::civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i) ----------- -The :prefix option controls where in the params hash the date components will be placed. Here it was set to `start_date`, if omitted it will default to `date`. +The :prefix option controls where in the `params` hash the date components will be placed. Here it was set to `start_date`, if omitted it will default to `date`. Model object helpers ~~~~~~~~~~~~~~~~~~~~ -`select_date` does not work well with forms that update or create Active Record objects as Active Record expects each element of the params hash to correspond to one attribute. +`select_date` does not work well with forms that update or create Active Record objects as Active Record expects each element of the `params` hash to correspond to one attribute. The model object helpers for dates and times submit parameters with special names. When Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example --------------- <%= date_select :person, :birth_date %> @@ -530,7 +532,7 @@ outputs (with the actual option values omitted for brevity) -------------- -which results in a params hash like +which results in a `params` hash like -------------- {:person => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}} -------------- @@ -563,7 +565,7 @@ Rails provides the usual pair of helpers: the barebones `file_field_tag` and the What gets uploaded ~~~~~~~~~~~~~~~~~~ -The object in the params hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#\{RAILS_ROOT\}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example). +The object in the `params` hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#\{RAILS_ROOT\}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example). [source, ruby] ----------------- @@ -621,8 +623,8 @@ Understanding Parameter Naming Conventions ----------------------------------------- [[parameter_names]] -As you've seen in the previous sections, values from forms can appear either at the top level of the params hash or may appear nested in another hash. For example in a standard create -action for a Person model, `params[:model]` would usually be a hash of all the attributes for the person to create. The params hash can also contain arrays, arrays of hashes and so on. +As you've seen in the previous sections, values from forms can be at the top level of the `params` hash or nested in another hash. For example in a standard create +action for a Person model, `params[:model]` would usually be a hash of all the attributes for the person to create. The `params` hash can also contain arrays, arrays of hashes and so on. Fundamentally HTML forms don't know about any sort of structured data, all they generate is name-value pairs. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses. @@ -638,11 +640,11 @@ ActionController::RequestParser.parse_query_parameters "name=fred&phone=01234567 Basic structures ~~~~~~~~~~~~~~~ -The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in the params. For example if a form contains +The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in `params`. For example if a form contains ----------------- ----------------- -the params hash will contain +the `params` hash will contain [source, ruby] ----------------- @@ -654,7 +656,7 @@ Hashes can be nested as many levels as required, for example ------------------ ------------------ -will result in the params hash being +will result in the `params` hash being [source, ruby] ----------------- @@ -708,7 +710,7 @@ Assuming our person had two addresses, with ids 23 and 45 this would create outp -------- -This will result in a params hash that looks like +This will result in a `params` hash that looks like [source, ruby] -------- -- cgit v1.2.3 From 7bcdb8a9e01b153a3abd4ad11b09a393254b5b3e Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 23 Jan 2009 15:11:40 +0000 Subject: Add Mailer to the index page and regenerate --- railties/doc/guides/html/action_mailer_basics.html | 7 +++ railties/doc/guides/html/form_helpers.html | 68 +++++++++++++--------- railties/doc/guides/html/index.html | 13 +++++ .../doc/guides/source/action_mailer_basics.txt | 6 +- railties/doc/guides/source/index.txt | 7 +++ 5 files changed, 71 insertions(+), 30 deletions(-) diff --git a/railties/doc/guides/html/action_mailer_basics.html b/railties/doc/guides/html/action_mailer_basics.html index c59012ec22..317f135e4c 100644 --- a/railties/doc/guides/html/action_mailer_basics.html +++ b/railties/doc/guides/html/action_mailer_basics.html @@ -53,6 +53,9 @@
  • Epilogue
  • +
  • + Changelog +
  • @@ -219,6 +222,10 @@ http://www.gnu.org/software/src-highlite -->

    4. Epilogue

    This guide presented how to create a mailer and how to test it. In reality, you may find that writing your tests before you actually write your code to be a rewarding experience. It may take some time to get used to TDD (Test Driven Development), but coding this way achieves two major benefits. Firstly, you know that the code does indeed work, because the tests fail (because there’s no code), then they pass, because the code that satisfies the tests was written. Secondly, when you start with the tests, you don’t have to make time AFTER you write the code, to write the tests, then never get around to it. The tests are already there and testing has now become part of your coding regimen.

    +
    +

    5. Changelog

    +
    diff --git a/railties/doc/guides/html/form_helpers.html b/railties/doc/guides/html/form_helpers.html index 54155cddb6..978beb4223 100644 --- a/railties/doc/guides/html/form_helpers.html +++ b/railties/doc/guides/html/form_helpers.html @@ -36,7 +36,7 @@
  • Generic search form
  • -
  • Multiple hashes in form helper attributes
  • +
  • Multiple hashes in form helper calls
  • Helpers for generating form elements
  • @@ -125,22 +125,32 @@
    • -Create search forms and similar kind of generic forms not representing any specific model in your application; +Create search forms and similar kind of generic forms not representing any specific model in your application

    • -Make model-centric forms for creation and editing of specific database records; +Make model-centric forms for creation and editing of specific database records

    • -Generate select boxes from multiple types of data; +Generate select boxes from multiple types of data

    • -Learn what makes a file upload form different; +Understand the date and time helpers Rails provides +

      +
    • +
    • +

      +Learn what makes a file upload form different +

      +
    • +
    • +

      +Find out where to look for complex forms

    @@ -163,7 +173,7 @@ Learn what makes a file upload form different; Form contents <% end %>
    -

    When called without arguments like this, it creates a form element that has the current page for action attribute and "POST" as method (some line breaks added for readability):

    +

    When called without arguments like this, it creates a form element that has the current page for action attribute and "post" as method (some line breaks added for readability):

    Sample output from form_tag
    @@ -174,7 +184,7 @@ Learn what makes a file upload form different; Form contents </form>
    -

    If you carefully observe this output, you can see that the helper generated something you didn’t specify: a div element with a hidden input inside. This is a security feature of Rails called cross-site request forgery protection and form helpers generate it for every form which action isn’t "GET" (provided that this security feature is enabled).

    +

    If you carefully observe this output, you can see that the helper generated something you didn’t specify: a div element with a hidden input inside. This is a security feature of Rails called cross-site request forgery protection and form helpers generate it for every form whose action is not "get" (provided that this security feature is enabled).

    - +
    @@ -212,10 +222,10 @@ a submit element. Important Always use "GET" as the method for search forms. Benefits are many: users are able to bookmark a specific search and get back to it; browsers cache results of "GET" requests, but not "POST"; and others.Always use "GET" as the method for search forms. This allows users are able to bookmark a specific search and get back to it, more generally Rails encourages you to use the right HTTP verb for an action.
    -

    To create that, you will use form_tag, label_tag, text_field_tag and submit_tag, respectively.

    +

    To create this form you will use form_tag, label_tag, text_field_tag and submit_tag, respectively.

    A basic search form
    @@ -258,8 +268,8 @@ a submit element. For every form input, an ID attribute is generated from its name ("q" in the example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript.
    -

    1.2. Multiple hashes in form helper attributes

    -

    By now you’ve seen that the form_tag helper accepts 2 arguments: the path for the action and an options hash. This hash specifies the method of form submission and HTML options such as the form element’s class.

    +

    1.2. Multiple hashes in form helper calls

    +

    By now you’ve seen that the form_tag helper accepts 2 arguments: the path for the action and an options hash. This hash specifies the method of form submission and HTML options such as the form element’s class.

    As with the ‘link_to` helper, the path argument doesn’t have to be given a string. It can be a hash of URL parameters that Rails’ routing mechanism will turn into a valid URL. Still, you cannot simply write this:

    A bad way to pass multiple hashes as method arguments
    @@ -284,7 +294,7 @@ a submit element.

    1.3. Helpers for generating form elements

    -

    Rails provides a series of helpers for generating form elements such as checkboxes, text fields, radio buttons and so. These basic helpers, with names ending in _tag such as text_field_tag, check_box_tag just generate a single <input> element. The first parameter to these is always the name of the input. This is the name under which value will appear in the params hash in the controller. For example if the form contains

    +

    Rails provides a series of helpers for generating form elements such as checkboxes, text fields, radio buttons and so. These basic helpers, with names ending in _tag such as text_field_tag, check_box_tag just generate a single <input> element. The first parameter to these is always the name of the input. In the controller, this name will be the key in the params hash used to get the value entered by the user. For example if the form contains

    <%= text_field_tag(:query) %>
    @@ -294,7 +304,7 @@ a submit element.
    params[:query]
    -

    to retrieve the value entered by the user. When naming inputs be aware that Rails uses certain conventions that control whether values appear at the top level of the params hash, inside an array or a nested hash and so on. You can read more about them in the parameter names section. For details on the precise usage of these helpers, please refer to the API documentation.

    +

    to retrieve the value entered by the user. When naming inputs be aware that Rails uses certain conventions that control whether values are at the top level of the params hash, inside an array or a nested hash and so on. You can read more about them in the parameter names section. For details on the precise usage of these helpers, please refer to the API documentation.

    1.3.1. Checkboxes

    Checkboxes are form controls that give the user a set of options they can enable or disable:

    @@ -311,7 +321,7 @@ output: <input id="pet_cat" name="pet_cat" type="checkbox" value="1" /> <label for="pet_cat">I own a cat</label>
    -

    The second parameter to check_box_tag is the value of the input. This is the value that will be submitted by the browser if the checkbox is ticked (i.e. the value that will be present in the params hash). With the above form you would check the value of params[:pet_dog] and params[:pet_cat] to see which pets the user owns.

    +

    The second parameter to check_box_tag is the value of the input. This is the value that will be submitted by the browser if the checkbox is ticked (i.e. the value that will be present in the params hash). With the above form you would check the value of params[:pet_dog] and params[:pet_cat] to see which pets the user owns.

    1.3.2. Radio buttons

    Radio buttons, while similar to checkboxes, are controls that specify a set of options in which they are mutually exclusive (user can only pick one):

    @@ -328,7 +338,7 @@ output: <input id="age_adult" name="age" type="radio" value="adult" /> <label for="age_adult">I'm over 21</label>
    -

    As with check_box_tag the second parameter to radio_button_tag is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one and params[:age] will contain either child or adult.

    +

    As with check_box_tag the second parameter to radio_button_tag is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one and params[:age] will contain either "child" or "adult".

    @@ -439,7 +449,7 @@ Methods to create form controls are called on the form builder <input name="commit" type="submit" value="Create" /> </form> -

    The name passed to form_for controls where in the params hash the form values will appear. Here the name is article and so all the inputs have names of the form article[attribute_name]. Accordingly, in the create action params[:article] will be a hash with keys :title and :body. You can read more about the significance of input names in the parameter names section.

    +

    The name passed to form_for controls the key used in params for form’s values. Here the name is article and so all the inputs have names of the form article[attribute_name]. Accordingly, in the create action params[:article] will be a hash with keys :title and :body. You can read more about the significance of input names in the parameter names section.

    The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder.

    You can create a similar binding without actually creating <form> tags with the fields_for helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for editing both like so:

    @@ -572,7 +582,7 @@ output: Tip
    -

    The second argument to options_for_select must be exactly equal to the desired internal value. In particular if the internal value is the integer 2 you cannot pass "2" to options_for_select — you must pass 2. Be aware of values extracted from the params hash as they are all strings.

    +

    The second argument to options_for_select must be exactly equal to the desired internal value. In particular if the internal value is the integer 2 you cannot pass "2" to options_for_select — you must pass 2. Be aware of values extracted from the params hash as they are all strings.

    @@ -605,7 +615,7 @@ output:
    ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got Fixnum(#1138750)
    -

    when you pass the params hash to Person.new or update_attributes. Another way of looking at this is that form helpers only edit attributes.

    +

    when you pass the params hash to Person.new or update_attributes. Another way of looking at this is that form helpers only edit attributes.

    @@ -647,7 +657,7 @@ output:
    1. -Unlike other attributes you might typically have, dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc...). So in particular, there is no single value in your params hash with your date or time. +Unlike other attributes you might typically have, dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc...). So in particular, there is no single value in your params hash with your date or time.

    2. @@ -675,9 +685,9 @@ Other helpers use the _tag suffix to indicate whether a helper is a barebones he
      Date::civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i)
    -

    The :prefix option controls where in the params hash the date components will be placed. Here it was set to start_date, if omitted it will default to date.

    +

    The :prefix option controls where in the params hash the date components will be placed. Here it was set to start_date, if omitted it will default to date.

    4.2. Model object helpers

    -

    select_date does not work well with forms that update or create Active Record objects as Active Record expects each element of the params hash to correspond to one attribute. +

    select_date does not work well with forms that update or create Active Record objects as Active Record expects each element of the params hash to correspond to one attribute. The model object helpers for dates and times submit parameters with special names. When Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example

    @@ -690,7 +700,7 @@ The model object helpers for dates and times submit parameters with special name <select id="person_birth_date_2i" name="person[birth_date(2i)]"> ... </select> <select id="person_birth_date_3i" name="person[birth_date(3i)]"> ... </select>
    -

    which results in a params hash like

    +

    which results in a params hash like

    {:person => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}}
    @@ -724,7 +734,7 @@ The model object helpers for dates and times submit parameters with special name

    Rails provides the usual pair of helpers: the barebones file_field_tag and the model oriented file_field. The only difference with other helpers is that you cannot set a default value for file inputs as this would have no meaning. As you would expect in the first case the uploaded file is in params[:picture] and in the second case in params[:person][:picture].

    5.1. What gets uploaded

    -

    The object in the params hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an original_filename attribute containing the name the file had on the user’s computer and a content_type attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in #{RAILS_ROOT}/public/uploads under the same name as the original file (assuming the form was the one in the previous example).

    +

    The object in the params hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an original_filename attribute containing the name the file had on the user’s computer and a content_type attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in #{RAILS_ROOT}/public/uploads under the same name as the original file (assuming the form was the one in the previous example).

    7. Understanding Parameter Naming Conventions

    -

    As you’ve seen in the previous sections, values from forms can appear either at the top level of the params hash or may appear nested in another hash. For example in a standard create -action for a Person model, params[:model] would usually be a hash of all the attributes for the person to create. The params hash can also contain arrays, arrays of hashes and so on.

    +

    As you’ve seen in the previous sections, values from forms can be at the top level of the params hash or nested in another hash. For example in a standard create +action for a Person model, params[:model] would usually be a hash of all the attributes for the person to create. The params hash can also contain arrays, arrays of hashes and so on.

    Fundamentally HTML forms don’t know about any sort of structured data, all they generate is name-value pairs. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses.

    @@ -804,12 +814,12 @@ action for a Person model, params[:model] would usually be a hash of al

    7.1. Basic structures

    -

    The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in the params. For example if a form contains

    +

    The two basic structures are arrays and hashes. Hashes mirror the syntax used for accessing the value in params. For example if a form contains

    <input id="person_name" name="person[name]" type="text" value="Henry"/>
    -

    the params hash will contain

    +

    the params hash will contain

    <input id="person_address_city" name="person[address][city]" type="text" value="New York"/>
    -

    will result in the params hash being

    +

    will result in the params hash being

    <input id="person_address_45_city" name="person[address][45][city]" size="30" type="text" /> </form>
    -

    This will result in a params hash that looks like

    +

    This will result in a params hash that looks like

    <input name="addresses[][city]" type="text"/>

    This would result in params[:addresses] being an array of hashes with keys line1, line2 and city. Rails decides to start accumulating values in a new hash whenever it encounters a input name that already exists in the current hash.

    -

    The one restriction is that although hashes can be nested arbitrarily deep then can be only one level of "arrayness". Frequently arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id.

    +

    The one restriction is that although hashes can be nested arbitrarily deep then can be only one level of "arrayness". Arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id, an array index or some other parameter.

    - +
    Warning Array parameters do not play well with the check_box helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. The check_box helper fakes this by creating a second hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted. If the checkbox is checked then both are submitted but the value submitted by the checkbox takes precedence. When working with array parameters this duplicate submission will confuse Rails since duplicate input names are how it decides when to start a new hash. It is preferable to either use check_box_tag or to use hashes instead of arrays.Array parameters do not play well with the check_box helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. The check_box helper fakes this by creating a second hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted and if it is checked then both are submitted but the value submitted by the checkbox takes precedence. When working with array parameters this duplicate submission will confuse Rails since duplicate input names are how it decides when to start a new array element. It is preferable to either use check_box_tag or to use hashes instead of arrays.

    7.3. Using form helpers

    -

    The previous sections did not use the Rails form helpers at all. While you can craft the input names yourself and pass them directly to helpers such as text_field_tag Rails also provides higher level support. The two tools at your disposal here are the name parameter to form_for/fields_for and the :index option.

    -

    You might want to render a form with a set of edit fields for each of a person’s addresses. Something a little like this will do the trick

    +

    The previous sections did not use the Rails form helpers at all. While you can craft the input names yourself and pass them directly to helpers such as text_field_tag Rails also provides higher level support. The two tools at your disposal here are the name parameter to form_for and fields_for and the :index option that helpers take.

    +

    You might want to render a form with a set of edit fields for each of a person’s addresses. For example:

    <% form_for @person do |person_form| %>
    @@ -879,7 +885,7 @@ http://www.gnu.org/software/src-highlite -->
       <% end %>
     <% end %>
    -

    Assuming our person had two addresses, with ids 23 and 45 this would create output similar to this:

    +

    Assuming the person had two addresses, with ids 23 and 45 this would create output similar to this:

    <form action="/people/1" class="edit_person" id="edit_person_1" method="post">
    @@ -895,7 +901,7 @@ by Lorenzo Bettini
     http://www.lorenzobettini.it
     http://www.gnu.org/software/src-highlite -->
     
    {'person' => {'name' => 'Bob', 'address' => { '23' => {'city' => 'Paris'}, '45' => {'city' => 'London'} }}}
    -

    Rails knows that all these inputs should be part of the person hash because you called fields_for on the first form builder. By specifying an :index option you’re telling rails that instead of naming the inputs person[address][city] it should insert that index surrounded by [] between the address and the city. If you pass an Active Record object as we did then Rails will call to_param on it, which by default returns the database id. This is often useful it is then easy to locate which Address record should be modified but you could pass numbers with some other significance, strings or even nil (which will result in an array parameter being created).

    +

    Rails knows that all these inputs should be part of the person hash because you called fields_for on the first form builder. By specifying an :index option you’re telling rails that instead of naming the inputs person[address][city] it should insert that index surrounded by [] between the address and the city. If you pass an Active Record object as we did then Rails will call to_param on it, which by default returns the database id. This is often useful as it is then easy to locate which Address record should be modified. You can pass numbers with some other significance, strings or even nil (which will result in an array parameter being created).

    To create more intricate nestings, you can specify the first part of the input name (person[address] in the previous example) explicitly, for example

    @@ -908,7 +914,7 @@ http://www.gnu.org/software/src-highlite -->
    <input id="person_address_primary_1_city" name="person[address][primary][1][city]" size="30" type="text" value="bologna" />
    -

    As a general rule the final input name is the concatenation of the name given to fields_for/form_for, the index value and the name of the attribute. You can also pass an :index option directly to helpers such as text_field, but usually it is less repetitive to specify this at the form builder level rather than on individual input controls.

    +

    As a general rule the final input name is the concatenation of the name given to fields_for/form_for, the index value and the name of the attribute. You can also pass an :index option directly to helpers such as text_field, but it is usually less repetitive to specify this at the form builder level rather than on individual input controls.

    As a shortcut you can append [] to the name and omit the :index option. This is the same as specifing :index => address so

    @@ -920,7 +926,7 @@ http://www.gnu.org/software/src-highlite -->

    8. Building Complex forms

    -

    Many apps grow beyond simple forms editing a single object. For example when creating a Person instance you might want to allow the user to (on the same form) create multiple address records (home, work etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. While this guide has shown you all the pieces necessary to handle this, Rails does not yet have a standard end-to-end way of accomplishing this, but many have come up with viable approaches. These include:

    +

    Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. While this guide has shown you all the pieces necessary to handle this, Rails does not yet have a standard end-to-end way of accomplishing this, but many have come up with viable approaches. These include:

    • -- cgit v1.2.3 From fe6ffce51dc16285094be49244b26591956c2dd6 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 24 Jan 2009 17:54:10 +0000 Subject: Make sure inner scope conditions get a preference over the outer ones --- actionpack/test/fixtures/replies.yml | 10 +++++++++- activerecord/lib/active_record/base.rb | 8 ++++++-- activerecord/test/cases/named_scope_test.rb | 20 ++++++++++++++++++++ activerecord/test/models/post.rb | 6 ++++++ activerecord/test/models/topic.rb | 2 ++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/actionpack/test/fixtures/replies.yml b/actionpack/test/fixtures/replies.yml index a17d2fc42b..2022523eac 100644 --- a/actionpack/test/fixtures/replies.yml +++ b/actionpack/test/fixtures/replies.yml @@ -12,4 +12,12 @@ another: developer_id: 1 content: Nuh uh! created_at: <%= 1.hour.ago.to_s(:db) %> - updated_at: nil \ No newline at end of file + updated_at: nil + +best_reply: + id: 3 + topic_id: 3 + developer_id: 2 + content: No one can know + created_at: <%= 5.hours.ago.to_s(:db) %> + updated_at: nil diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index ebc0b7783f..8db3909d9a 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2097,7 +2097,11 @@ module ActiveRecord #:nodoc: (hash[method].keys + params.keys).uniq.each do |key| merge = hash[method][key] && params[key] # merge if both scopes have the same key if key == :conditions && merge - hash[method][key] = merge_conditions(params[key], hash[method][key]) + if params[key].is_a?(Hash) && hash[method][key].is_a?(Hash) + hash[method][key] = merge_conditions(hash[method][key].deep_merge(params[key])) + else + hash[method][key] = merge_conditions(params[key], hash[method][key]) + end elsif key == :include && merge hash[method][key] = merge_includes(hash[method][key], params[key]).uniq elsif key == :joins && merge @@ -2107,7 +2111,7 @@ module ActiveRecord #:nodoc: end end else - hash[method] = params.merge(hash[method]) + hash[method] = hash[method].merge(params) end else hash[method] = params diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index bab842cf66..e1e27fa130 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -277,6 +277,26 @@ class NamedScopeTest < ActiveRecord::TestCase post = Post.find(1) assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size end + + def test_chanining_should_use_latest_conditions_when_creating + post1 = Topic.rejected.approved.new + assert post1.approved? + + post2 = Topic.approved.rejected.new + assert ! post2.approved? + end + + def test_chanining_should_use_latest_conditions_when_searching + # Normal hash conditions + assert_equal Topic.all(:conditions => {:approved => true}), Topic.rejected.approved.all + assert_equal Topic.all(:conditions => {:approved => false}), Topic.approved.rejected.all + + # Nested hash conditions with same keys + assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all + + # Nested hash conditions with different keys + assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.uniq + end end class DynamicScopeMatchTest < ActiveRecord::TestCase diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index e0d8be676a..388fff8fba 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -17,6 +17,12 @@ class Post < ActiveRecord::Base has_one :last_comment, :class_name => 'Comment', :order => 'id desc' + named_scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} } + named_scope :with_very_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'VerySpecialComment'} } + named_scope :with_post, lambda {|post_id| + { :joins => :comments, :conditions => {:comments => {:post_id => post_id} } } + } + has_many :comments, :order => "body" do def find_most_recent find(:first, :order => "id DESC") diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 39ca1bf42a..08bb24ed03 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -4,6 +4,8 @@ class Topic < ActiveRecord::Base { :conditions => ['written_on < ?', time] } } named_scope :approved, :conditions => {:approved => true} + named_scope :rejected, :conditions => {:approved => false} + named_scope :by_lifo, :conditions => {:author_name => 'lifo'} named_scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}} -- cgit v1.2.3 From 617ad23574770310fd037529af3d4bd7e722f72a Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 24 Jan 2009 18:22:17 +0000 Subject: Remove the irrelevant fixture added in fe6ffce51dc16285094be49244b26591956c2dd6 --- actionpack/test/fixtures/replies.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/actionpack/test/fixtures/replies.yml b/actionpack/test/fixtures/replies.yml index 2022523eac..66020b706a 100644 --- a/actionpack/test/fixtures/replies.yml +++ b/actionpack/test/fixtures/replies.yml @@ -13,11 +13,3 @@ another: content: Nuh uh! created_at: <%= 1.hour.ago.to_s(:db) %> updated_at: nil - -best_reply: - id: 3 - topic_id: 3 - developer_id: 2 - content: No one can know - created_at: <%= 5.hours.ago.to_s(:db) %> - updated_at: nil -- cgit v1.2.3 From add4e878a8fde990fc49ab1ff41786a28f2389b5 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 12:29:08 +0000 Subject: lisabon -> lisbon --- railties/doc/guides/source/form_helpers.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index 71393fe603..f53cb1b140 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -397,11 +397,11 @@ Knowing this, you can combine `select_tag` and `options_for_select` to achieve t `options_for_select` allows you to pre-select an option by specify its value as the second argument: ---------------------------------------------------------------------------- -<%= options_for_select([['Lisabon', 1], ['Madrid', 2], ...], 2) %> +<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...], 2) %> output: - + ... ---------------------------------------------------------------------------- @@ -424,7 +424,7 @@ In most cases form controls will be tied to a specific database model and as you @person = Person.new(:city_id => 2) # view: -<%= select(:person, :city_id, [['Lisabon', 1], ['Madrid', 2], ...]) %> +<%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %> ---------------------------------------------------------------------------- Notice that the third parameter, the options array, is the same kind of argument you pass to `options_for_select`. One advantage here is that you don't have to worry about pre-selecting the correct city if the user already has one -- Rails will do this for you by reading from the `@person.city_id` attribute. -- cgit v1.2.3 From bf26f42d77eca61acc9affecd5a2aec9ad61beda Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 12:30:31 +0000 Subject: clarify wording around second arg to options_for_select --- railties/doc/guides/source/form_helpers.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index f53cb1b140..055dc20553 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -394,7 +394,7 @@ Knowing this, you can combine `select_tag` and `options_for_select` to achieve t <%= select_tag(:city_id, options_for_select(...)) %> ---------------------------------------------------------------------------- -`options_for_select` allows you to pre-select an option by specify its value as the second argument: +`options_for_select` allows you to pre-select an option by passing its value. ---------------------------------------------------------------------------- <%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...], 2) %> -- cgit v1.2.3 From 13e4b02a5c603b9368448febe09254e2b2dcbc1a Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 12:31:46 +0000 Subject: find :all -> .all --- railties/doc/guides/source/form_helpers.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index 055dc20553..a17c7e5249 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -453,7 +453,7 @@ Option tags from a collection of arbitrary objects Generating options tags with `options_for_select` requires that you create an array containing the text and value for each option. But what if you had a City model (perhaps an Active Record one) and you wanted to generate option tags from a collection of those objects? One solution would be to make a nested array by iterating over them: ---------------------------------------------------------------------------- -<% cities_array = City.find(:all).map { |city| [city.name, city.id] } %> +<% cities_array = City.all.map { |city| [city.name, city.id] } %> <%= options_for_select(cities_array) %> ---------------------------------------------------------------------------- -- cgit v1.2.3 From 14dd6c4d2f978d743269cf1096f0ad551a32d240 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 12:36:56 +0000 Subject: add warning about options_from_collection_for_select --- railties/doc/guides/source/form_helpers.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index a17c7e5249..bac3496b86 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -436,7 +436,7 @@ As with other helpers, if you were to use `select` helper on a form builder scop <%= f.select(:city_id, ...) %> ---------------------------------------------------------------------------- -[WARNING] +[WARNING] ============================= If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. @@ -462,7 +462,6 @@ This is a perfectly valid solution, but Rails provides a less verbose alternativ ---------------------------------------------------------------------------- <%= options_from_collection_for_select(City.all, :id, :name) %> ---------------------------------------------------------------------------- - As the name implies, this only generates option tags. To generate a working select box you would need to use it in conjunction with `select_tag`, just as you would with `options_for_select`. When working with model objects, just as `select` combines `select_tag` and `options_for_select`, `collection_select` combines `select_tag` with `options_from_collection_for_select`. ---------------------------------------------------------------------------- @@ -471,6 +470,11 @@ As the name implies, this only generates option tags. To generate a working sele To recap, `options_from_collection_for_select` is to `collection_select` what `options_for_select` is to `select`. +[NOTE] +============================= +Pairs passed to `options_for_select` should have the name first and the id second, however with `options_from_collection_for_select` the first argument is the value method and the second the text method. +============================= + Time zone and country select ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 279ec8116c34dc6aa26e4e54c8d6f2eb1e03baf5 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 12:38:05 +0000 Subject: tweak section name --- railties/doc/guides/source/form_helpers.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index bac3496b86..94a1d0a3ed 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -365,7 +365,7 @@ Here is what the markup might look like: Here you have a list of cities whose names are presented to the user. Internally the application only wants to handle their IDs so they are used as the options' value attribute. Let's see how Rails can help out here. -The select tag and options +The select and options tag ~~~~~~~~~~~~~~~~~~~~~~~~~~ The most generic helper is `select_tag`, which -- as the name implies -- simply generates the `SELECT` tag that encapsulates an options string: -- cgit v1.2.3 From 098dc68f5c627bfc2b8705762736096a2bae3b38 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 12:41:11 +0000 Subject: don't use short form of form_for before it's been introduced --- railties/doc/guides/source/form_helpers.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index 94a1d0a3ed..a0c6fecdcc 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -263,9 +263,9 @@ The name passed to `form_for` controls the key used in `params` to access the fo The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder. -You can create a similar binding without actually creating `

      ` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for editing both like so: +You can create a similar binding without actually creating `` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for creating both like so: ------------- -<% form_for @person do |person_form| %> +<% form_for :person, @person, :url => { :action => "create" } do |person_form| %> <%= person_form.text_field :name %> <% fields_for @person.contact_detail do |contact_details_form| %> <%= contact_details_form.text_field :phone_number %> @@ -276,7 +276,7 @@ You can create a similar binding without actually creating `` tags with t which produces the following output: ------------- - +
      -- cgit v1.2.3 From c6dbd5f45b53c54caf5a066dc66bc43c50108daf Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 12:43:33 +0000 Subject: some missing fixed width words --- railties/doc/guides/source/form_helpers.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index a0c6fecdcc..de8ef9436b 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -276,7 +276,7 @@ You can create a similar binding without actually creating `
      ` tags with t which produces the following output: ------------- - +
      @@ -306,7 +306,7 @@ form_for(@article) Notice how the short-style `form_for` invocation is conveniently the same, regardless of the record being new or existing. Record identification is smart enough to figure out if the record is new by asking `record.new_record?`. It also selects the correct path to submit to and the name based on the class of the object. -Rails will also automatically set the class and id of the form appropriately: a form creating an article would have id and class `new_article`. If you were editing the article with id 23 the class would be set to `edit_article` and the id to `edit_article_23`. These attributes will be omitted for brevity in the rest of this guide. +Rails will also automatically set the `class` and `id` of the form appropriately: a form creating an article would have `id` and `class` `new_article`. If you were editing the article with id 23 the `class` would be set to `edit_article` and the id to `edit_article_23`. These attributes will be omitted for brevity in the rest of this guide. WARNING: When you're using STI (single-table inheritance) with your models, you can't rely on record identification on a subclass if only their parent class is declared a resource. You will have to specify the model name, `:url` and `:method` explicitly. -- cgit v1.2.3 From cbb6cfd20449d518a703715e1308dc2e2cf4a62a Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 12:50:38 +0000 Subject: point people in the direction of attr_accessible etc... --- railties/doc/guides/source/form_helpers.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index de8ef9436b..1025dc8baf 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -445,6 +445,8 @@ If you specify `city` instead of `city_id` Active Record will raise an error alo ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) -------- when you pass the `params` hash to `Person.new` or `update_attributes`. Another way of looking at this is that form helpers only edit attributes. + +You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. You may wish to consider the use of `attr_protected` and `attr_accessible`. For further details on this, see the link:security.html#_mass_assignment[Ruby On Rails Security Guide]. ============================ Option tags from a collection of arbitrary objects -- cgit v1.2.3 From 24ce639647edc588ac33bb736d9ae6c80eac0405 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 25 Jan 2009 18:13:10 +0000 Subject: Regen form guide --- railties/doc/guides/html/form_helpers.html | 33 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/railties/doc/guides/html/form_helpers.html b/railties/doc/guides/html/form_helpers.html index 91b7b02574..5329d604b1 100644 --- a/railties/doc/guides/html/form_helpers.html +++ b/railties/doc/guides/html/form_helpers.html @@ -60,7 +60,7 @@ Making select boxes with ease

    The name passed to form_for controls the key used in params to access the form’s values. Here the name is article and so all the inputs have names of the form article[attribute_name]. Accordingly, in the create action params[:article] will be a hash with keys :title and :body. You can read more about the significance of input names in the parameter names section.

    The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder.

    -

    You can create a similar binding without actually creating <form> tags with the fields_for helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for editing both like so:

    +

    You can create a similar binding without actually creating <form> tags with the fields_for helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for creating both like so:

    -
    <% form_for @person do |person_form| %>
    +
    <% form_for :person, @person, :url => { :action => "create" } do |person_form| %>
       <%= person_form.text_field :name %>
       <% fields_for @person.contact_detail do |contact_details_form| %>
         <%= contact_details_form.text_field :phone_number %>
    @@ -466,7 +466,7 @@ Methods to create form controls are called on the form builder
     

    which produces the following output:

    -
    <form action="/people/1" class="edit_person" id="edit_person_1" method="post">
    +
    <form action="/people/create" class="new_person" id="new_person" method="post">
       <input id="person_name" name="person[name]" size="30" type="text" />
       <input id="contact_detail_phone_number" name="contact_detail[phone_number]" size="30" type="text" />
     </form>
    @@ -490,7 +490,7 @@ form_for(:article, @article, :url => article_path(@article), :method => "p form_for(@article)

    Notice how the short-style form_for invocation is conveniently the same, regardless of the record being new or existing. Record identification is smart enough to figure out if the record is new by asking record.new_record?. It also selects the correct path to submit to and the name based on the class of the object.

    -

    Rails will also automatically set the class and id of the form appropriately: a form creating an article would have id and class new_article. If you were editing the article with id 23 the class would be set to edit_article and the id to edit_article_23. These attributes will be omitted for brevity in the rest of this guide.

    +

    Rails will also automatically set the class and id of the form appropriately: a form creating an article would have id and class new_article. If you were editing the article with id 23 the class would be set to edit_article and the id to edit_article_23. These attributes will be omitted for brevity in the rest of this guide.

    @@ -543,7 +543,7 @@ output: </select>

    Here you have a list of cities whose names are presented to the user. Internally the application only wants to handle their IDs so they are used as the options' value attribute. Let’s see how Rails can help out here.

    -

    3.1. The select tag and options

    +

    3.1. The select and options tag

    The most generic helper is select_tag, which — as the name implies — simply generates the SELECT tag that encapsulates an options string:

    @@ -566,14 +566,14 @@ output:
    <%= select_tag(:city_id, options_for_select(...)) %>
    -

    options_for_select allows you to pre-select an option by specify its value as the second argument:

    +

    options_for_select allows you to pre-select an option by passing its value.

    -
    <%= options_for_select([['Lisabon', 1], ['Madrid', 2], ...], 2) %>
    +
    <%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...], 2) %>
     
     output:
     
    -<option value="1">Lisabon</option>
    +<option value="1">Lisbon</option>
     <option value="2" selected="selected">Madrid</option>
     ...
    @@ -596,7 +596,7 @@ output: @person = Person.new(:city_id => 2) # view: -<%= select(:person, :city_id, [['Lisabon', 1], ['Madrid', 2], ...]) %> +<%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %>

    Notice that the third parameter, the options array, is the same kind of argument you pass to options_for_select. One advantage here is that you don’t have to worry about pre-selecting the correct city if the user already has one — Rails will do this for you by reading from the @person.city_id attribute.

    As with other helpers, if you were to use select helper on a form builder scoped to @person object, the syntax would be:

    @@ -618,6 +618,7 @@ output:
    ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750)

    when you pass the params hash to Person.new or update_attributes. Another way of looking at this is that form helpers only edit attributes.

    +

    You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. You may wish to consider the use of attr_protected and attr_accessible. For further details on this, see the Ruby On Rails Security Guide.

    @@ -625,7 +626,7 @@ output:

    Generating options tags with options_for_select requires that you create an array containing the text and value for each option. But what if you had a City model (perhaps an Active Record one) and you wanted to generate option tags from a collection of those objects? One solution would be to make a nested array by iterating over them:

    -
    <% cities_array = City.find(:all).map { |city| [city.name, city.id] } %>
    +
    <% cities_array = City.all.map { |city| [city.name, city.id] } %>
     <%= options_for_select(cities_array) %>

    This is a perfectly valid solution, but Rails provides a less verbose alternative: options_from_collection_for_select. This helper expects a collection of arbitrary objects and two additional arguments: the names of the methods to read the option value and text from, respectively:

    @@ -639,6 +640,16 @@ output:
    <%= collection_select(:person, :city_id, City.all, :id, :name) %>

    To recap, options_from_collection_for_select is to collection_select what options_for_select is to select.

    +
    + + + +
    +Note + +

    Pairs passed to options_for_select should have the name first and the id second, however with options_from_collection_for_select the first argument is the value method and the second the text method.

    +
    +

    3.4. Time zone and country select

    To leverage time zone support in Rails, you have to ask your users what time zone they are in. Doing so would require generating select options from a list of pre-defined TimeZone objects using collection_select, but you can simply use the time_zone_select helper that already wraps this:

    -- cgit v1.2.3 From 56be53f220f232bb8eb4b6843dff72aacb3b41a5 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 23:49:57 +0000 Subject: etc... => etc. --- railties/doc/guides/source/form_helpers.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index 1025dc8baf..6e62855649 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -498,7 +498,7 @@ The date and time helpers differ from all the other form helpers in two importan 1. Dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc.) and so there is no single value in your `params` hash with your date or time. 2. Other helpers use the _tag suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, `select\_date`, `select\_time` and `select_datetime` are the barebones helpers, `date_select`, `time_select` and `datetime_select` are the equivalent model object helpers. -Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc...). +Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc.). Barebones helpers ~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From f4c7d42fd439f69b2f9d46d62d33338ecf69c084 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 23:52:44 +0000 Subject: Some minor typos and corrections --- railties/doc/guides/source/form_helpers.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index 6e62855649..e6e591cfe9 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -550,7 +550,7 @@ NOTE: In many cases the built in date pickers are clumsy as they do not aid the Individual Components ~~~~~~~~~~~~~~~~~~~~~ -Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate a input named after the time component (for example "year" for `select_year`, "month" for `select_month` etc.) although this can be override with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. +Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate a input named after the time component (for example "year" for `select_year`, "month" for `select_month` etc.) although this can be overriden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. The first parameter specifies which value should be selected and can either be an instance of a Date, Time or DateTime, in which case the relevant component will be extracted, or a numerical value. For example @@ -559,7 +559,7 @@ The first parameter specifies which value should be selected and can either be a <%= select_year(Time.now) %> --------------- -Will produce the same output if the current year is 2009 and the value chosen by the user can be retrieved by `params[:date][:year]`. +will produce the same output if the current year is 2009 and the value chosen by the user can be retrieved by `params[:date][:year]`. Uploading Files -------------- @@ -597,15 +597,15 @@ NOTE: If the user has not selected a file the corresponding parameter will be an Dealing with Ajax ~~~~~~~~~~~~~~~~~ -Unlike other forms making an asynchronous file upload form is not as simple as replacing `form_for` with `remote_form_for`. With an AJAX form the serialization is done by javascript running inside the browser and since javascript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. +Unlike other forms making an asynchronous file upload form is not as simple as replacing `form_for` with `remote_form_for`. With an AJAX form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. Customising Form Builders ------------------------- -As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying a form elements for a single object. While you can of course write helpers for your forms in the usual way you can also subclass FormBuilder and add the helpers there. For example +As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way you can also subclass FormBuilder and add the helpers there. For example ---------- -<% form_for @person do |f| %> +<% form_for @person do |f| %> <%= text_field_with_label f, :first_name %> <% end %> ---------- @@ -620,7 +620,7 @@ by defining a LabellingFormBuilder class similar to the following: [source, ruby] ------- class LabellingFormBuilder < FormBuilder - def text_field attribute, options={} + def text_field(attribute, options={}) label(attribute) + text_field(attribute, options) end end @@ -631,7 +631,7 @@ The form builder used also determines what happens when you do ------ <%= render :partial => f %> ------ -If `f` is an instance of FormBuilder then this will render the 'form' partial, setting the partial's object to the form builder. If the form builder is of class LabellingFormBuilder then the 'labelling_form' partial would be rendered instead. +If `f` is an instance of FormBuilder then this will render the `form` partial, setting the partial's object to the form builder. If the form builder is of class LabellingFormBuilder then the `labelling_form` partial would be rendered instead. Understanding Parameter Naming Conventions ----------------------------------------- -- cgit v1.2.3 From 16f2215d29580919a1b7c3d8c11a4b7ef8efcb99 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 23:55:36 +0000 Subject: AJAX => Ajax --- railties/doc/guides/source/form_helpers.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index e6e591cfe9..b09cc530ee 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -597,7 +597,7 @@ NOTE: If the user has not selected a file the corresponding parameter will be an Dealing with Ajax ~~~~~~~~~~~~~~~~~ -Unlike other forms making an asynchronous file upload form is not as simple as replacing `form_for` with `remote_form_for`. With an AJAX form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. +Unlike other forms making an asynchronous file upload form is not as simple as replacing `form_for` with `remote_form_for`. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. Customising Form Builders ------------------------- -- cgit v1.2.3 From b3fd79d82d6c8124a62dbfc3c100d755fc2a4cd6 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 23:56:11 +0000 Subject: Rails.root instead of RAILS_ROOT --- railties/doc/guides/source/form_helpers.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index b09cc530ee..9c965c2a7d 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -579,7 +579,7 @@ Rails provides the usual pair of helpers: the barebones `file_field_tag` and the What gets uploaded ~~~~~~~~~~~~~~~~~~ -The object in the `params` hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#\{RAILS_ROOT\}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example). +The object in the `params` hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#\{Rails.root\}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example). [source, ruby] ----------------- -- cgit v1.2.3 From 893d053fce216cb117cfd9f98c8e583a39803161 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 23:56:37 +0000 Subject: no to excessive capitalisation --- railties/doc/guides/source/form_helpers.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index 9c965c2a7d..4e71753c77 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -547,7 +547,7 @@ As a rule of thumb you should be using `date_select` when working with model obj NOTE: In many cases the built in date pickers are clumsy as they do not aid the user in working out the relationship between the date and the day of the week. -Individual Components +Individual components ~~~~~~~~~~~~~~~~~~~~~ Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate a input named after the time component (for example "year" for `select_year`, "month" for `select_month` etc.) although this can be overriden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. -- cgit v1.2.3 From a1fe722263bd2ff177c9c35badac184c9885faa9 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 25 Jan 2009 23:59:11 +0000 Subject: More minor corrections --- railties/doc/guides/source/form_helpers.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index 4e71753c77..cddcc5f0c2 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -505,7 +505,7 @@ Barebones helpers The `select_*` family of helpers take as their first argument an instance of Date, Time or DateTime that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example ----------- -<%= select_date Date::today, :prefix => :start_date %> +<%= select_date Date.today, :prefix => :start_date %> ----------- outputs (with actual option values omitted for brevity) ----------- @@ -515,7 +515,7 @@ outputs (with actual option values omitted for brevity) ----------- The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual Time or Date object you would have to extract these values and pass them to the appropriate constructor, for example ----------- -Date::civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i) +Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i) ----------- The `:prefix` option is the key used to retrieve the hash of date components from the `params` hash. Here it was set to `start_date`, if omitted it will default to `date`. @@ -537,7 +537,7 @@ which results in a `params` hash like {:person => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}} -------------- -When this is passed to `Person.new` (or `update_attributes`), Active Record spots that these parameters should all be used to construct the `birth_date` attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as `Date::civil`. +When this is passed to `Person.new` (or `update_attributes`), Active Record spots that these parameters should all be used to construct the `birth_date` attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as `Date.civil`. Common options ~~~~~~~~~~~~~~ @@ -563,7 +563,7 @@ will produce the same output if the current year is 2009 and the value chosen by Uploading Files -------------- -A common task is uploading some sort of file, whether it's a picture of a person or a CSV file containing data to process. The most important thing to remember with file uploads is that the form's encoding *MUST* be set to multipart/form-data. If you forget to do this the file will not be uploaded. This can be done by passing `:multi_part => true` as an HTML option. This means that in the case of `form_tag` it must be passed in the second options hash and in the case of `form_for` inside the `:html` hash. +A common task is uploading some sort of file, whether it's a picture of a person or a CSV file containing data to process. The most important thing to remember with file uploads is that the form's encoding *MUST* be set to "multipart/form-data". If you forget to do this the file will not be uploaded. This can be done by passing `:multi_part => true` as an HTML option. This means that in the case of `form_tag` it must be passed in the second options hash and in the case of `form_for` inside the `:html` hash. The following two forms both upload a file. ----------- -- cgit v1.2.3 From 68ad1547f9e0e8dcf681af67d31a73550dbb6ebb Mon Sep 17 00:00:00 2001 From: CassioMarques Date: Sun, 25 Jan 2009 22:32:34 -0200 Subject: Starting to work at the Active Record Basics guide --- railties/doc/guides/html/active_record_basics.html | 124 ++++++++++++++++++--- .../doc/guides/source/active_record_basics.txt | 43 ++++++- 2 files changed, 146 insertions(+), 21 deletions(-) diff --git a/railties/doc/guides/html/active_record_basics.html b/railties/doc/guides/html/active_record_basics.html index 04f1e3e838..19275708a5 100644 --- a/railties/doc/guides/html/active_record_basics.html +++ b/railties/doc/guides/html/active_record_basics.html @@ -31,7 +31,16 @@

    Chapters

    1. - ORM The Blueprint of Active Record + What’s Active Record +
    2. +
    3. + Object Relational Mapping +
    4. +
    5. + ActiveRecord as an ORM framework +
    6. +
    7. + Active Record inside the MVC model
    8. Active Record The Engine of Rails @@ -66,15 +75,98 @@

      Active Record Basics

      -

      Active Record is a design pattern that mitigates the mind-numbing mental gymnastics often needed to get your application to communicate with a database. This guide uses a mix of real-world examples, metaphors and detailed explanations of the actual Rails source code to help you make the most of ActiveRecord.

      -

      After reading this guide readers should have a strong grasp of the Active Record pattern and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make them a stronger and better developer.

      +

      This guide will give you a strong grasp of the Active Record pattern and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make you a stronger and better developer.

      +

      After reading this guide we hope that you’ll be able to:

      +
        +
      • +

        +Understand the way Active Record fits into the MVC model. +

        +
      • +
      • +

        +Create basic Active Record models and map them with your database tables. +

        +
      • +
      • +

        +Use your models to execute CRUD (Create, Read, Update and Delete) database operations. +

        +
      • +
      • +

        +Follow the naming conventions used by Rails to make developing database applications easier and obvious. +

        +
      • +
      • +

        +Take advantage of the way Active Record maps it’s attributes with the database tables' columns to implement your application’s logic. +

        +
      • +
      • +

        +Use Active Record with legacy databases that do not follow the Rails naming conventions. +

        +
      • +
      +
      +
      +

      1. What’s Active Record

      +
      +

      Rails' ActiveRecord is an implementation of Martin Fowler’s Active Record Design Pattern. This pattern is based on the idea of creating relations between the database and the application in the following way:

      +
        +
      • +

        +Each database table is mapped to a class. +

        +
      • +
      • +

        +Each table column is mapped to an attribute of this class. +

        +
      • +
      • +

        +Each instance of this class is mapped to a single row in the database table. +

        +
      • +
      +

      2. Object Relational Mapping

      +
      +

      The relation between databases and object-oriented software is called ORM, which is short for "Object Relational Mapping". The purpose of an ORM framework is to minimize the mismatch existent between relational databases and object-oriented software. In applications with a domain model, we have objects that represent both the state of the system and the behaviour of the real world elements that were modeled through these objects. Since we need to store the system’s state somehow, we can use relational databases, which are proven to be an excelent approach to data management. Usually this may become a very hard thing to do, since we need to create an object-oriented model of everything that lives in the database, from simple columns to complicated relations between different tables. Doing this kind of thing by hand is a tedious and error prone job. This is where an ORM framework comes in.

      +
      +

      3. ActiveRecord as an ORM framework

      +
      +

      ActiveRecord gives us several mechanisms, being the most important ones the hability to:

      +
        +
      • +

        +Represent models. +

        +
      • +
      • +

        +Represent associations between these models. +

        +
      • +
      • +

        +Validate models before they get recorded to the database. +

        +
      • +
      • +

        +Perform database operations in an object-oriented fashion. +

        +
      • +
      +

      It’s easy to see that the Rails Active Record implementation goes way beyond the basic description of the Active Record Pattern.

      -

      1. ORM The Blueprint of Active Record

      +

      4. Active Record inside the MVC model

      -

      If Active Record is the engine of Rails then ORM is the blueprint of that engine. ORM is short for “Object Relational Mapping” and is a programming concept used to make structures within a system relational. As a thought experiment imagine the components that make up a typical car. There are doors, seats, windows, engines etc. Viewed independently they are simple parts, yet when bolted together through the aid of a blueprint, the parts become a more complex device. ORM is the blueprint that describes how the individual parts relate to one another and in some cases infers the part’s purpose through the way the associations are described.

      -

      2. Active Record The Engine of Rails

      +

      5. Active Record The Engine of Rails

      Active Record is a design pattern used to access data within a database. The name “Active Record” was coined by Martin Fowler in his book “Patterns of Enterprise Application Architecture”. Essentially, when a record is returned from the database instead of being just the data it is wrapped in a class, which gives you methods to control that data with. The rails framework is built around the MVC (Model View Controller) design patten and the Active Record is used as the default Model.

      The Rails community added several useful concepts to their version of Active Record, including inheritance and associations, which are extremely useful for web applications. The associations are created by using a DSL (domain specific language) of macros, and inheritance is achieved through the use of STI (Single Table Inheritance) at the database level.

      @@ -91,9 +183,9 @@ Class attributes & Database Table Columns

    -

    2.1. Rails Active Record Conventions

    +

    5.1. Rails Active Record Conventions

    Here are the key conventions to consider when using Active Record.

    -

    2.1.1. Naming Conventions

    +

    5.1.1. Naming Conventions

    Database Table - Plural with underscores separating words i.e. (book_clubs) Model Class - Singular with the first letter of each word capitalized i.e. (BookClub) Here are some additional Examples:

    @@ -157,7 +249,7 @@ cellspacing="0" cellpadding="4">
    -

    2.1.2. Schema Conventions

    +

    5.1.2. Schema Conventions

    To take advantage of some of the magic of Rails database tables must be modeled to reflect the ORM decisions that Rails makes.

    @@ -195,7 +287,7 @@ cellspacing="0" cellpadding="4">
    -

    2.1.3. Magic Field Names

    +

    5.1.3. Magic Field Names

    When these optional fields are used in your database table definition they give the Active Record instance additional features.

    @@ -332,11 +424,11 @@ http://www.gnu.org/software/src-highlite -->

    Rails further extends this model by giving each ActiveRecord a way of describing the variety of ways records are associated with one another. We will touch on some of these associations later in the guide but I encourage readers who are interested to read the guide to ActiveRecord associations for an in-depth explanation of the variety of ways rails can model associations. - Associations between objects controlled by meta-programming macros.

    -

    3. Philosophical Approaches & Common Conventions

    +

    6. Philosophical Approaches & Common Conventions

    Rails has a reputation of being a zero-config framework which means that it aims to get you off the ground with as little pre-flight checking as possible. This speed benefit is achieved by following “Convention over Configuration”, which is to say that if you agree to live with the defaults then you benefit from a the inherent speed-boost. As Courtneay Gasking put it to me once “You don’t want to off-road on Rails”. ActiveRecord is no different, while it’s possible to override or subvert any of the conventions of AR, unless you have a good reason for doing so you will probably be happy with the defaults. The following is a list of the common conventions of ActiveRecord

    -

    4. ActiveRecord Magic

    +

    7. ActiveRecord Magic

    • @@ -351,7 +443,7 @@ updates
    -

    5. How ActiveRecord Maps your Database.

    +

    8. How ActiveRecord Maps your Database.

    • @@ -366,10 +458,10 @@ overriding conventions
    -

    6. Growing Your Database Relationships Naturally

    +

    9. Growing Your Database Relationships Naturally

    -

    7. Attributes

    +

    10. Attributes

    • @@ -391,7 +483,7 @@ dirty records
    -

    8. Validations & Callbacks

    +

    11. Validations & Callbacks

    see the Validations & Callbacks guide for more info.

    diff --git a/railties/doc/guides/source/active_record_basics.txt b/railties/doc/guides/source/active_record_basics.txt index 367a1bba5e..d348b4dc0c 100644 --- a/railties/doc/guides/source/active_record_basics.txt +++ b/railties/doc/guides/source/active_record_basics.txt @@ -1,13 +1,46 @@ Active Record Basics ==================== -Active Record is a design pattern that mitigates the mind-numbing mental gymnastics often needed to get your application to communicate with a database. This guide uses a mix of real-world examples, metaphors and detailed explanations of the actual Rails source code to help you make the most of ActiveRecord. +This guide will give you a strong grasp of the Active Record pattern and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make you a stronger and better developer. + +After reading this guide we hope that you'll be able to: + +* Understand the way Active Record fits into the MVC model. +* Create basic Active Record models and map them with your database tables. +* Use your models to execute CRUD (Create, Read, Update and Delete) database operations. +* Follow the naming conventions used by Rails to make developing database applications easier and obvious. +* Take advantage of the way Active Record maps it's attributes with the database tables' columns to implement your application's logic. +* Use Active Record with legacy databases that do not follow the Rails naming conventions. + +== What's Active Record + +Rails' ActiveRecord is an implementation of Martin Fowler's http://martinfowler.com/eaaCatalog/activeRecord.html[Active Record Design Pattern]. This pattern is based on the idea of creating relations between the database and the application in the following way: + +* Each database table is mapped to a class. +* Each table column is mapped to an attribute of this class. +* Each instance of this class is mapped to a single row in the database table. + +== Object Relational Mapping + +The relation between databases and object-oriented software is called ORM, which is short for "Object Relational Mapping". The purpose of an ORM framework is to minimize the mismatch existent between relational databases and object-oriented software. In applications with a domain model, we have objects that represent both the state of the system and the behaviour of the real world elements that were modeled through these objects. Since we need to store the system's state somehow, we can use relational databases, which are proven to be an excelent approach to data management. Usually this may become a very hard thing to do, since we need to create an object-oriented model of everything that lives in the database, from simple columns to complicated relations between different tables. Doing this kind of thing by hand is a tedious and error prone job. This is where an ORM framework comes in. + +== ActiveRecord as an ORM framework + +ActiveRecord gives us several mechanisms, being the most important ones the hability to: + +* Represent models. +* Represent associations between these models. +* Validate models before they get recorded to the database. +* Perform database operations in an object-oriented fashion. + +It's easy to see that the Rails Active Record implementation goes way beyond the basic description of the Active Record Pattern. + +== Active Record inside the MVC model + + -After reading this guide readers should have a strong grasp of the Active Record pattern and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make them a stronger and better developer. -== ORM The Blueprint of Active Record -If Active Record is the engine of Rails then ORM is the blueprint of that engine. ORM is short for “Object Relational Mapping” and is a programming concept used to make structures within a system relational. As a thought experiment imagine the components that make up a typical car. There are doors, seats, windows, engines etc. Viewed independently they are simple parts, yet when bolted together through the aid of a blueprint, the parts become a more complex device. ORM is the blueprint that describes how the individual parts relate to one another and in some cases infers the part’s purpose through the way the associations are described. == Active Record The Engine of Rails @@ -151,4 +184,4 @@ Rails has a reputation of being a zero-config framework which means that it aim == ActiveRecord handling the CRUD of your Rails application - Understanding the life-cycle of an ActiveRecord == Validations & Callbacks -see the Validations & Callbacks guide for more info. \ No newline at end of file +see the Validations & Callbacks guide for more info. -- cgit v1.2.3 From 929088189de1d189c643246a491ba8fb5347b1d3 Mon Sep 17 00:00:00 2001 From: Christos Zisopoulos Date: Mon, 26 Jan 2009 01:36:36 +0100 Subject: An updated description of Object#try to better reflect changes documented at http://is.gd/hdmo and http://is.gd/hdmQ --- activesupport/lib/active_support/core_ext/try.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/try.rb b/activesupport/lib/active_support/core_ext/try.rb index 0dccd40c55..c7c7778a77 100644 --- a/activesupport/lib/active_support/core_ext/try.rb +++ b/activesupport/lib/active_support/core_ext/try.rb @@ -1,6 +1,9 @@ class Object - # Tries to send the method only if object responds to it. Return +nil+ otherwise. - # It will also forward any arguments and/or block like Object#send does. + # Invokes the method identified by +symbol+, passing it any arguments + # and/or the block specified, just like the regular Ruby Object#send does. + # + # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised + # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass. # # ==== Examples # @@ -12,13 +15,15 @@ class Object # With try # @person.try(:name) # - # Try also accepts arguments/blocks for the method it is trying + # +try+ also accepts arguments and/or a block, for the method it is trying # Person.try(:find, 1) # @people.try(:collect) {|p| p.name} #-- - # This method def is for rdoc only. The alias_method below overrides it as an optimization. - def try(method, *args, &block) - send(method, *args, &block) + # This method definition below is for rdoc purposes only. The alias_method call + # below overrides it as an optimization since +try+ behaves like +Object#send+, + # unless called on +NilClass+. + def try(symbol, *args, &block) + send(symbol, *args, &block) end alias_method :try, :__send__ end -- cgit v1.2.3 From c9b8a26ba1cd8277580fc2a554fe3a67a52f1756 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 26 Jan 2009 00:40:37 +0000 Subject: We have a very strict NO code changes policy. Revert "An updated description of Object#try to better reflect changes documented at http://is.gd/hdmo and http://is.gd/hdmQ" This reverts commit 929088189de1d189c643246a491ba8fb5347b1d3. --- activesupport/lib/active_support/core_ext/try.rb | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/try.rb b/activesupport/lib/active_support/core_ext/try.rb index c7c7778a77..0dccd40c55 100644 --- a/activesupport/lib/active_support/core_ext/try.rb +++ b/activesupport/lib/active_support/core_ext/try.rb @@ -1,9 +1,6 @@ class Object - # Invokes the method identified by +symbol+, passing it any arguments - # and/or the block specified, just like the regular Ruby Object#send does. - # - # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised - # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass. + # Tries to send the method only if object responds to it. Return +nil+ otherwise. + # It will also forward any arguments and/or block like Object#send does. # # ==== Examples # @@ -15,15 +12,13 @@ class Object # With try # @person.try(:name) # - # +try+ also accepts arguments and/or a block, for the method it is trying + # Try also accepts arguments/blocks for the method it is trying # Person.try(:find, 1) # @people.try(:collect) {|p| p.name} #-- - # This method definition below is for rdoc purposes only. The alias_method call - # below overrides it as an optimization since +try+ behaves like +Object#send+, - # unless called on +NilClass+. - def try(symbol, *args, &block) - send(symbol, *args, &block) + # This method def is for rdoc only. The alias_method below overrides it as an optimization. + def try(method, *args, &block) + send(method, *args, &block) end alias_method :try, :__send__ end -- cgit v1.2.3 From 6df580a0a8fe9c5df2353e5b435590a9a1cab3de Mon Sep 17 00:00:00 2001 From: Christos Zisopoulos Date: Mon, 26 Jan 2009 00:47:05 +0000 Subject: An updated description of Object#try to better reflect changes documented at http://is.gd/hdmo and http://is.gd/hdmQ Signed-off-by: Pratik Naik --- activesupport/lib/active_support/core_ext/try.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/try.rb b/activesupport/lib/active_support/core_ext/try.rb index 0dccd40c55..5cee97fd0d 100644 --- a/activesupport/lib/active_support/core_ext/try.rb +++ b/activesupport/lib/active_support/core_ext/try.rb @@ -1,6 +1,9 @@ class Object - # Tries to send the method only if object responds to it. Return +nil+ otherwise. - # It will also forward any arguments and/or block like Object#send does. + # Invokes the method identified by +symbol+, passing it any arguments + # and/or the block specified, just like the regular Ruby Object#send does. + # + # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised + # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass. # # ==== Examples # @@ -12,11 +15,13 @@ class Object # With try # @person.try(:name) # - # Try also accepts arguments/blocks for the method it is trying + # +try+ also accepts arguments and/or a block, for the method it is trying # Person.try(:find, 1) # @people.try(:collect) {|p| p.name} #-- - # This method def is for rdoc only. The alias_method below overrides it as an optimization. + # This method definition below is for rdoc purposes only. The alias_method call + # below overrides it as an optimization since +try+ behaves like +Object#send+, + # unless called on +NilClass+. def try(method, *args, &block) send(method, *args, &block) end -- cgit v1.2.3 From 4ef9845aa324679b88e19b8223dd90b774215bc6 Mon Sep 17 00:00:00 2001 From: Stephen Bannasch Date: Sat, 27 Dec 2008 15:37:47 -0500 Subject: Adding AR tests for JDBC connections New connections: jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb jdbcpostgresql To test you will need the native database installed (if one is required), activerecord-jdbc-adapter and the specific activerecord-jdbc-adapter for the database you are testing. Run the tests like this: jruby -S rake test_jdbcmysql Signed-off-by: Michael Koziarski [#1685 state:committed] --- activerecord/Rakefile | 8 +++++-- .../test/connections/jdbc_jdbcderby/connection.rb | 18 +++++++++++++++ .../test/connections/jdbc_jdbch2/connection.rb | 18 +++++++++++++++ .../test/connections/jdbc_jdbchsqldb/connection.rb | 18 +++++++++++++++ .../test/connections/jdbc_jdbcmysql/connection.rb | 26 ++++++++++++++++++++++ .../connections/jdbc_jdbcpostgresql/connection.rb | 26 ++++++++++++++++++++++ .../connections/jdbc_jdbcsqlite3/connection.rb | 25 +++++++++++++++++++++ 7 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 activerecord/test/connections/jdbc_jdbcderby/connection.rb create mode 100644 activerecord/test/connections/jdbc_jdbch2/connection.rb create mode 100644 activerecord/test/connections/jdbc_jdbchsqldb/connection.rb create mode 100644 activerecord/test/connections/jdbc_jdbcmysql/connection.rb create mode 100644 activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb create mode 100644 activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb diff --git a/activerecord/Rakefile b/activerecord/Rakefile index f47674d5b7..1c7e2603ee 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -32,9 +32,13 @@ task :default => :test desc 'Run mysql, sqlite, and postgresql tests' task :test => %w(test_mysql test_sqlite3 test_postgresql) -for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase ) +for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ) Rake::TestTask.new("test_#{adapter}") { |t| - t.libs << "test" << "test/connections/native_#{adapter}" + if adapter =~ /jdbc/ + t.libs << "test" << "test/connections/jdbc_#{adapter}" + else + t.libs << "test" << "test/connections/native_#{adapter}" + end adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/] t.test_files=Dir.glob( "test/cases/**/*_test{,_#{adapter_short}}.rb" ).sort t.verbose = true diff --git a/activerecord/test/connections/jdbc_jdbcderby/connection.rb b/activerecord/test/connections/jdbc_jdbcderby/connection.rb new file mode 100644 index 0000000000..222ef5db38 --- /dev/null +++ b/activerecord/test/connections/jdbc_jdbcderby/connection.rb @@ -0,0 +1,18 @@ +print "Using Derby via JRuby, activerecord-jdbc-adapter and activerecord-jdbcderby-adapter\n" +require_dependency 'models/course' +require 'logger' +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'jdbcderby', + :database => 'activerecord_unittest' + }, + 'arunit2' => { + :adapter => 'jdbcderby', + :database => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/jdbc_jdbch2/connection.rb b/activerecord/test/connections/jdbc_jdbch2/connection.rb new file mode 100644 index 0000000000..9d2875e8e7 --- /dev/null +++ b/activerecord/test/connections/jdbc_jdbch2/connection.rb @@ -0,0 +1,18 @@ +print "Using H2 via JRuby, activerecord-jdbc-adapter and activerecord-jdbch2-adapter\n" +require_dependency 'models/course' +require 'logger' +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'jdbch2', + :database => 'activerecord_unittest' + }, + 'arunit2' => { + :adapter => 'jdbch2', + :database => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/jdbc_jdbchsqldb/connection.rb b/activerecord/test/connections/jdbc_jdbchsqldb/connection.rb new file mode 100644 index 0000000000..fa943c2c76 --- /dev/null +++ b/activerecord/test/connections/jdbc_jdbchsqldb/connection.rb @@ -0,0 +1,18 @@ +print "Using HSQLDB via JRuby, activerecord-jdbc-adapter and activerecord-jdbchsqldb-adapter\n" +require_dependency 'models/course' +require 'logger' +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'jdbchsqldb', + :database => 'activerecord_unittest' + }, + 'arunit2' => { + :adapter => 'jdbchsqldb', + :database => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/jdbc_jdbcmysql/connection.rb b/activerecord/test/connections/jdbc_jdbcmysql/connection.rb new file mode 100644 index 0000000000..e2517a50eb --- /dev/null +++ b/activerecord/test/connections/jdbc_jdbcmysql/connection.rb @@ -0,0 +1,26 @@ +print "Using MySQL via JRuby, activerecord-jdbc-adapter and activerecord-jdbcmysql-adapter\n" +require_dependency 'models/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost'; +# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost'; + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'jdbcmysql', + :username => 'rails', + :encoding => 'utf8', + :database => 'activerecord_unittest', + }, + 'arunit2' => { + :adapter => 'jdbcmysql', + :username => 'rails', + :database => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' + diff --git a/activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb b/activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb new file mode 100644 index 0000000000..0685da4433 --- /dev/null +++ b/activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb @@ -0,0 +1,26 @@ +print "Using Postgrsql via JRuby, activerecord-jdbc-adapter and activerecord-postgresql-adapter\n" +require_dependency 'models/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +# createuser rails --createdb --no-superuser --no-createrole +# createdb -O rails activerecord_unittest +# createdb -O rails activerecord_unittest2 + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'jdbcpostgresql', + :username => ENV['USER'] || 'rails', + :database => 'activerecord_unittest' + }, + 'arunit2' => { + :adapter => 'jdbcpostgresql', + :username => ENV['USER'] || 'rails', + :database => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' + diff --git a/activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb b/activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb new file mode 100644 index 0000000000..26d4676ff3 --- /dev/null +++ b/activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb @@ -0,0 +1,25 @@ +print "Using SQLite3 via JRuby, activerecord-jdbc-adapter and activerecord-jdbcsqlite3-adapter\n" +require_dependency 'models/course' +require 'logger' +ActiveRecord::Base.logger = Logger.new("debug.log") + +class SqliteError < StandardError +end + +BASE_DIR = FIXTURES_ROOT +sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite3" +sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite3" + +def make_connection(clazz, db_file) + ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'jdbcsqlite3', :database => db_file, :timeout => 5000 } } + unless File.exist?(db_file) + puts "SQLite3 database not found at #{db_file}. Rebuilding it." + sqlite_command = %Q{sqlite3 "#{db_file}" "create table a (a integer); drop table a;"} + puts "Executing '#{sqlite_command}'" + raise SqliteError.new("Seems that there is no sqlite3 executable available") unless system(sqlite_command) + end + clazz.establish_connection(clazz.name) +end + +make_connection(ActiveRecord::Base, sqlite_test_db) +make_connection(Course, sqlite_test_db2) -- cgit v1.2.3 From f4bf318db061daa8c6ebb2b700fd15034e031310 Mon Sep 17 00:00:00 2001 From: Greg Borenstein Date: Mon, 19 Jan 2009 11:44:55 -0800 Subject: add an inspect method to OrderedHash to make it clear that it is not a species of Array Signed-off-by: Michael Koziarski [#1782 state:committed] --- activesupport/lib/active_support/ordered_hash.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 25ea505813..66aab9e562 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -92,6 +92,10 @@ module ActiveSupport dup.merge!(other_hash) end + def inspect + "#" + end + private def sync_keys! -- cgit v1.2.3 From 5c062bf1000886d26b3a4c3b08dfb6618a4adcdf Mon Sep 17 00:00:00 2001 From: Sven Fuchs Date: Thu, 8 Jan 2009 22:14:24 -0600 Subject: add #available_locales to I18n and I18n::SimpleBackend, flatten translations load_path when loading translations [#1714 state:resolved] Signed-off-by: Joshua Peek --- .../active_support/vendor/i18n-0.1.1/lib/i18n.rb | 5 +++++ .../vendor/i18n-0.1.1/lib/i18n/backend/simple.rb | 8 +++++++- .../vendor/i18n-0.1.1/test/simple_backend_test.rb | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb index b5ad094d0e..76361bed90 100755 --- a/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb +++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n.rb @@ -45,6 +45,11 @@ module I18n Thread.current[:locale] = locale end + # Returns an array of locales for which translations are available + def available_locales + backend.available_locales + end + # Sets the exception handler. def exception_handler=(exception_handler) @@exception_handler = exception_handler diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb index d298b3a85a..b54164d496 100644 --- a/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb +++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/lib/i18n/backend/simple.rb @@ -69,6 +69,12 @@ module I18n @initialized ||= false end + # Returns an array of locales for which translations are available + def available_locales + init_translations unless initialized? + translations.keys + end + def reload! @initialized = false @translations = nil @@ -76,7 +82,7 @@ module I18n protected def init_translations - load_translations(*I18n.load_path) + load_translations(*I18n.load_path.flatten) @initialized = true end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb index e181975f38..8ba7036abf 100644 --- a/activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb +++ b/activesupport/lib/active_support/vendor/i18n-0.1.1/test/simple_backend_test.rb @@ -124,6 +124,16 @@ class I18nSimpleBackendTranslationsTest < Test::Unit::TestCase end end +class I18nSimpleBackendAvailableLocalesTest < Test::Unit::TestCase + def test_available_locales + @backend = I18n::Backend::Simple.new + @backend.store_translations 'de', :foo => 'bar' + @backend.store_translations 'en', :foo => 'foo' + + assert_equal ['de', 'en'], @backend.available_locales.map{|locale| locale.to_s }.sort + end +end + class I18nSimpleBackendTranslateTest < Test::Unit::TestCase include I18nSimpleBackendTestSetup @@ -472,6 +482,18 @@ class I18nSimpleBackendLoadTranslationsTest < Test::Unit::TestCase end end +class I18nSimpleBackendLoadPathTest < Test::Unit::TestCase + include I18nSimpleBackendTestSetup + + def test_nested_load_paths_do_not_break_locale_loading + @backend = I18n::Backend::Simple.new + I18n.load_path = [[File.dirname(__FILE__) + '/locale/en.yml']] + assert_nil backend_get_translations + assert_nothing_raised { @backend.send :init_translations } + assert_not_nil backend_get_translations + end +end + class I18nSimpleBackendReloadTranslationsTest < Test::Unit::TestCase include I18nSimpleBackendTestSetup -- cgit v1.2.3 From a98cd7ca9b2f24a4500963e58ba5c37d6bdf9259 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sun, 25 Jan 2009 22:50:02 -0600 Subject: Add localized templates # Default locale app/views/messages/index.html.erb # I18n.locale is set to :da (Danish) app/views/messages/index.da.html.erb --- actionpack/lib/action_view/paths.rb | 6 ++- actionpack/lib/action_view/template.rb | 56 ++++++++++++++++------ actionpack/test/abstract_unit.rb | 4 ++ .../test/fixtures/test/hello_world.da.html.erb | 1 + actionpack/test/template/render_test.rb | 15 ++++++ 5 files changed, 67 insertions(+), 15 deletions(-) create mode 100644 actionpack/test/fixtures/test/hello_world.da.html.erb diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index 19207e7262..cb351620d2 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -37,7 +37,11 @@ module ActionView #:nodoc: template_path = original_template_path.sub(/^\//, '') each do |load_path| - if format && (template = load_path["#{template_path}.#{format}"]) + if format && (template = load_path["#{template_path}.#{I18n.locale}.#{format}"]) + return template + elsif format && (template = load_path["#{template_path}.#{format}"]) + return template + elsif template = load_path["#{template_path}.#{I18n.locale}"] return template elsif template = load_path[template_path] return template diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 9d1e0d3ac5..6f3bf576eb 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -93,13 +93,14 @@ module ActionView #:nodoc: @@exempt_from_layout.merge(regexps) end - attr_accessor :filename, :load_path, :base_path, :name, :format, :extension + attr_accessor :filename, :load_path, :base_path + attr_accessor :locale, :name, :format, :extension delegate :to_s, :to => :path def initialize(template_path, load_paths = []) template_path = template_path.dup @load_path, @filename = find_full_path(template_path, load_paths) - @base_path, @name, @format, @extension = split(template_path) + @base_path, @name, @locale, @format, @extension = split(template_path) @base_path.to_s.gsub!(/\/$/, '') # Push to split method # Extend with partial super powers @@ -137,17 +138,17 @@ module ActionView #:nodoc: memoize :mime_type def path - [base_path, [name, format, extension].compact.join('.')].compact.join('/') + [base_path, [name, locale, format, extension].compact.join('.')].compact.join('/') end memoize :path def path_without_extension - [base_path, [name, format].compact.join('.')].compact.join('/') + [base_path, [name, locale, format].compact.join('.')].compact.join('/') end memoize :path_without_extension def path_without_format_and_extension - [base_path, name].compact.join('/') + [base_path, [name, locale].compact.join('.')].compact.join('/') end memoize :path_without_format_and_extension @@ -207,6 +208,10 @@ module ActionView #:nodoc: !Template.registered_template_handler(extension).nil? end + def valid_locale?(locale) + I18n.available_locales.include?(locale.to_sym) + end + def find_full_path(path, load_paths) load_paths = Array(load_paths) + [nil] load_paths.each do |load_path| @@ -217,19 +222,42 @@ module ActionView #:nodoc: end # Returns file split into an array - # [base_path, name, format, extension] + # [base_path, name, locale, format, extension] def split(file) - if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/) - if valid_extension?(m[5]) # Multipart formats - [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]] - elsif valid_extension?(m[4]) # Single format - [m[1], m[2], m[3], m[4]] - elsif valid_extension?(m[3]) # No format - [m[1], m[2], nil, m[3]] + if m = file.match(/^(.*\/)?([^\.]+)\.(.*)$/) + base_path = m[1] + name = m[2] + extensions = m[3] + else + return + end + + locale = nil + format = nil + extension = nil + + if m = extensions.match(/^(\w+)?\.?(\w+)?\.?(\w+)?\.?/) + if valid_locale?(m[1]) && m[2] && valid_extension?(m[3]) # All three + locale = m[1] + format = m[2] + extension = m[3] + elsif m[1] && m[2] && valid_extension?(m[3]) # Multipart formats + format = "#{m[1]}.#{m[2]}" + extension = m[3] + elsif valid_extension?(m[1]) # Just extension + extension = m[1] + elsif valid_locale?(m[1]) && valid_extension?(m[2]) # locale and extension + locale = m[1] + extension = m[2] + elsif valid_extension?(m[2]) # format and extension + format = m[1] + extension = m[2] else # No extension - [m[1], m[2], m[3], nil] + format = m[1] end end + + [base_path, name, locale, format, extension] end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 30e2d863d0..4baebcb4d1 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -32,6 +32,10 @@ ActionController::Routing::Routes.reload rescue nil ActionController::Base.session_store = nil +# Register danish language for testing +I18n.backend.store_translations 'da', {} +ORIGINAL_LOCALES = I18n.available_locales + FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') ActionController::Base.view_paths = FIXTURE_LOAD_PATH diff --git a/actionpack/test/fixtures/test/hello_world.da.html.erb b/actionpack/test/fixtures/test/hello_world.da.html.erb new file mode 100644 index 0000000000..10ec443291 --- /dev/null +++ b/actionpack/test/fixtures/test/hello_world.da.html.erb @@ -0,0 +1 @@ +Hey verden \ No newline at end of file diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 4bd897efeb..c226e212b5 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -5,6 +5,13 @@ module RenderTestCases def setup_view(paths) @assigns = { :secret => 'in the sauce' } @view = ActionView::Base.new(paths, @assigns) + + # Reload and register danish language for testing + I18n.reload! + I18n.backend.store_translations 'da', {} + + # Ensure original are still the same since we are reindexing view paths + assert_equal ORIGINAL_LOCALES, I18n.available_locales end def test_render_file @@ -19,6 +26,14 @@ module RenderTestCases assert_equal "Hello world!", @view.render(:file => "test/hello_world") end + def test_render_file_with_localization + old_locale = I18n.locale + I18n.locale = :da + assert_equal "Hey verden", @view.render(:file => "test/hello_world") + ensure + I18n.locale = old_locale + end + def test_render_file_at_top_level assert_equal 'Elastica', @view.render(:file => '/shared') end -- cgit v1.2.3 From 37fac27cd9bbd1a0e0a0325a87bbfa70cc7baa47 Mon Sep 17 00:00:00 2001 From: Dimitri Krassovski Date: Mon, 26 Jan 2009 10:36:31 +0200 Subject: A small addition to the fragment caching documentation --- actionpack/lib/action_controller/caching/fragments.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 95cba0e411..aa30b57760 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -25,6 +25,13 @@ module ActionController #:nodoc: # The expiration call for this example is: # # expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics") + # + # You can also pass a options hash to the call, which is passed on to the read and write methods of your fragment cache store. For example, + # if you are using the MemCacheStore, then you can pass the :expire_in option to make the fragment expire in a certain amount of time. + # + # <% cache "latest_photos", :expires_in => 5.minutes do %> + # <%= render :partial => "photo", :collection => Photo.latest%> + # <% end%> module Fragments # Given a key (as described in expire_fragment), returns a key suitable for use in reading, # writing, or expiring a cached fragment. If the key is a hash, the generated key is the return -- cgit v1.2.3 From 72bd7ed807192a0bf8bf60b6525f767bc67b1bde Mon Sep 17 00:00:00 2001 From: Christos Zisopoulos Date: Mon, 26 Jan 2009 10:18:58 +0100 Subject: Updated Object#try doc to match lifo's "no code change" revert c9b8a26ba1cd8277580fc2a554fe3a67a52f1756 --- activesupport/lib/active_support/core_ext/try.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/try.rb b/activesupport/lib/active_support/core_ext/try.rb index 5cee97fd0d..54a38c5189 100644 --- a/activesupport/lib/active_support/core_ext/try.rb +++ b/activesupport/lib/active_support/core_ext/try.rb @@ -1,5 +1,5 @@ class Object - # Invokes the method identified by +symbol+, passing it any arguments + # Invokes the method identified by the symbol +method+, passing it any arguments # and/or the block specified, just like the regular Ruby Object#send does. # # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised -- cgit v1.2.3 From de7100dcb6a155f3eb22b859adefbd0672d6d94c Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 26 Jan 2009 12:56:43 +0100 Subject: Re-wrote intro section w/r/t to i18n/L18n difference, more concrete description --- railties/doc/guides/source/i18n.txt | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index ca8bf2f783..2ec28cd3d8 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -3,10 +3,19 @@ The Rails Internationalization (I18n) API The Ruby I18n (shorthand for _internationalization_) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for *translating your application to a single custom language* other than English or for *providing multi-language support* in your application. -In the process of _localizing_ your application you'll probably want to do following two things: +The process of "internationalization" usually means to abstract all strings and other locale specific bits (such as date or currency formats) out of your application. The process of "localization" means to provide translations and localized formats for these bits. <<1>> + +So, in the process of _internationalizing_ your Rails application you have to: + +* Ensure you have support for i18n +* Tell Rails where to find locale dictionaries +* Tell Rails how to set, preserve and switch locale + +In the process of _localizing_ your application you'll probably want to do following three things: * Replace or supplement Rail's default locale -- eg. date and time formats, month names, ActiveRecord model names, etc * Abstract texts in your application into keyed dictionaries -- eg. flash messages, static texts in your views, etc +* Store the resulting dictionaries somewhere This guide will walk you through the I18n API and contains a tutorial how to internationalize a Rails application from the start. @@ -284,13 +293,14 @@ IMPORTANT: This solution has currently one rather big *downside*. Due to the _de # TODO: Accept-Language, GeoIP, etc. Explain why it is not such a good idea in most cases. -OK! Now you've initialized I18n support for your application and told it which locale should be used and how to preserve it between requests. With that in place you're now ready for the really interesting stuff. -== Internationalize your application +== Internationalizing your application + +OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale should be used and how to preserve it between requests. With that in place you're now ready for the really interesting stuff. -The process of "internationalization" usually means to abstract all strings and other locale specific bits out of your application. The process of "localization" means to then provide translations and localized formats for these bits. <<1>> +Let's _internationalize_ our application, ie. abstract every locale-specific parts, and that _localize_ it, ie. provide neccessary translations for these abstracts. -So, let's internationalize something. You most probably have something like this in one of your applications: +You most probably have something like this in one of your applications: [source, ruby] ------------------------------------------------------- -- cgit v1.2.3 From aae0bec4f6db00435e833f76b244757caa9626b8 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 26 Jan 2009 13:35:54 +0100 Subject: Clarifications for the i18n guide: descriptions of working with URLs like "/en/books" --- railties/doc/guides/source/i18n.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index 2ec28cd3d8..e3b3877760 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -265,7 +265,7 @@ Every helper method dependent on +url_for+ (eg. helpers for named routes like +r You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of application domain: and URLs should reflect this. -You probably want URLs look like this: +www.example.com/en/books+ versus +www.example.com/nl/books+. This is achievable with the over-riding +default_url_options+ strategy: you just have to set up your routes with http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354[+path_prefix+] option in this way: +You probably want URLs look like this: +www.example.com/en/books+ (which loads English locale) and +www.example.com/nl/books+ (which loads Netherlands locale). This is achievable with the "over-riding +default_url_options+" strategy from above: you just have to set up your routes with http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354[+path_prefix+] option in this way: [source, ruby] ------------------------------------------------------- @@ -273,7 +273,7 @@ You probably want URLs look like this: +www.example.com/en/books+ versus +www.ex map.resources :books, :path_prefix => '/:locale' ------------------------------------------------------- -Now, when you call +books_path+ method you should get +"/en/books"+ (for the default locale). An URL like +http://localhost:3001/nl/books+ should load the Netherlands locale, then, and so on. +Now, when you call +books_path+ method you should get +"/en/books"+ (for the default locale). An URL like +http://localhost:3001/nl/books+ should load the Netherlands locale, then, and following call to +books_path+ should return +"/nl/books"+ (because the locale changed). Of course, you need to take special care of root URL (usually "homepage" or "dashboard") of your application. An URL like +http://localhost:3001/nl+ will not work automatically, because the +map.root :controller => "dashboard"+ declaration in your +routes.rb+ doesn't take locale into account. (And rightly so. There's only one "root" URL.) -- cgit v1.2.3 From 0b02e443a130db0f4f6bc4c0839bf4dad76cd7d5 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 26 Jan 2009 13:40:35 +0100 Subject: Clarifying the need and context of patching i18n in Rails 2.2, so we have "available_locales" available {i18n guide} --- railties/doc/guides/source/i18n.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index e3b3877760..6a20133254 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -151,7 +151,7 @@ Of course, you probably don't want to manually include locale in every URL all o IMPORTANT: Following examples rely on having locales loaded into your application available as an array of strings like +["en", "es", "gr"]+. This is not inclued in current version of Rails 2.2 -- forthcoming Rails version 2.3 will contain easy accesor +available_locales+. (See http://github.com/svenfuchs/i18n/commit/411f8fe7[this commit] and background at http://rails-i18n.org/wiki/pages/i18n-available_locales[Rails I18n Wiki].) -We have to include support for getting available locales manually in an initializer like this: +So, for having available locales easily available in Rails 2.2, we have to include this support manually in an initializer, like this: [source, ruby] ------------------------------------------------------- -- cgit v1.2.3 From c73f17f379e0e53b7845c4c4a1a5dd4b5ea62dc3 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 26 Jan 2009 14:23:52 +0100 Subject: Added note about Rails' i18n pragmatic to locale keys and differences between eg. en-US / en-UK. Based on feedback from Matt Falkenhagen, http://groups.google.com/group/rails-i18n/browse_thread/thread/64ce98be0fe48388?hl=en --- railties/doc/guides/source/i18n.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index 6a20133254..70405ca6c6 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -94,6 +94,8 @@ This means, that in the +:en+ locale, the key _hello_ will map to _Hello world_ The I18n library will use *English* as a *default locale*, ie. if you don't set a different locale, +:en+ will be used for looking up translations. +NOTE: The i18n library takes *pragmatic approach* to locale keys (after http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en[some discussion]), including only the _locale_ ("language") part, like +:en+, +:pl+, not the _region_ part, like +:en-US+ or +:en-UK+, which are traditionally used for separating "languages" and "regional setting" or "dialects". (For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have €. Also, insults would be different in American and British English :) Reason for this pragmatic approach is that most of the time, you usually care about making your application available in different "languages", and working with locales is much simpler this way. However, nothing stops you from separating regional and other settings in the traditional way. In this case, you could eg. inherit from the default +en+ locale and then provide UK specific settings in a +:en-UK+ dictionary. + The *translations load path* (+I18n.load_path+) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you. NOTE: The backend will lazy-load these translations when a translation is looked up for the first time. This makes it possible to just swap the backend with something else even after translations have already been announced. -- cgit v1.2.3 From 6e32e369a9484ce57a46e3e5b780d5a6effd33a3 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 26 Jan 2009 15:24:41 +0100 Subject: First part of "Setting locale from the client supplied information" section (using HTTP Accept-Language) {i18n guide} --- railties/doc/guides/source/i18n.txt | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index 70405ca6c6..f5cdefd180 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -275,7 +275,7 @@ You probably want URLs look like this: +www.example.com/en/books+ (which loads E map.resources :books, :path_prefix => '/:locale' ------------------------------------------------------- -Now, when you call +books_path+ method you should get +"/en/books"+ (for the default locale). An URL like +http://localhost:3001/nl/books+ should load the Netherlands locale, then, and following call to +books_path+ should return +"/nl/books"+ (because the locale changed). +Now, when you call +books_path+ method you should get +"/en/books"+ (for the default locale). An URL like +http://localhost:3001/nl/books+ should load the Netherlands locale, then, and following calls to +books_path+ should return +"/nl/books"+ (because the locale changed). Of course, you need to take special care of root URL (usually "homepage" or "dashboard") of your application. An URL like +http://localhost:3001/nl+ will not work automatically, because the +map.root :controller => "dashboard"+ declaration in your +routes.rb+ doesn't take locale into account. (And rightly so. There's only one "root" URL.) @@ -293,9 +293,38 @@ IMPORTANT: This solution has currently one rather big *downside*. Due to the _de === Setting locale from the client supplied information -# TODO: Accept-Language, GeoIP, etc. Explain why it is not such a good idea in most cases. +In specific cases, it would make sense to set locale from client supplied information, ie. not from URL. This information may come for example from users' preffered language (set in their browser), can be based on users' geographical location inferred from their IP, or users can provide it simply by choosing locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites -- see the box about _sessions_, _cookies_ and RESTful architecture above. +==== Using Accept-Language + +One source of client supplied information would be an +Accept-Language+ HTTP header. People may http://www.w3.org/International/questions/qa-lang-priorities[set this in their browser] or other clients (such as _curl_). + +A trivial implementation of using +Accept-Language+ header would be: + +[source, ruby] +------------------------------------------------------- +def set_locale + logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}" + I18n.locale = extract_locale_from_accept_language_header + logger.debug "* Locale set to '#{I18n.locale}'" +end +private +def extract_locale_from_accept_language_header + request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first +end +------------------------------------------------------- + +Of course, in production environment you would need much robust code, and could use a plugin such as Iaian Hecker's http://github.com/iain/http_accept_language[http_accept_language]. + +==== Using GeoIP (or similar) database + +#TODO http://www.maxmind.com/app/geolitecountry + +==== User profile + +#TODO + == Internationalizing your application OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale should be used and how to preserve it between requests. With that in place you're now ready for the really interesting stuff. -- cgit v1.2.3 From 1b8131bb94c16e8b7945e5819949fe2ecffb2082 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 26 Jan 2009 16:18:28 +0100 Subject: Finished section about "Setting the locale from the client's information": 'Using Accept-Language' and 'Using GeoIP (or similar) database' {i18n guide} --- railties/doc/guides/source/i18n.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index f5cdefd180..87a5f24a16 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -319,11 +319,11 @@ Of course, in production environment you would need much robust code, and could ==== Using GeoIP (or similar) database -#TODO http://www.maxmind.com/app/geolitecountry +Another way of choosing the locale from client's information would be to use a database for mapping client IP to region, such as http://www.maxmind.com/app/geolitecountry[GeoIP Lite Country]. The mechanics of the code would be very similar to the code above -- you would need to query database for user's IP, and lookup your preffered locale for the country/region/city returned. ==== User profile -#TODO +You can also provide users of your application with means to set (and possibly over-ride) locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above -- you'd probably let users choose a locale from a dropdown list and save it to their profile in database. Then you'd set the locale to this value. == Internationalizing your application -- cgit v1.2.3 From b24f136fd418815aec88afff2a9459b91fde6dab Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 26 Jan 2009 16:26:05 +0100 Subject: Fixed grammar and formatting issues in i18n guide --- railties/doc/guides/source/i18n.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index 87a5f24a16..2167e389e3 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -327,7 +327,7 @@ You can also provide users of your application with means to set (and possibly o == Internationalizing your application -OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale should be used and how to preserve it between requests. With that in place you're now ready for the really interesting stuff. +OK! Now you've initialized I18n support for your Ruby on Rails application and told it which locale should be used and how to preserve it between requests. With that in place, you're now ready for the really interesting stuff. Let's _internationalize_ our application, ie. abstract every locale-specific parts, and that _localize_ it, ie. provide neccessary translations for these abstracts. @@ -356,7 +356,7 @@ image:images/i18n/demo_untranslated.png[rails i18n demo untranslated] === Adding Translations -Obviously there are *two strings that are localized to English*. In order to internationalize this code replace these strings with calls to Rails' +#t+ helper with a key that makes sense for the translation: +Obviously there are *two strings that are localized to English*. In order to internationalize this code, *replace these strings* with calls to Rails' +#t+ helper with a key that makes sense for the translation: [source, ruby] ------------------------------------------------------- @@ -372,13 +372,13 @@ end

    <%= flash[:notice] %>

    ------------------------------------------------------- -When you now render this view it will show an error message that tells you that the translations for the keys :hello_world and :hello_flash are missing. +When you now render this view, it will show an error message which tells you that the translations for the keys +:hello_world+ and +:hello_flash+ are missing. image:images/i18n/demo_translation_missing.png[rails i18n demo translation missing] -NOTE: Rails adds a +t+ (+translate+) helper method to your views so that you do not need to spell out +I18n.t+ all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a <span class="translation_missing">. +NOTE: Rails adds a +t+ (+translate+) helper method to your views so that you do not need to spell out +I18n.t+ all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a ++. -So let's add the missing translations (i.e. do the "localization" part): +So let's add the missing translations into the dictionary files (i.e. do the "localization" part): [source, ruby] ------------------------------------------------------- @@ -397,7 +397,7 @@ There you go. Because you haven't changed the default_locale, I18n will use Engl image:images/i18n/demo_translated_en.png[rails i18n demo translated to english] -And when you change the URL to pass the pirate locale you get: +And when you change the URL to pass the pirate locale (+http://localhost:3000?locale=pirate+), you'll get: image:images/i18n/demo_translated_pirate.png[rails i18n demo translated to pirate] -- cgit v1.2.3 From 095c89baebb5b72dc126c45669512d74eec80541 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 26 Jan 2009 17:04:56 +0100 Subject: Finished the tutorial part of i18n guide (organization of locale files) --- railties/doc/guides/source/i18n.txt | 38 ++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index 2167e389e3..ca03808c02 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -432,10 +432,46 @@ image:images/i18n/demo_localized_pirate.png[rails i18n demo localized time to pi TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected. Of course, there's a great chance that somebody already did all the work by *translating Rails's defaults for your locale*. See the http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale[rails-i18n repository at Github] for an archive of various locale files. When you put such file(s) in +config/locale/+ directory, they will automatically ready for use. +=== Organization of locale files + +When you are using the default SimpleStore, shipped with the i18n library, you store dictionaries in plain-text files on the disc. Putting translations for all parts of your application in one file per locale could be hard to manage. You can store these files in a hierarchy which makes sense to you. + +For example, your +config/locale+ directory could look like this: + +------------------------------------------------------- +|-defaults +|---es.rb +|---en.rb +|-models +|---book +|-----es.rb +|-----en.rb +|-views +|---defaults +|-----es.rb +|-----en.rb +|---books +|-----es.rb +|-----en.rb +|---users +|-----es.rb +|-----en.rb +|---navigation +|-----es.rb +|-----en.rb +------------------------------------------------------- + +This way, you can separate model and model attribute names from text inside views, and all of this from the "defaults" (eg. date and time formats). + +Other stores for the i18n library could provide different means of such separation. + +Do check the http://rails-i18n.org/wiki[Rails i18n Wiki] for list of tools available for managing translations. == Overview of the I18n API features -The following purposes are covered: +By knowing all neccessary aspects of internationalizing a basic Ruby on Rails application, now you should have good understanding of using the i18n library. In the following chapters, we'll cover it's features in more depth. + +Covered are features like these: * lookup translations * interpolate data into translations -- cgit v1.2.3 From 41fbf746c2feb4ddde64bd465207c9d8dfda6ec9 Mon Sep 17 00:00:00 2001 From: Karel Minarik Date: Mon, 26 Jan 2009 19:52:53 +0100 Subject: Fixing stylistic issues in i18n guide --- railties/doc/guides/source/i18n.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/railties/doc/guides/source/i18n.txt b/railties/doc/guides/source/i18n.txt index ca03808c02..4465a77289 100644 --- a/railties/doc/guides/source/i18n.txt +++ b/railties/doc/guides/source/i18n.txt @@ -469,14 +469,14 @@ Do check the http://rails-i18n.org/wiki[Rails i18n Wiki] for list of tools avail == Overview of the I18n API features -By knowing all neccessary aspects of internationalizing a basic Ruby on Rails application, now you should have good understanding of using the i18n library. In the following chapters, we'll cover it's features in more depth. +You should have good understanding of using the i18n library now, knowing all neccessary aspects of internationalizing a basic Rails application. In the following chapters, we'll cover it's features in more depth. Covered are features like these: -* lookup translations -* interpolate data into translations -* pluralize translations -* localize dates, numbers, currency etc. +* looking up translations +* interpolating data into translations +* pluralizing translations +* localizing dates, numbers, currency etc. === Looking up translations -- cgit v1.2.3 From 57f0b859004698a38d6aa7d5cebc00de3fcb346d Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 26 Jan 2009 13:15:29 -0600 Subject: Fix for failing ActionMailer multipart tests --- actionpack/lib/action_view/template.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 6f3bf576eb..1361a969a9 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -244,14 +244,14 @@ module ActionView #:nodoc: elsif m[1] && m[2] && valid_extension?(m[3]) # Multipart formats format = "#{m[1]}.#{m[2]}" extension = m[3] - elsif valid_extension?(m[1]) # Just extension - extension = m[1] elsif valid_locale?(m[1]) && valid_extension?(m[2]) # locale and extension locale = m[1] extension = m[2] elsif valid_extension?(m[2]) # format and extension format = m[1] extension = m[2] + elsif valid_extension?(m[1]) # Just extension + extension = m[1] else # No extension format = m[1] end -- cgit v1.2.3 From 9a5c2d3d5cbe9c8380f921e7f44ae0646d8163a4 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Mon, 26 Jan 2009 20:35:21 +0000 Subject: remove stray backslashes --- railties/doc/guides/source/form_helpers.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index cddcc5f0c2..7ad170aaa2 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -496,7 +496,7 @@ Using Date and Time Form Helpers The date and time helpers differ from all the other form helpers in two important respects: 1. Dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc.) and so there is no single value in your `params` hash with your date or time. -2. Other helpers use the _tag suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, `select\_date`, `select\_time` and `select_datetime` are the barebones helpers, `date_select`, `time_select` and `datetime_select` are the equivalent model object helpers. +2. Other helpers use the _tag suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, `select_date`, `select_time` and `select_datetime` are the barebones helpers, `date_select`, `time_select` and `datetime_select` are the equivalent model object helpers. Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc.). -- cgit v1.2.3 From d28045fd87026ddf514ed8d9296e9239ff88aab6 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Mon, 26 Jan 2009 20:43:02 +0000 Subject: corrections from Xavier --- railties/doc/guides/source/form_helpers.txt | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index 7ad170aaa2..f48b40c0ab 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -637,7 +637,7 @@ Understanding Parameter Naming Conventions ----------------------------------------- [[parameter_names]] -As you've seen in the previous sections, values from forms can be at the top level of the `params` hash or nested in another hash. For example in a standard create +As you've seen in the previous sections, values from forms can be at the top level of the `params` hash or nested in another hash. For example in a standard `create` action for a Person model, `params[:model]` would usually be a hash of all the attributes for the person to create. The `params` hash can also contain arrays, arrays of hashes and so on. Fundamentally HTML forms don't know about any sort of structured data, all they generate is name-value pairs. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses. @@ -647,8 +647,8 @@ Fundamentally HTML forms don't know about any sort of structured data, all they You may find you can try out examples in this section faster by using the console to directly invoke Rails' parameter parser. For example ------------- -ActionController::RequestParser.parse_query_parameters "name=fred&phone=0123456789" -#=> {"name"=>"fred", "phone"=>"0123456789"} +ActionController::UrlEncodedPairParser.parse_query_parameters "name=fred&phone=0123456789" +# => {"name"=>"fred", "phone"=>"0123456789"} ------------- ======================== @@ -660,7 +660,6 @@ The two basic structures are arrays and hashes. Hashes mirror the syntax used fo ----------------- the `params` hash will contain -[source, ruby] ----------------- {'person' => {'name' => 'Henry'}} ----------------- @@ -672,7 +671,6 @@ Hashes can be nested as many levels as required, for example ------------------ will result in the `params` hash being -[source, ruby] ----------------- {'person' => {'address' => {'city' => 'New York'}}} ----------------- @@ -693,9 +691,9 @@ We can mix and match these two concepts. For example, one element of a hash migh ----------------- -This would result in `params[:addresses]` being an array of hashes with keys `line1`, `line2` and `city`. Rails decides to start accumulating values in a new hash whenever it encounters a input name that already exists in the current hash. +This would result in `params[:addresses]` being an array of hashes with keys `line1`, `line2` and `city`. Rails decides to start accumulating values in a new hash whenever it encounters an input name that already exists in the current hash. -The one restriction is that although hashes can be nested arbitrarily deep then can be only one level of "arrayness". Arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id, an array index or some other parameter. +There's a restriction, however, while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id, an array index or some other parameter. [WARNING] Array parameters do not play well with the `check_box` helper. According to the HTML specification unchecked checkboxes submit no value. However it is often convenient for a checkbox to always submit a value. The `check_box` helper fakes this by creating a second hidden input with the same name. If the checkbox is unchecked only the hidden input is submitted and if it is checked then both are submitted but the value submitted by the checkbox takes precedence. When working with array parameters this duplicate submission will confuse Rails since duplicate input names are how it decides when to start a new array element. It is preferable to either use `check_box_tag` or to use hashes instead of arrays. @@ -707,7 +705,7 @@ The previous sections did not use the Rails form helpers at all. While you can c You might want to render a form with a set of edit fields for each of a person's addresses. For example: -------- <% form_for @person do |person_form| %> - <%= person_form.text_field :name%> + <%= person_form.text_field :name %> <% for address in @person.addresses %> <% person_form.fields_for address, :index => address do |address_form|%> <%= address_form.text_field :city %> @@ -725,9 +723,8 @@ Assuming the person had two addresses, with ids 23 and 45 this would create outp -------- This will result in a `params` hash that looks like -[source, ruby] -------- -{'person' => {'name' => 'Bob', 'address' => { '23' => {'city' => 'Paris'}, '45' => {'city' => 'London'} }}} +{'person' => {'name' => 'Bob', 'address' => {'23' => {'city' => 'Paris'}, '45' => {'city' => 'London'}}}} -------- Rails knows that all these inputs should be part of the person hash because you called `fields_for` on the first form builder. By specifying an `:index` option you're telling rails that instead of naming the inputs `person[address][city]` it should insert that index surrounded by [] between the address and the city. If you pass an Active Record object as we did then Rails will call `to_param` on it, which by default returns the database id. This is often useful as it is then easy to locate which Address record should be modified. You can pass numbers with some other significance, strings or even nil (which will result in an array parameter being created). -- cgit v1.2.3 From 94fde8c63439bc86265fadbd293dbd28b808d830 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Mon, 26 Jan 2009 20:45:32 +0000 Subject: more corrections --- railties/doc/guides/source/form_helpers.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/doc/guides/source/form_helpers.txt b/railties/doc/guides/source/form_helpers.txt index f48b40c0ab..e2afcf41ba 100644 --- a/railties/doc/guides/source/form_helpers.txt +++ b/railties/doc/guides/source/form_helpers.txt @@ -640,7 +640,7 @@ Understanding Parameter Naming Conventions As you've seen in the previous sections, values from forms can be at the top level of the `params` hash or nested in another hash. For example in a standard `create` action for a Person model, `params[:model]` would usually be a hash of all the attributes for the person to create. The `params` hash can also contain arrays, arrays of hashes and so on. -Fundamentally HTML forms don't know about any sort of structured data, all they generate is name-value pairs. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses. +Fundamentally HTML forms don't know about any sort of structured data, all they generate is name-value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses. [TIP] ======================== @@ -727,7 +727,7 @@ This will result in a `params` hash that looks like {'person' => {'name' => 'Bob', 'address' => {'23' => {'city' => 'Paris'}, '45' => {'city' => 'London'}}}} -------- -Rails knows that all these inputs should be part of the person hash because you called `fields_for` on the first form builder. By specifying an `:index` option you're telling rails that instead of naming the inputs `person[address][city]` it should insert that index surrounded by [] between the address and the city. If you pass an Active Record object as we did then Rails will call `to_param` on it, which by default returns the database id. This is often useful as it is then easy to locate which Address record should be modified. You can pass numbers with some other significance, strings or even nil (which will result in an array parameter being created). +Rails knows that all these inputs should be part of the person hash because you called `fields_for` on the first form builder. By specifying an `:index` option you're telling rails that instead of naming the inputs `person[address][city]` it should insert that index surrounded by [] between the address and the city. If you pass an Active Record object as we did then Rails will call `to_param` on it, which by default returns the database id. This is often useful as it is then easy to locate which Address record should be modified. You can pass numbers with some other significance, strings or even `nil` (which will result in an array parameter being created). To create more intricate nestings, you can specify the first part of the input name (`person[address]` in the previous example) explicitly, for example -------- @@ -752,7 +752,7 @@ produces exactly the same output as the previous example. Building Complex forms ---------------------- -Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. While this guide has shown you all the pieces necessary to handle this, Rails does not yet have a standard end-to-end way of accomplishing this, but many have come up with viable approaches. These include: +Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. While this guide has shown you all the pieces necessary to handle this, Rails does not yet have a standard end-to-end way of accomplishing this, but many have come up with viable approaches. These include: * Ryan Bates' series of railscasts on http://railscasts.com/episodes/75[complex forms] * Handle Multiple Models in One Form from http://media.pragprog.com/titles/fr_arr/multiple_models_one_form.pdf[Advanced Rails Recipes] -- cgit v1.2.3 From 5fe6635e05a18b312c47fe6bbbaf88fd62e7703d Mon Sep 17 00:00:00 2001 From: Dov Murik Date: Mon, 26 Jan 2009 16:10:00 +0200 Subject: Mysql#reconnect is set according to the 'reconnect' key in the connection spec. The 'reconenct' boolean option is read from the connection specification and is used to set the reconnect attribute of Mysql. The default is false in order not to change existing application behaviour. Also, reconnect is set AFTER real_connect is called, so its value sticks (the mysql gem sets reconnect to false inside real_connect). Signed-off-by: Michael Koziarski [#1797 state:committed] --- .../connection_adapters/mysql_adapter.rb | 7 ++++-- activerecord/test/cases/connection_test_mysql.rb | 26 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index b2345fd571..9300df28ee 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -152,6 +152,7 @@ module ActiveRecord # * :password - Defaults to nothing. # * :database - The name of the database. No default, must be provided. # * :encoding - (Optional) Sets the client encoding by executing "SET NAMES " after connection. + # * :reconnect - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html). # * :sslca - Necessary to use MySQL with an SSL connection. # * :sslkey - Necessary to use MySQL with an SSL connection. # * :sslcert - Necessary to use MySQL with an SSL connection. @@ -563,8 +564,6 @@ module ActiveRecord private def connect - @connection.reconnect = true if @connection.respond_to?(:reconnect=) - encoding = @config[:encoding] if encoding @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil @@ -575,6 +574,10 @@ module ActiveRecord end @connection.real_connect(*@connection_options) + + # reconnect must be set after real_connect is called, because real_connect sets it to false internally + @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=) + configure_connection end diff --git a/activerecord/test/cases/connection_test_mysql.rb b/activerecord/test/cases/connection_test_mysql.rb index 40ddcf5420..f79ee2f1f7 100644 --- a/activerecord/test/cases/connection_test_mysql.rb +++ b/activerecord/test/cases/connection_test_mysql.rb @@ -2,9 +2,24 @@ require "cases/helper" class MysqlConnectionTest < ActiveRecord::TestCase def setup + super @connection = ActiveRecord::Base.connection end + def test_mysql_reconnect_attribute_after_connection_with_reconnect_true + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => true})) + assert ActiveRecord::Base.connection.raw_connection.reconnect + end + end + + def test_mysql_reconnect_attribute_after_connection_with_reconnect_false + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false})) + assert !ActiveRecord::Base.connection.raw_connection.reconnect + end + end + def test_no_automatic_reconnection_after_timeout assert @connection.active? @connection.update('set @@wait_timeout=1') @@ -27,4 +42,15 @@ class MysqlConnectionTest < ActiveRecord::TestCase @connection.verify! assert @connection.active? end + + private + + def run_without_connection + original_connection = ActiveRecord::Base.remove_connection + begin + yield original_connection + ensure + ActiveRecord::Base.establish_connection(original_connection) + end + end end -- cgit v1.2.3 From c06eecfbc280da3295b73ef882e6a7c3b632c156 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 27 Jan 2009 15:12:21 +0000 Subject: Regenerate html --- railties/doc/guides/html/form_helpers.html | 74 ++++++++--------- railties/doc/guides/html/i18n.html | 126 ++++++++++++++++++++++++----- 2 files changed, 140 insertions(+), 60 deletions(-) diff --git a/railties/doc/guides/html/form_helpers.html b/railties/doc/guides/html/form_helpers.html index 5329d604b1..2693f29a9a 100644 --- a/railties/doc/guides/html/form_helpers.html +++ b/railties/doc/guides/html/form_helpers.html @@ -80,7 +80,7 @@
  • Common options
  • -
  • Individual Components
  • +
  • Individual components
  • @@ -670,16 +670,16 @@ Dates and times are not representable by a single input element. Instead you hav
  • -Other helpers use the _tag suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, select\_date, select\_time and select_datetime are the barebones helpers, date_select, time_select and datetime_select are the equivalent model object helpers. +Other helpers use the _tag suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, select_date, select_time and select_datetime are the barebones helpers, date_select, time_select and datetime_select are the equivalent model object helpers.

  • -

    Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc...).

    +

    Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc.).

    4.1. Barebones helpers

    The select_* family of helpers take as their first argument an instance of Date, Time or DateTime that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example

    -
    <%= select_date Date::today, :prefix => :start_date %>
    +
    <%= select_date Date.today, :prefix => :start_date %>

    outputs (with actual option values omitted for brevity)

    @@ -691,7 +691,7 @@ Other helpers use the _tag suffix to indicate whether a helper is a barebones he

    The above inputs would result in params[:start_date] being a hash with keys :year, :month, :day. To get an actual Time or Date object you would have to extract these values and pass them to the appropriate constructor, for example

    -
    Date::civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i)
    +
    Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i)

    The :prefix option is the key used to retrieve the hash of date components from the params hash. Here it was set to start_date, if omitted it will default to date.

    4.2. Model object helpers

    @@ -713,7 +713,7 @@ The model object helpers for dates and times submit parameters with special name
    {:person => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}}
    -

    When this is passed to Person.new (or update_attributes), Active Record spots that these parameters should all be used to construct the birth_date attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as Date::civil.

    +

    When this is passed to Person.new (or update_attributes), Active Record spots that these parameters should all be used to construct the birth_date attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as Date.civil.

    4.3. Common options

    Both families of helpers use the same core set of functions to generate the individual select tags and so both accept largely the same options. In particular, by default Rails will generate year options 5 years either side of the current year. If this is not an appropriate range, the :start_year and :end_year options override this. For an exhaustive list of the available options, refer to the API documentation.

    As a rule of thumb you should be using date_select when working with model objects and select_date in others cases, such as a search form which filters results by date.

    @@ -725,19 +725,19 @@ The model object helpers for dates and times submit parameters with special name In many cases the built in date pickers are clumsy as they do not aid the user in working out the relationship between the date and the day of the week.
    -

    4.4. Individual Components

    -

    Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component select_year, select_month, select_day, select_hour, select_minute, select_second. These helpers are fairly straightforward. By default they will generate a input named after the time component (for example "year" for select_year, "month" for select_month etc.) although this can be override with the :field_name option. The :prefix option works in the same way that it does for select_date and select_time and has the same default value.

    +

    4.4. Individual components

    +

    Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component select_year, select_month, select_day, select_hour, select_minute, select_second. These helpers are fairly straightforward. By default they will generate a input named after the time component (for example "year" for select_year, "month" for select_month etc.) although this can be overriden with the :field_name option. The :prefix option works in the same way that it does for select_date and select_time and has the same default value.

    The first parameter specifies which value should be selected and can either be an instance of a Date, Time or DateTime, in which case the relevant component will be extracted, or a numerical value. For example

    <%= select_year(2009) %>
     <%= select_year(Time.now) %>
    -

    Will produce the same output if the current year is 2009 and the value chosen by the user can be retrieved by params[:date][:year].

    +

    will produce the same output if the current year is 2009 and the value chosen by the user can be retrieved by params[:date][:year].

    5. Uploading Files

    -

    A common task is uploading some sort of file, whether it’s a picture of a person or a CSV file containing data to process. The most important thing to remember with file uploads is that the form’s encoding MUST be set to multipart/form-data. If you forget to do this the file will not be uploaded. This can be done by passing :multi_part => true as an HTML option. This means that in the case of form_tag it must be passed in the second options hash and in the case of form_for inside the :html hash.

    +

    A common task is uploading some sort of file, whether it’s a picture of a person or a CSV file containing data to process. The most important thing to remember with file uploads is that the form’s encoding MUST be set to "multipart/form-data". If you forget to do this the file will not be uploaded. This can be done by passing :multi_part => true as an HTML option. This means that in the case of form_tag it must be passed in the second options hash and in the case of form_for inside the :html hash.

    The following two forms both upload a file.

    @@ -751,7 +751,7 @@ The model object helpers for dates and times submit parameters with special name

    Rails provides the usual pair of helpers: the barebones file_field_tag and the model oriented file_field. The only difference with other helpers is that you cannot set a default value for file inputs as this would have no meaning. As you would expect in the first case the uploaded file is in params[:picture] and in the second case in params[:person][:picture].

    5.1. What gets uploaded

    -

    The object in the params hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an original_filename attribute containing the name the file had on the user’s computer and a content_type attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in #{RAILS_ROOT}/public/uploads under the same name as the original file (assuming the form was the one in the previous example).

    +

    The object in the params hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an original_filename attribute containing the name the file had on the user’s computer and a content_type attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in #{Rails.root}/public/uploads under the same name as the original file (assuming the form was the one in the previous example).

    5.2. Dealing with Ajax

    -

    Unlike other forms making an asynchronous file upload form is not as simple as replacing form_for with remote_form_for. With an AJAX form the serialization is done by javascript running inside the browser and since javascript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission.

    +

    Unlike other forms making an asynchronous file upload form is not as simple as replacing form_for with remote_form_for. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission.

    6. Customising Form Builders

    -

    As mentioned previously the object yielded by form_for and fields_for is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying a form elements for a single object. While you can of course write helpers for your forms in the usual way you can also subclass FormBuilder and add the helpers there. For example

    +

    As mentioned previously the object yielded by form_for and fields_for is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way you can also subclass FormBuilder and add the helpers there. For example

    -
    <% form_for @person  do |f| %>
    +
    <% form_for @person do |f| %>
       <%= text_field_with_label f, :first_name %>
     <% end %>
    @@ -798,7 +798,7 @@ by Lorenzo Bettini http://www.lorenzobettini.it http://www.gnu.org/software/src-highlite -->
    class LabellingFormBuilder < FormBuilder
    -  def text_field attribute, options={}
    +  def text_field(attribute, options={})
         label(attribute) + text_field(attribute, options)
       end
     end
    @@ -808,13 +808,13 @@ http://www.gnu.org/software/src-highlite -->
    <%= render :partial => f %>
    -

    If f is an instance of FormBuilder then this will render the form partial, setting the partial’s object to the form builder. If the form builder is of class LabellingFormBuilder then the labelling_form partial would be rendered instead.

    +

    If f is an instance of FormBuilder then this will render the form partial, setting the partial’s object to the form builder. If the form builder is of class LabellingFormBuilder then the labelling_form partial would be rendered instead.

    7. Understanding Parameter Naming Conventions

    -

    As you’ve seen in the previous sections, values from forms can be at the top level of the params hash or nested in another hash. For example in a standard create +

    As you’ve seen in the previous sections, values from forms can be at the top level of the params hash or nested in another hash. For example in a standard create action for a Person model, params[:model] would usually be a hash of all the attributes for the person to create. The params hash can also contain arrays, arrays of hashes and so on.

    -

    Fundamentally HTML forms don’t know about any sort of structured data, all they generate is name-value pairs. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses.

    +

    Fundamentally HTML forms don’t know about any sort of structured data, all they generate is name-value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses.

    @@ -824,8 +824,8 @@ action for a Person model, params[:model] would usually be a hash of al

    You may find you can try out examples in this section faster by using the console to directly invoke Rails' parameter parser. For example

    -
    ActionController::RequestParser.parse_query_parameters "name=fred&phone=0123456789"
    -#=> {"name"=>"fred", "phone"=>"0123456789"}
    +
    ActionController::UrlEncodedPairParser.parse_query_parameters "name=fred&phone=0123456789"
    +# => {"name"=>"fred", "phone"=>"0123456789"}
    @@ -838,11 +838,9 @@ action for a Person model, params[:model] would usually be a hash of al

    the params hash will contain

    -
    -
    {'person' => {'name' => 'Henry'}}
    +
    +
    {'person' => {'name' => 'Henry'}}
    +

    and params["name"] will retrieve the submitted value in the controller.

    Hashes can be nested as many levels as required, for example

    @@ -851,11 +849,9 @@ http://www.gnu.org/software/src-highlite -->

    will result in the params hash being

    -
    -
    {'person' => {'address' => {'city' => 'New York'}}}
    +
    +
    {'person' => {'address' => {'city' => 'New York'}}}
    +

    Normally Rails ignores duplicate parameter names. If the parameter name contains [] then they will be accumulated in an array. If you wanted people to be able to input multiple phone numbers, your could place this in the form:

    @@ -872,8 +868,8 @@ http://www.gnu.org/software/src-highlite --> <input name="addresses[][line2]" type="text"/> <input name="addresses[][city]" type="text"/>
    -

    This would result in params[:addresses] being an array of hashes with keys line1, line2 and city. Rails decides to start accumulating values in a new hash whenever it encounters a input name that already exists in the current hash.

    -

    The one restriction is that although hashes can be nested arbitrarily deep then can be only one level of "arrayness". Arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id, an array index or some other parameter.

    +

    This would result in params[:addresses] being an array of hashes with keys line1, line2 and city. Rails decides to start accumulating values in a new hash whenever it encounters an input name that already exists in the current hash.

    +

    There’s a restriction, however, while hashes can be nested arbitrarily, only one level of "arrayness" is allowed. Arrays can be usually replaced by hashes, for example instead of having an array of model objects one can have a hash of model objects keyed by their id, an array index or some other parameter.

    @@ -888,7 +884,7 @@ http://www.gnu.org/software/src-highlite -->
    <% form_for @person do |person_form| %>
    -  <%= person_form.text_field :name%>
    +  <%= person_form.text_field :name %>
       <% for address in @person.addresses %>
         <% person_form.fields_for address, :index => address do |address_form|%>
           <%= address_form.text_field :city %>
    @@ -907,12 +903,10 @@ http://www.gnu.org/software/src-highlite -->
     

    This will result in a params hash that looks like

    -
    -
    {'person' => {'name' => 'Bob', 'address' => { '23' => {'city' => 'Paris'}, '45' => {'city' => 'London'} }}}
    -

    Rails knows that all these inputs should be part of the person hash because you called fields_for on the first form builder. By specifying an :index option you’re telling rails that instead of naming the inputs person[address][city] it should insert that index surrounded by [] between the address and the city. If you pass an Active Record object as we did then Rails will call to_param on it, which by default returns the database id. This is often useful as it is then easy to locate which Address record should be modified. You can pass numbers with some other significance, strings or even nil (which will result in an array parameter being created).

    +
    +
    {'person' => {'name' => 'Bob', 'address' => {'23' => {'city' => 'Paris'}, '45' => {'city' => 'London'}}}}
    +
    +

    Rails knows that all these inputs should be part of the person hash because you called fields_for on the first form builder. By specifying an :index option you’re telling rails that instead of naming the inputs person[address][city] it should insert that index surrounded by [] between the address and the city. If you pass an Active Record object as we did then Rails will call to_param on it, which by default returns the database id. This is often useful as it is then easy to locate which Address record should be modified. You can pass numbers with some other significance, strings or even nil (which will result in an array parameter being created).

    To create more intricate nestings, you can specify the first part of the input name (person[address] in the previous example) explicitly, for example

    @@ -937,7 +931,7 @@ http://www.gnu.org/software/src-highlite -->

    8. Building Complex forms

    -

    Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. While this guide has shown you all the pieces necessary to handle this, Rails does not yet have a standard end-to-end way of accomplishing this, but many have come up with viable approaches. These include:

    +

    Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. While this guide has shown you all the pieces necessary to handle this, Rails does not yet have a standard end-to-end way of accomplishing this, but many have come up with viable approaches. These include:

    • diff --git a/railties/doc/guides/html/i18n.html b/railties/doc/guides/html/i18n.html index 60253145de..b0d52c0ff5 100644 --- a/railties/doc/guides/html/i18n.html +++ b/railties/doc/guides/html/i18n.html @@ -59,13 +59,15 @@

  • - Internationalize your application + Internationalizing your application
  • @@ -128,7 +130,26 @@

    The Ruby I18n (shorthand for internationalization) gem which is shipped with Ruby on Rails (starting from Rails 2.2) provides an easy-to-use and extensible framework for translating your application to a single custom language other than English or for providing multi-language support in your application.

    -

    In the process of localizing your application you’ll probably want to do following two things:

    +

    The process of "internationalization" usually means to abstract all strings and other locale specific bits (such as date or currency formats) out of your application. The process of "localization" means to provide translations and localized formats for these bits. [1]

    +

    So, in the process of internationalizing your Rails application you have to:

    +
      +
    • +

      +Ensure you have support for i18n +

      +
    • +
    • +

      +Tell Rails where to find locale dictionaries +

      +
    • +
    • +

      +Tell Rails how to set, preserve and switch locale +

      +
    • +
    +

    In the process of localizing your application you’ll probably want to do following three things:

    • @@ -140,6 +161,11 @@ Replace or supplement Rail’s default locale — eg. date a Abstract texts in your application into keyed dictionaries — eg. flash messages, static texts in your views, etc

    • +
    • +

      +Store the resulting dictionaries somewhere +

      +

    This guide will walk you through the I18n API and contains a tutorial how to internationalize a Rails application from the start.

    @@ -237,6 +263,14 @@ http://www.gnu.org/software/src-highlite --> hello: "Hello world"

    This means, that in the :en locale, the key hello will map to Hello world string. Every string inside Rails is internationalized in this way, see for instance Active Record validation messages in the activerecord/lib/active_record/locale/en.yml file or time and date formats in the activesupport/lib/active_support/locale/en.yml file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend.

    The I18n library will use English as a default locale, ie. if you don’t set a different locale, :en will be used for looking up translations.

    +
    + + + +
    +Note +The i18n library takes pragmatic approach to locale keys (after some discussion), including only the locale ("language") part, like :en, :pl, not the region part, like :en-US or :en-UK, which are traditionally used for separating "languages" and "regional setting" or "dialects". (For instance, in the :en-US locale you would have $ as a currency symbol, while in :en-UK, you would have €. Also, insults would be different in American and British English :) Reason for this pragmatic approach is that most of the time, you usually care about making your application available in different "languages", and working with locales is much simpler this way. However, nothing stops you from separating regional and other settings in the traditional way. In this case, you could eg. inherit from the default en locale and then provide UK specific settings in a :en-UK dictionary.
    +

    The translations load path (I18n.load_path) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you.

    @@ -304,7 +338,7 @@ http://www.gnu.org/software/src-highlite -->
    Following examples rely on having locales loaded into your application available as an array of strings like ["en", "es", "gr"]. This is not inclued in current version of Rails 2.2 — forthcoming Rails version 2.3 will contain easy accesor available_locales. (See this commit and background at Rails I18n Wiki.)
    -

    We have to include support for getting available locales manually in an initializer like this:

    +

    So, for having available locales easily available in Rails 2.2, we have to include this support manually in an initializer, like this:

    end

    Every helper method dependent on url_for (eg. helpers for named routes like root_path or root_url, resource routes like books_path or books_url, etc.) will now automatically include the locale in the query string, like this: http://localhost:3001/?locale=ja.

    You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of application domain: and URLs should reflect this.

    -

    You probably want URLs look like this: www.example.com/en/books versus www.example.com/nl/books. This is achievable with the over-riding default_url_options strategy: you just have to set up your routes with path_prefix option in this way:

    +

    You probably want URLs look like this: www.example.com/en/books (which loads English locale) and www.example.com/nl/books (which loads Netherlands locale). This is achievable with the "over-riding default_url_options" strategy from above: you just have to set up your routes with path_prefix option in this way:

    # config/routes.rb
     map.resources :books, :path_prefix => '/:locale'
    -

    Now, when you call books_path method you should get "/en/books" (for the default locale). An URL like http://localhost:3001/nl/books should load the Netherlands locale, then, and so on.

    +

    Now, when you call books_path method you should get "/en/books" (for the default locale). An URL like http://localhost:3001/nl/books should load the Netherlands locale, then, and following calls to books_path should return "/nl/books" (because the locale changed).

    Of course, you need to take special care of root URL (usually "homepage" or "dashboard") of your application. An URL like http://localhost:3001/nl will not work automatically, because the map.root :controller => "dashboard" declaration in your routes.rb doesn’t take locale into account. (And rightly so. There’s only one "root" URL.)

    You would probably need to map URLs like these:

    @@ -452,13 +486,35 @@ map.dashboard '
  • 2.6. Setting locale from the client supplied information

    -

    # TODO: Accept-Language, GeoIP, etc. Explain why it is not such a good idea in most cases.

    -

    OK! Now you’ve initialized I18n support for your application and told it which locale should be used and how to preserve it between requests. With that in place you’re now ready for the really interesting stuff.

    +

    In specific cases, it would make sense to set locale from client supplied information, ie. not from URL. This information may come for example from users' preffered language (set in their browser), can be based on users' geographical location inferred from their IP, or users can provide it simply by choosing locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites — see the box about sessions, cookies and RESTful architecture above.

    +

    2.6.1. Using Accept-Language

    +

    One source of client supplied information would be an Accept-Language HTTP header. People may set this in their browser or other clients (such as curl).

    +

    A trivial implementation of using Accept-Language header would be:

    +
    +
    +
    def set_locale
    +  logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
    +  I18n.locale = extract_locale_from_accept_language_header
    +  logger.debug "* Locale set to '#{I18n.locale}'"
    +end
    +private
    +def extract_locale_from_accept_language_header
    +  request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
    +end
    +

    Of course, in production environment you would need much robust code, and could use a plugin such as Iaian Hecker’s http_accept_language.

    +

    2.6.2. Using GeoIP (or similar) database

    +

    Another way of choosing the locale from client’s information would be to use a database for mapping client IP to region, such as GeoIP Lite Country. The mechanics of the code would be very similar to the code above — you would need to query database for user’s IP, and lookup your preffered locale for the country/region/city returned.

    +

    2.6.3. User profile

    +

    You can also provide users of your application with means to set (and possibly over-ride) locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above — you’d probably let users choose a locale from a dropdown list and save it to their profile in database. Then you’d set the locale to this value.

    -

    3. Internationalize your application

    +

    3. Internationalizing your application

    -

    The process of "internationalization" usually means to abstract all strings and other locale specific bits out of your application. The process of "localization" means to then provide translations and localized formats for these bits. [1]

    -

    So, let’s internationalize something. You most probably have something like this in one of your applications:

    +

    OK! Now you’ve initialized I18n support for your Ruby on Rails application and told it which locale should be used and how to preserve it between requests. With that in place, you’re now ready for the really interesting stuff.

    +

    Let’s internationalize our application, ie. abstract every locale-specific parts, and that localize it, ie. provide neccessary translations for these abstracts.

    +

    You most probably have something like this in one of your applications:

    # app/views/home/index.html.erb <h1><%=t :hello_world %></h1> <p><%= flash[:notice] %></p>
    -

    When you now render this view it will show an error message that tells you that the translations for the keys :hello_world and :hello_flash are missing.

    +

    When you now render this view, it will show an error message which tells you that the translations for the keys :hello_world and :hello_flash are missing.

    rails i18n demo translation missing

    @@ -508,10 +564,10 @@ http://www.gnu.org/software/src-highlite --> Note -Rails adds a t (translate) helper method to your views so that you do not need to spell out I18n.t all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a <span class="translation_missing">. +Rails adds a t (translate) helper method to your views so that you do not need to spell out I18n.t all the time. Additionally this helper will catch missing translations and wrap the resulting error message into a <span class="translation_missing">.
    -

    So let’s add the missing translations (i.e. do the "localization" part):

    +

    So let’s add the missing translations into the dictionary files (i.e. do the "localization" part):

    +
    class Product < ActiveRecord::Base; end
    +

    This will create a Product model, mapped to a products table at the database. By doing this you’ll also have the hability to map the columns of each row in that table with the attributes of the instances of your model. So, suppose that the products table was created using a SQL sentence like:

    +
    +
    +
    CREATE TABLE products (
    +   id int(11) NOT NULL auto_increment,
    +   name varchar(255),
    +   PRIMARY KEY  (id)
    +);
    +

    Following the table schema above, you would be able to write code like the following:

    +
    +
    +
    p = Product.new
    +p.name = "Some Book"
    +puts p.name # "Some Book"
    + +

    6. Convention over Configuration in ActiveRecord

    +
    +

    When writing applications using other programming languages or frameworks, it may be necessary to write a lot of configuration code. This is particulary true for ORM frameworks in general. However, if you follow the conventions adopted by Rails, you’ll need to write very little configuration (in some case no configuration at all) when creating ActiveRecord models. The idea is that if you configure your applications in the very same way most of the times then this should be the default way. In this cases, explicity configuration would be needed only in those cases where you can’t follow the conventions for any reason.

    +

    6.1. Naming Conventions

    +

    By default, ActiveRecord uses some naming conventions to find out how the mapping between models and database tables should be created. It uses two basic strategies to convert between class names and table names:

    +

    6.1.1. Pluralization

    +

    Rails will pluralize your class names to find the respective database table. So, for a class Book, you should have a database table called books. The Rails pluralization mechanisms are very powerful, being capable to pluralize (and singularize) both regular and irregular words.

    +
    +

    7. STOPED HERE

    -

    Active Record is a design pattern used to access data within a database. The name “Active Record” was coined by Martin Fowler in his book “Patterns of Enterprise Application Architecture”. Essentially, when a record is returned from the database instead of being just the data it is wrapped in a class, which gives you methods to control that data with. The rails framework is built around the MVC (Model View Controller) design patten and the Active Record is used as the default Model.

    -

    The Rails community added several useful concepts to their version of Active Record, including inheritance and associations, which are extremely useful for web applications. The associations are created by using a DSL (domain specific language) of macros, and inheritance is achieved through the use of STI (Single Table Inheritance) at the database level.

    -

    By following a few simple conventions the Rails Active Record will automatically map between:

    -
      -
    • -

      -Classes & Database Tables -

      -
    • -
    • -

      -Class attributes & Database Table Columns -

      -
    • -
    -

    5.1. Rails Active Record Conventions

    -

    Here are the key conventions to consider when using Active Record.

    -

    5.1.1. Naming Conventions

    Database Table - Plural with underscores separating words i.e. (book_clubs) Model Class - Singular with the first letter of each word capitalized i.e. (BookClub) Here are some additional Examples:

    @@ -249,7 +288,7 @@ cellspacing="0" cellpadding="4">
    -

    5.1.2. Schema Conventions

    +

    7.1. Schema Conventions

    To take advantage of some of the magic of Rails database tables must be modeled to reflect the ORM decisions that Rails makes.

    @@ -287,7 +326,7 @@ cellspacing="0" cellpadding="4">
    -

    5.1.3. Magic Field Names

    +

    7.1.1. Magic Field Names

    When these optional fields are used in your database table definition they give the Active Record instance additional features.

    @@ -424,11 +463,11 @@ http://www.gnu.org/software/src-highlite -->

    Rails further extends this model by giving each ActiveRecord a way of describing the variety of ways records are associated with one another. We will touch on some of these associations later in the guide but I encourage readers who are interested to read the guide to ActiveRecord associations for an in-depth explanation of the variety of ways rails can model associations. - Associations between objects controlled by meta-programming macros.

    -

    6. Philosophical Approaches & Common Conventions

    +

    8. Philosophical Approaches & Common Conventions

    Rails has a reputation of being a zero-config framework which means that it aims to get you off the ground with as little pre-flight checking as possible. This speed benefit is achieved by following “Convention over Configuration”, which is to say that if you agree to live with the defaults then you benefit from a the inherent speed-boost. As Courtneay Gasking put it to me once “You don’t want to off-road on Rails”. ActiveRecord is no different, while it’s possible to override or subvert any of the conventions of AR, unless you have a good reason for doing so you will probably be happy with the defaults. The following is a list of the common conventions of ActiveRecord

    -

    7. ActiveRecord Magic

    +

    9. ActiveRecord Magic

    • @@ -443,7 +482,7 @@ updates
    -

    8. How ActiveRecord Maps your Database.

    +

    10. How ActiveRecord Maps your Database.

    • @@ -458,10 +497,10 @@ overriding conventions
    -

    9. Growing Your Database Relationships Naturally

    +

    11. Growing Your Database Relationships Naturally

    -

    10. Attributes

    +

    12. Attributes

    • @@ -483,7 +522,7 @@ dirty records
    -

    11. Validations & Callbacks

    +

    13. Validations & Callbacks

    see the Validations & Callbacks guide for more info.

    diff --git a/railties/doc/guides/source/active_record_basics.txt b/railties/doc/guides/source/active_record_basics.txt index d348b4dc0c..6b4c950435 100644 --- a/railties/doc/guides/source/active_record_basics.txt +++ b/railties/doc/guides/source/active_record_basics.txt @@ -20,6 +20,10 @@ Rails' ActiveRecord is an implementation of Martin Fowler's http://martinfowler. * Each table column is mapped to an attribute of this class. * Each instance of this class is mapped to a single row in the database table. +The definition of the Active Record pattern in Martin Fowler's words: + +"_An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data."_ + == Object Relational Mapping The relation between databases and object-oriented software is called ORM, which is short for "Object Relational Mapping". The purpose of an ORM framework is to minimize the mismatch existent between relational databases and object-oriented software. In applications with a domain model, we have objects that represent both the state of the system and the behaviour of the real world elements that were modeled through these objects. Since we need to store the system's state somehow, we can use relational databases, which are proven to be an excelent approach to data management. Usually this may become a very hard thing to do, since we need to create an object-oriented model of everything that lives in the database, from simple columns to complicated relations between different tables. Doing this kind of thing by hand is a tedious and error prone job. This is where an ORM framework comes in. @@ -30,6 +34,7 @@ ActiveRecord gives us several mechanisms, being the most important ones the habi * Represent models. * Represent associations between these models. +* Represent inheritance hierarquies through related models. * Validate models before they get recorded to the database. * Perform database operations in an object-oriented fashion. @@ -37,26 +42,51 @@ It's easy to see that the Rails Active Record implementation goes way beyond the == Active Record inside the MVC model +Active Record plays the role of model inside the MVC structure followed by Rails applications. Since model objects should encapsulate both state and logic of your applications, it's ActiveRecord responsability to deliver you the easiest possible way to recover this data from the database. + +== Creating ActiveRecord models +It's very easy to create ActiveRecord models. All you have to do is to subclass the ActiveRecord::Base class and you're good to go: + +[source, ruby] +------------------------------------------------------------------ +class Product < ActiveRecord::Base; end +------------------------------------------------------------------ +This will create a +Product+ model, mapped to a *products* table at the database. By doing this you'll also have the hability to map the columns of each row in that table with the attributes of the instances of your model. So, suppose that the *products* table was created using a SQL sentence like: +[source, sql] +------------------------------------------------------------------ +CREATE TABLE products ( + id int(11) NOT NULL auto_increment, + name varchar(255), + PRIMARY KEY (id) +); +------------------------------------------------------------------ + +Following the table schema above, you would be able to write code like the following: + +[source, ruby] +------------------------------------------------------------------ +p = Product.new +p.name = "Some Book" +puts p.name # "Some Book" +------------------------------------------------------------------ +== Convention over Configuration in ActiveRecord -== Active Record The Engine of Rails +When writing applications using other programming languages or frameworks, it may be necessary to write a lot of configuration code. This is particulary true for ORM frameworks in general. However, if you follow the conventions adopted by Rails, you'll need to write very little configuration (in some case no configuration at all) when creating ActiveRecord models. The idea is that if you configure your applications in the very same way most of the times then this should be the default way. In this cases, explicity configuration would be needed only in those cases where you can't follow the conventions for any reason. -Active Record is a design pattern used to access data within a database. The name “Active Record” was coined by Martin Fowler in his book “Patterns of Enterprise Application Architecture”. Essentially, when a record is returned from the database instead of being just the data it is wrapped in a class, which gives you methods to control that data with. The rails framework is built around the MVC (Model View Controller) design patten and the Active Record is used as the default Model. +=== Naming Conventions -The Rails community added several useful concepts to their version of Active Record, including inheritance and associations, which are extremely useful for web applications. The associations are created by using a DSL (domain specific language) of macros, and inheritance is achieved through the use of STI (Single Table Inheritance) at the database level. +By default, ActiveRecord uses some naming conventions to find out how the mapping between models and database tables should be created. It uses two basic strategies to convert between class names and table names: -By following a few simple conventions the Rails Active Record will automatically map between: +==== Pluralization -* Classes & Database Tables -* Class attributes & Database Table Columns +Rails will pluralize your class names to find the respective database table. So, for a class +Book+, you should have a database table called *books*. The Rails pluralization mechanisms are very powerful, being capable to pluralize (and singularize) both regular and irregular words. -=== Rails Active Record Conventions -Here are the key conventions to consider when using Active Record. +== STOPED HERE -==== Naming Conventions Database Table - Plural with underscores separating words i.e. (book_clubs) Model Class - Singular with the first letter of each word capitalized i.e. (BookClub) Here are some additional Examples: @@ -72,7 +102,7 @@ Mouse mice Person people ---------------------------- -==== Schema Conventions +=== Schema Conventions To take advantage of some of the magic of Rails database tables must be modeled to reflect the ORM decisions that Rails makes. -- cgit v1.2.3 From 4290b3cba0fee69f7f87bb095d4104cdfd49a08e Mon Sep 17 00:00:00 2001 From: Mike Gunderloy Date: Tue, 27 Jan 2009 21:04:16 -0600 Subject: First draft of Rails 2.3 release notes. --- railties/doc/guides/source/2_3_release_notes.txt | 399 +++++++++++++++++++++++ 1 file changed, 399 insertions(+) create mode 100644 railties/doc/guides/source/2_3_release_notes.txt diff --git a/railties/doc/guides/source/2_3_release_notes.txt b/railties/doc/guides/source/2_3_release_notes.txt new file mode 100644 index 0000000000..5a63e30567 --- /dev/null +++ b/railties/doc/guides/source/2_3_release_notes.txt @@ -0,0 +1,399 @@ +Ruby on Rails 2.3 Release Notes +=============================== + +Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the link:http://github.com/rails/rails/commits/master[list of commits] in the main Rails repository on GitHub. + +== Application Architecture + +There are two major changes in the architecture of Rails applications: complete integration of the link:http://rack.rubyforge.org/[Rack] modular web server interface, and renewed support for Rails Engines. + +=== Rack Integration + +Rails has now broken with its CGI past, and uses Rack everywhere. This required and resulted in a tremendous number of internal changes (but if you use CGI, don't worry; Rails now supports CGI through a proxy interface.) Still, this is a major change to Rails internals. After upgrading to 2.3, you should test on your local environment and your production environment. Some things to test: + +* Sessions +* Cookies +* File uploads +* JSON/XML APIs + +Here's a summary of the rack-related changes: + +* +script/server+ has been switched to use Rack, which means it supports any Rack compatible server. +script/server+ will also pick up a rackup configuration file if one exists. By default, it will look for a +config.ru+ file, but you can override this with the +-c+ switch. +* The FCGI handler goes through Rack +* ActionController::Dispatcher maintains its own default middleware stack. Middlewares can be injected in, reordered, and removed. The stack is compiled into a chain on boot. You can configure the middleware stack in +environment.rb+ +* The +rake middleware+ task has been added to inspect the middleware stack. This is useful for debugging the order of the middleware stack. +* The integration test runner has been modified to execute the entire middleware and application stack. This makes integration tests perfect for testing Rack middleware. +* +ActionController::CGIHandler+ is a backwards compatible CGI wrapper around Rack. The +CGIHandler+ is meant to take an old CGI object and converts its environment information into a Rack compatible form. +* +CgiRequest+ and +CgiResponse+ have been removed +* Session stores are now lazy loaded. If you never access the session object during a request, it will never attempt to load the session data (parse the cookie, load the data from memcache, or lookup an Active Record object). +* +CGI::Session::CookieStore+ has been replaced by +ActionController::Session::CookieStore+ +* +CGI::Session::MemCacheStore+ has been replaced by +ActionController::Session::MemCacheStore+ +* +CGI::Session::ActiveRecordStore+ has been replaced by +ActiveRecord::SessionStore+ +* You can still change you session store with +ActionController::Base.session_store = :active_record_store+ +* Default sessions options are still set with +ActionController::Base.session = { :key => "..." }+ +* The mutex that normally wraps your entire request has been moved into middleware, +ActionController::Lock+ +* +ActionController::AbstractRequest+ and +ActionController::Request+ have been unified. The new +ActionController::Request+ inherits from +Rack::Request+. This affects access to +response.headers['type']+ in test requests. Use +response.content_type+ instead. +* +ActiveRecord::QueryCache+ middleware is automatically inserted onto the middleware stack if +ActiveRecord+ has been loaded. This middleware sets up and flushes the per-request Active Record query cache. +* The Rails router and controller classes follow the Rack spec. You can call a controller directly with +SomeController.call(env)+. The router stores the routing parameters in +rack.routing_args+. +* +ActionController::Request+ inherits from +Rack::Request+ +* Instead of +config.action_controller.session = { :session_key => 'foo', ...+ use +config.action_controller.session = { :key => 'foo', ...+ +* Using the +ParamsParser+ middleware preprocesses any XML, JSON, or YAML requests so they can be read normally with any +Rack::Request+ object after it. + +=== Renewed Support for Rails Engines + +After some versions without an upgrade, Rails 2.3 offers some new features for Rails Engines (Rails applications that can be embedded within other applications). First, routing files in engines are automatically loaded and reloaded now, just like your +routes.rb+ file (this also applies to routing files in other plugins). Second, if your plugin has an app folder, then app/[models|controllers|helpers] will automatically be added to the Rails load path. Engines also support adding view paths now. + +* Lead Contributor: link:http://www.loudthinking.com/[David Heinemeier Hansson] + +== Documentation + +The link:http://guides.rubyonrails.org/[Ruby on Rails guides] project has published several additional guides for Rails 2.3. In addition, a link:http://guides.rails.info/[separate site] maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the link:http://newwiki.rubyonrails.org/[Rails wiki] and early planning for a Rails Book. + +* More Information: link:http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects[Rails Documentation Projects] + +== Active Record + +Active Record gets quite a number of new features and bug fixes in Rails 2.3. The highlights include nested transactions, dynamic scopes, and default scopes. + +=== Nested Transactions + +Active Record now supports nested transactions, a much-requested feature. Now you can write code like this: + +[source, ruby] +------------------------------------------------------- +User.transaction do + User.create(:username => 'Admin') + User.transaction(:requires_new => true) do + User.create(:username => 'Regular') + raise ActiveRecord::Rollback + end + end + + User.find(:all) # => Returns only Admin +------------------------------------------------------- + +Nested transactions let you roll back an inner transaction without affecting the state of the outer transaction. If you want a transaction to be nested, you must explicitly add the +:requires_new+ option; otherwise, a nested transaction simply becomes part of the parent transaction (as it does currently on Rails 2.2). Under the covers, nested transactions are link:http://rails.lighthouseapp.com/projects/8994/tickets/383[using savepoints], so they're supported even on databases that don't have true nested transactions. There is also a bit of magic going on to make these transactions play well with transactional fixtures during testing. + +* Lead Contributors: link:http://www.workingwithrails.com/person/4985-jonathan-viney[Jonathan Viney] and link:http://izumi.plan99.net/blog/[Hongli Lai] + +=== Dynamic Scopes + +You know about dynamic finders in Rails (which allow you to concoct methods like +find_by_color_and_flavor+ on the fly) and named scopes (which allow you to encapsulate reusable query conditions into friendly names like +currently_active+). Well, now you can have dynamic scope methods. The idea is to put together syntax that allows filtering on the fly and method chaining. For example: + +[source, ruby] +------------------------------------------------------- +Order.scoped_by_customer_id(12) +Order.scoped_by_customer_id(12).find(:all, + :conditions => "status = 'open'") +Order.scoped_by_customer_id(12).scoped_by_status("open") +------------------------------------------------------- + +There's nothing to define to use dynamic scopes: they just work. + +* Lead Contributor: link:http://evilmartians.com/[Yaroslav Markin] +* More Information: link:http://ryandaigle.com/articles/2008/12/29/what-s-new-in-edge-rails-dynamic-scope-methods[What's New in Edge Rails: Dynamic Scope Methods]. + +=== Default Scopes + +Rails 2.3 will introduce the notion of _default scopes_ similar to named scopes, but applying to all named scopes or find methods within the model. For example, you can write +default_scope :order => 'name ASC'+ and any time you retrieve records from that model they'll come out sorted by name (unless you override the option, of course). + +* Lead Contributor: Paweł Kondzior +* More Information: link:http://ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping[What's New in Edge Rails: Default Scoping] + +=== Multiple Conditions for Callbacks + +When using Active Record callbacks, you can now combine +:if+ and +:unless+ options on the same callback, and supply multiple conditions as an array: + +[source, ruby] +------------------------------------------------------- +before_save :update_credit_rating, :if => :active, + :unless => [:admin, :cash_only] +------------------------------------------------------- +* Lead Contributor: L. Caviola + +=== Find with having + +Rails now has a +:having+ option on find (as well as on +has_many+ and +has_and_belongs_to_many+ associations) for filtering records in grouped finds. As those with heavy SQL backgrounds know, this allows filtering based on grouped results: + +[source, ruby] +------------------------------------------------------- +developers = Developer.find(:all, :group => "salary", + :having => "sum(salary) > 10000", :select => "salary") +------------------------------------------------------- + +* Lead Contributor: link:http://github.com/miloops[Emilio Tagua] + +=== Hash Conditions for +has_many+ relationships + +You can once again use a hash in conditions for a +has_many+ relationship: + +[source, ruby] +------------------------------------------------------- +has_many :orders, :conditions => {:status => 'confirmed'} +------------------------------------------------------- + +That worked in Rails 2.1, fails in Rails 2.2, and will now work again in Rails 2.3 (if you're dealing with this issue in Rails 2.2, you can use a string rather than a hash to specify conditions). + +* Lead Contributor: link:http://www.spacevatican.org/[Frederick Cheung] + +=== Other Active Record Changes + +* An extra +AS+ was removed from the code for has_and_belongs_to_many preloading, making it work better for some databases. +* +ActiveRecord::Base#new_record?+ now returns false rather than nil when confronted with an existing record. +* A bug in quoting table names in some +has_many :through+ associations was fixed. +* You can now specify a particular timestamp for +updated_at+ timestamps: +cust = Customer.create(:name => "ABC Industries", :updated_at => 1.day.ago)+ +* Better error messages on failed +find_by_attribute!+ calls. +* Active Record's +to_xml+ support gets just a little bit more flexible with the addition of a +:camelize+ option. +* A bug in canceling callbacks from +before_update+ or +before_create_ was fixed. + +== Action Controller + +Action Controller rolls out some significant changes to rendering, as well as improvements in routing and other areas, in this release. + +=== Unified Rendering + ++ActionController::Base#render+ is a lot smarter about deciding what to render. Now you can just tell it what to render and expect to get the right results. In older versions of Rails, you often need to supply explicit information to render: + +[source, ruby] +------------------------------------------------------- +render :file => '/tmp/random_file.erb' +render :template => 'other_controller/action' +render :action => 'show' +------------------------------------------------------- + +Now in Rails 2.3, you can just supply what you want to render: + +[source, ruby] +------------------------------------------------------- +render '/tmp/random_file.erb' +render 'other_controller/action' +render 'show' +render :show +------------------------------------------------------- +Rails chooses between file, template, and action depending on whether there is a leading slash, an embedded slash, or no slash at all in what's to be rendered. Note that you can also use a symbol instead of a string when rendering an action. Other rendering styles (+:inline, :text, :update, :nothing, :json, :xml, :js+) still require an explicit option. + +* Lead Contributor: link:http://m.onkey.org/[Pratik Naik] + +=== Application Controller Renamed + +If you're one of the people who has always been bothered by the special-case naming of +application.rb+, rejoice! It's been reworked to be application_controller.rb in Rails 2.3. In addition, there's a new rake task, +rake rails:update:application_controller+ to do this automatically for you - and it will be run as part of the normal +rake rails:update+ process. + +* Lead Contributor: link:http://www.loudthinking.com/[David Heinemeier Hansson] +* More Information: + - link:http://afreshcup.com/2008/11/17/rails-2x-the-death-of-applicationrb/[The Death of Application.rb] + - link:http://ryandaigle.com/articles/2008/11/19/what-s-new-in-edge-rails-application-rb-duality-is-no-more[What's New in Edge Rails: Application.rb Duality is no More] + +=== More Efficient Routing + +There are a couple of significant routing changes in Rails 2.3. The +formatted_+ route helpers are gone, in favor just passing in +:format+ as an option. This cuts down the route generation process by 50% for any resource - and can save a substantial amount of memory (up to 100MB on large applications). If your code uses the +formatted_+ helpers, it will still work for the time being - but that behavior is deprecated and your application will be more efficient if you rewrite those routes using the new standard. Another big change is that Rails now supports multiple routing files, not just routes.rb. You can use +RouteSet#add_configuration_file+ to bring in more routes at any time - without clearing the currently-loaded routes. While this change is most useful for Engines, you can use it in any application that needs to load routes in batches. + +Lead Contributors: link:http://blog.hungrymachine.com/[Aaron Batalion] and link:http://www.loudthinking.com/[David Heinemeier Hansson] + +=== Rack-based Lazy-loaded Sessions + +A big change pushed the underpinnings of Action Controller session storage down to the Rack level. This involved a good deal of work in the code, though it should be completely transparent to your Rails applications (as a bonus, some icky patches around the old CGI session handler got removed). It's still significant, though, for one simple reason: non-Rails Rack applications have access to the same session storage handlers (and therefore the same session) as your Rails applications. In addition, sessions are now lazy-loaded (in line with the loading improvements to the rest of the framework). This means that you no longer need to explicitly disable sessions if you don't want them; just don't refer to them and they won't load. + +* Lead Contributor: link:http://joshpeek.com/[Josh Peek] + +=== MIME Type Handling Changes + +There are a couple of changes to the code for handling MIME types in Rails. First, +MIME::Type+ now implements the +=~+ operator, making things much cleaner when you need to check for the presence of a type that has synonyms: + +[source, ruby] +------------------------------------------------------- +if content_type && Mime::JS =~ content_type + # do something cool +end + +Mime::JS =~ "text/javascript" => true +Mime::JS =~ "application/javascript" => true +------------------------------------------------------- + +The other change is that the framework now uses the +Mime::JS+ when checking for javascript in various spots, making it handle those alternatives cleanly. + +Lead Contributor: link:http://www.workingwithrails.com/person/5510-seth-fitzsimmons[Seth Fitzsimmons] + +=== Optimization of +respond_to+ + +In some of the first fruits of the Rails-Merb team merger, Rails 2.3 includes some optimizations for the +respond_to+ method, which is of course heavily used in many Rails applications to allow your controller to format results differently based on the MIME type of the incoming request. After eliminating a call to +method_missing+ and some profiling and tweaking, we're seeing an 8% improvement in the number of requests per second served with a simple +respond_to+ that switches between three formats. The best part? No change at all required to the code of your application to take advantage of this speedup. + +* Lead Contributor: link:http://yehudakatz.com/[Yehuda Katz] + +=== Improved Caching Performance + +Rails now keeps a per-request local cache of requests, cutting down on unnecessary reads and leading to better site performance. While this work was originally limited to +MemCacheStore+, it is available to any remote store than implements the required methods. + +* Lead Contributor: link:http://www.motionstandingstill.com/[Nahum Wild] + +=== Other Action Controller Changes + +* ETag handling has been cleaned up a bit: Rails will now skip sending an ETag header when there's no body to the response or when sending files with +send_file+. +* The fact that Rails checks for IP spoofing can be a nuisance for sites that do heavy traffic with cell phones, because their proxies don't generally set things up right. If that's you, you can now set +ActionController::Base.ip_spoofing_check = false+ to disable the check entirely. +* ActionController::Dispatcher now implements its own middleware stack, which you can see by running +rake middleware+. +* Cookie sessions now have persistent session identifiers, with API compatibility with the server-side stores. +* You can now use symbols for the +:type+ option of +send_file+ and +send_data+, like this: +send_file("fabulous.png", :type => :png)+. + +== Action View + +Action View in Rails 2.3 picks up improvements to +render+, more flexible prompts for the date select helpers, and a speedup in asset caching, among other things. + +=== Smart Rendering + +The render method has been getting smarter over the years, and it's even smarter now. If you have an object or a collection and an appropriate partial, and the naming matches up, you can now just render the object and things will work. For example, in Rails 2.3, these render calls will work in your view (assuming sensible naming): + +[source, ruby] +------------------------------------------------------- +<% render @article %> +<% render @articles %> +------------------------------------------------------- + +* Lead Contributor: link:http://www.loudthinking.com/[David Heinemeier Hansson] +* More Information: link:http://ryandaigle.com/articles/2008/11/20/what-s-new-in-edge-rails-render-stops-being-high-maintenance[What's New in Edge Rails: render Stops Being High-Maintenance] + +=== Prompts for Date Select Helpers + +In Rails 2.3, you can supply custom prompts for the various date select helpers (+date_select+, +time_select+, and +datetime_select+), the same way you can with collection select helpers. You can supply a prompt string or a hash of individual prompt strings for the various components. You can also just set +:prompt+ to +true+ to use the custom generic prompt: + +[source, ruby] +------------------------------------------------------- +select_datetime(DateTime.now, :prompt => true) + +select_datetime(DateTime.now, :prompt => "Choose date and time") + +select_datetime(DateTime.now, :prompt => + {:day => 'Choose day', :month => 'Choose month', + :year => 'Choose year', :hour => 'Choose hour', + :minute => 'Choose minute'}) +------------------------------------------------------- + +Lead Contributor: link:http://samoliver.com/[Sam Oliver] + +=== AssetTag Timestamp Caching + +You're likely familiar with Rails' practice of adding timestamps to static asset paths as a "cache buster." This helps ensure that stale copies of things like images and stylesheets don't get served out of the user's browser cache when you change them on the server. You can now modify this behavior with the +cache_asset_timestamps+ configuration option for Action View. If you enable the cache, then Rails will calculate the timestamp once when it first serves an asset, and save that value. This means fewer (expensive) file system calls to serve static assets - but it also means that you can't modify any of the assets while the server is running and expect the changes to get picked up by clients. + +* Lead Contributor: link:http://joshpeek.com/[Josh Peek] + +=== Asset Hosts as Objects + +Asset hosts get more flexible in edge Rails with the ability to declare an asset host as a specific object that responds to a call. This allows you to to implement any complex logic you need in your asset hosting. + +* Lead Contributor: link:http://www.loudthinking.com/[David Heinemeier Hansson] +* More Information: link:http://github.com/dhh/asset-hosting-with-minimum-ssl/tree/master[asset-hosting-with-minimum-ssl] + +=== Other Action View Changes + +* Token generation for CSRF protection has been simplified; now Rails uses a simple random string generated by +ActiveSupport::SecureRandom+ rather than mucking around with session IDs. +* +auto_link+ now properly applies options (such as :target and :class) to generated e-mail links. +* The +autolink+ helper has been refactored to make it a bit less messy and more intuitive. + +== Active Support + +Active Support has a few interesting changes, including the introduction of +Object#try+. + +=== Object#try + +A lot of folks have adopted the notion of using try() to attempt operations on objects - It's especially helpful in views where you can avoid nil-checking by writing code like +<%= @person.try(:name) %>+. Well, now it's baked right into Rails. As implemented in Rails, it raises +NoMethodError+ on private methods and always returns +nil+ if the object is nil. + +* Lead Contributor: link:http://m.onkey.org/[Pratik Naik] +* More Information: link:http://ozmm.org/posts/try.html[try()]. + +=== Object#tap Backport + ++Object#tap+ is an addition to "Ruby 1.9":http://www.ruby-doc.org/core-1.9/classes/Object.html#M000309 and 1.8.7 that is similar to the +returning+ method that Rails has had for a while: it yields to a block, and then returns the object that was yielded. Rails now includes code to make this available under older versions of Ruby as well. + +Lead Contributor: link:http://bitsweat.net/[Jeremy Kemper] + +=== Fractional seconds for TimeWithZone + +The +Time+ and +TimeWithZone+ classes include an +xmlschema+ method to return the time in an XML-friendly string. As of Rails 2.3, +TimeWithZone+ supports the same argument for specifying the number of digits in the fractional second part of the returned string that +Time+ does: + +[source, ruby] +------------------------------------------------------- +>> Time.zone.now.xmlschema(6) +=> "2009-01-16T13:00:06.13653Z" +------------------------------------------------------- + +Lead Contributor: link:http://www.workingwithrails.com/person/13536-nicholas-dainty[Nicholas Dainty] + +=== JSON Key Quoting + +If you look up the spec on the "json.org" site, you'll discover that all keys in a JSON structure must be strings, and they must be quoted with double quotes. Starting with Rails 2.3, we doe the right thing here, even with numeric keys. + +Lead Contributor: link:http://www.koziarski.net/[Michael Koziarski] + +=== Other Active Support Changes + +* You can use +Enumerable#none?+ to check that none of the elements match the supplied block. +* If you're using Active Support link:http://afreshcup.com/2008/10/19/coming-in-rails-22-delegate-prefixes/[delegates], the new +:allow_nil+ option lets you return nil instead of raising an exception when the target object is nil. +* +ActiveSupport::OrderedHash+: now implements +each_key+ and +each_value+. +* +ActiveSupport::MessageEncryptor+ provides a simple way to encrypt information for storage in an untrusted location (like cookies). +* Active Support's +from_xml+ no longer depends on XmlSimple. Instead, Rails now includes its own XmlMini implementation, with just the functionality that it requires. This lets Rails dispense with the bundled copy of XmlSimple that it's been carting around. + +== Railties + +In addition to the Rack changes covered above, Railties (the core code of Rails itself) sports a number of significant changes, including Rails Metal, application templates, and quiet backtraces. + +=== Rails Metal + +Rails Metal is a new mechanism that provides superfast endpoints inside of your Rails applications. Metal classes bypass routing and Action Controller to give you raw speed (at the cost of all the things in Action Controller, of course). This builds on all of the recent foundation work to make Rails a Rack application with an exposed middleware stack. + +* Lead Contributor: link:http://www.loudthinking.com/[David Heinemeier Hansson] +* More Information: + - link:http://weblog.rubyonrails.org/2008/12/17/introducing-rails-metal[Introducing Rails Metal] + - link:http://soylentfoo.jnewland.com/articles/2008/12/16/rails-metal-a-micro-framework-with-the-power-of-rails-m[Rails Metal: a micro-framework with the power of Rails] + - link:http://www.railsinside.com/deployment/180-metal-super-fast-endpoints-within-your-rails-apps.html[Metal: Super-fast Endpoints within your Rails Apps] + - link:http://ryandaigle.com/articles/2008/12/18/what-s-new-in-edge-rails-rails-metal[What's New in Edge Rails: Rails Metal] + +=== Application Templates + +Rails 2.3 incorporates Jeremy McAnally's "rg":http://github.com/jeremymcanally/rg/tree/master application generator. What this means is that we now have template-based application generation built right into Rails; if you have a set of plugins you include in every application (among many other use cases), you can just set up a template once and use it over and over again when you run the +rails+ command. There's also a rake task to apply a template to an existing application: + +[source, ruby] +------------------------------------------------------- +rake rails:template LOCATION=~/template.rb +------------------------------------------------------- + +This will layer the changes from the template on top of whatever code the project already contains. + +* Lead Contributor: link:http://www.jeremymcanally.com/[Jeremy McAnally] +* More Info:http://m.onkey.org/2008/12/4/rails-templates[Rails templates] + +=== Quieter Backtraces + +Building on Thoughtbot's "Quiet Backtrace":http://www.thoughtbot.com/projects/quietbacktrace plugin, which allows you to selectively remove lines from Test::Unit backtraces, Rails 2.3 implements +ActiveSupport::BacktraceCleaner+ and +Rails::BacktraceCleaner+ in core. This supports both filters (to perform regex-based substitutions on backtrace lines) and silencers (to remove backtrace lines entirely). Rails automatically adds silencers to get rid of the most common noise in a new application, and builds a +config/backtrace_silencers.rb+ file to hold your own additions. This feature also enables prettier printing from any gem in the backtrace. + +* Lead Contributor: link:http://www.loudthinking.com/[David Heinemeier Hansson] + +=== Faster Boot Time in Development Mode with Lazy Loading/Autoload + +Quite a bit of work was done to make sure that bits of Rails (and its dependencies) are only brought into memory when they're actually needed. The core frameworks - Active Support, Active Record, Action Controller, Action Mailer and Action View - are now using +autoload+ to lazy-load their individual classes. This work should help keep the memory footprint down and improve overall Rails performance. + +You can also specify (by using the new +preload_frameworks+ option) whether the core libraries should be autoloaded at startup. This defaults to +false+ so that Rails autoloads itself piece-by-piece, but there are some circumstances where you still need to bring in everything at once - Passenger and JRuby both want to see all of Rails loaded together. + +* Lead Contributors: link:http://bitsweat.net/[Jeremy Kemper] and link:http://joshpeek.com/[Josh Peek] + +=== Other Railties Changes + +* The instructions for updating a CI server to build Rails have been updated and expanded. +* Internal Rails testing has been switched from +Test::Unit::TestCase+ to +ActiveSupport::TestCase+, and the Rails core requires Mocha to test. +* The default +environment.rb+ file has been decluttered. +* The dbconsole script now lets you use an all-numeric password without crashing. +* Rails.root now returns a Pathname object, which means you can use it directly with the join method to link:http://afreshcup.com/2008/12/05/a-little-rails_root-tidiness/[clean up existing code] that uses File.join. +* Various files in /public that deal with CGI and FCGI dispatching are no longer generated in every Rails application by default (you can still get them if you need them by adding +--with-dispatches+ when you run the rails command, or add them later with +rake rails:generate_dispatchers+). + +== Deprecated + +A few pieces of older code are deprecated in this release: + +* If you're one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you'll need to know that those scripts are no longer included in core Rails. If you need them, you'll be able to pick up copies via the link:http://github.com/rails/irs_process_scripts/tree[irs_process_scripts] plugin. +* +render_component+ goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the link:http://github.com/rails/render_component/tree/master[render_component plugin]. +* Support for Rails components has been removed. +* If you were one of the people who got used to running +script/performance/request+ to look at performance based on integration tests, you need to learn a new trick: that script has been removed from core Rails now. There’s a new request_profiler plugin that you can install to get the exact same functionality back. +* +ActionController::Base#session_enabled?+ is deprecated because sessions are lazy-loaded now. +* The +:digest+ and +:secret+ options to +protect_from_forgery+ are deprecated and have no effect. +* Some integration test helpers have been removed. +response.headers["Status"]+ and +headers["Status"]+ will no longer return anything. Rack does not allow "Status" in its return headers. However you can still use the +status+ and +status_message+ helpers. +response.headers["cookie"]+ and +headers["cookie"]+ will no longer return any CGI cookies. You can inspect +headers["Set-Cookie"]+ to see the raw cookie header or use the +cookies+ helper to get a hash of the cookies sent to the client. + +== Credits + +Release notes compiled by link:http://afreshcup.com[Mike Gunderloy] -- cgit v1.2.3 From fbd2cd64e2664a84edffdfa808da7680c6d777ec Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 28 Jan 2009 03:47:07 +0000 Subject: Change the license to Creative Commons Attribution-Share Alike --- railties/doc/guides/html/2_3_release_notes.html | 892 ++++++++++++++++++++++++ railties/doc/guides/html/index.html | 2 +- railties/doc/guides/source/index.txt | 2 +- 3 files changed, 894 insertions(+), 2 deletions(-) create mode 100644 railties/doc/guides/html/2_3_release_notes.html diff --git a/railties/doc/guides/html/2_3_release_notes.html b/railties/doc/guides/html/2_3_release_notes.html new file mode 100644 index 0000000000..07e825ca99 --- /dev/null +++ b/railties/doc/guides/html/2_3_release_notes.html @@ -0,0 +1,892 @@ + + + + + Ruby on Rails 2.3 Release Notes + + + + + + + + +
    + + + +
    +

    Ruby on Rails 2.3 Release Notes

    +
    +
    +

    Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn’t include every little bug fix and change. If you want to see everything, check out the list of commits in the main Rails repository on GitHub.

    +
    +
    +

    1. Application Architecture

    +
    +

    There are two major changes in the architecture of Rails applications: complete integration of the Rack modular web server interface, and renewed support for Rails Engines.

    +

    1.1. Rack Integration

    +

    Rails has now broken with its CGI past, and uses Rack everywhere. This required and resulted in a tremendous number of internal changes (but if you use CGI, don’t worry; Rails now supports CGI through a proxy interface.) Still, this is a major change to Rails internals. After upgrading to 2.3, you should test on your local environment and your production environment. Some things to test:

    +
      +
    • +

      +Sessions +

      +
    • +
    • +

      +Cookies +

      +
    • +
    • +

      +File uploads +

      +
    • +
    • +

      +JSON/XML APIs +

      +
    • +
    +

    Here’s a summary of the rack-related changes:

    +
      +
    • +

      +script/server has been switched to use Rack, which means it supports any Rack compatible server. script/server will also pick up a rackup configuration file if one exists. By default, it will look for a config.ru file, but you can override this with the -c switch. +

      +
    • +
    • +

      +The FCGI handler goes through Rack +

      +
    • +
    • +

      +ActionController::Dispatcher maintains its own default middleware stack. Middlewares can be injected in, reordered, and removed. The stack is compiled into a chain on boot. You can configure the middleware stack in environment.rb +

      +
    • +
    • +

      +The rake middleware task has been added to inspect the middleware stack. This is useful for debugging the order of the middleware stack. +

      +
    • +
    • +

      +The integration test runner has been modified to execute the entire middleware and application stack. This makes integration tests perfect for testing Rack middleware. +

      +
    • +
    • +

      +ActionController::CGIHandler is a backwards compatible CGI wrapper around Rack. The CGIHandler is meant to take an old CGI object and converts its environment information into a Rack compatible form. +

      +
    • +
    • +

      +CgiRequest and CgiResponse have been removed +

      +
    • +
    • +

      +Session stores are now lazy loaded. If you never access the session object during a request, it will never attempt to load the session data (parse the cookie, load the data from memcache, or lookup an Active Record object). +

      +
    • +
    • +

      +CGI::Session::CookieStore has been replaced by ActionController::Session::CookieStore +

      +
    • +
    • +

      +CGI::Session::MemCacheStore has been replaced by ActionController::Session::MemCacheStore +

      +
    • +
    • +

      +CGI::Session::ActiveRecordStore has been replaced by ActiveRecord::SessionStore +

      +
    • +
    • +

      +You can still change you session store with ActionController::Base.session_store = :active_record_store +

      +
    • +
    • +

      +Default sessions options are still set with ActionController::Base.session = { :key => "..." } +

      +
    • +
    • +

      +The mutex that normally wraps your entire request has been moved into middleware, ActionController::Lock +

      +
    • +
    • +

      +ActionController::AbstractRequest and ActionController::Request have been unified. The new ActionController::Request inherits from Rack::Request. This affects access to response.headers[type] in test requests. Use response.content_type instead. +

      +
    • +
    • +

      +ActiveRecord::QueryCache middleware is automatically inserted onto the middleware stack if ActiveRecord has been loaded. This middleware sets up and flushes the per-request Active Record query cache. +

      +
    • +
    • +

      +The Rails router and controller classes follow the Rack spec. You can call a controller directly with SomeController.call(env). The router stores the routing parameters in rack.routing_args. +

      +
    • +
    • +

      +ActionController::Request inherits from Rack::Request +

      +
    • +
    • +

      +Instead of config.action_controller.session = { :session_key => foo, ... use config.action_controller.session = { :key => foo, ... +

      +
    • +
    • +

      +Using the ParamsParser middleware preprocesses any XML, JSON, or YAML requests so they can be read normally with any Rack::Request object after it. +

      +
    • +
    +

    1.2. Renewed Support for Rails Engines

    +

    After some versions without an upgrade, Rails 2.3 offers some new features for Rails Engines (Rails applications that can be embedded within other applications). First, routing files in engines are automatically loaded and reloaded now, just like your routes.rb file (this also applies to routing files in other plugins). Second, if your plugin has an app folder, then app/[models|controllers|helpers] will automatically be added to the Rails load path. Engines also support adding view paths now.

    +
    +
    +

    2. Documentation

    +
    +

    The Ruby on Rails guides project has published several additional guides for Rails 2.3. In addition, a separate site maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the Rails wiki and early planning for a Rails Book.

    +
    +
    +

    3. Active Record

    +
    +

    Active Record gets quite a number of new features and bug fixes in Rails 2.3. The highlights include nested transactions, dynamic scopes, and default scopes.

    +

    3.1. Nested Transactions

    +

    Active Record now supports nested transactions, a much-requested feature. Now you can write code like this:

    +
    +
    +
    User.transaction do
    +    User.create(:username => 'Admin')
    +    User.transaction(:requires_new => true) do
    +      User.create(:username => 'Regular')
    +      raise ActiveRecord::Rollback
    +    end
    +  end
    +
    +  User.find(:all)  # => Returns only Admin
    +

    Nested transactions let you roll back an inner transaction without affecting the state of the outer transaction. If you want a transaction to be nested, you must explicitly add the :requires_new option; otherwise, a nested transaction simply becomes part of the parent transaction (as it does currently on Rails 2.2). Under the covers, nested transactions are using savepoints, so they’re supported even on databases that don’t have true nested transactions. There is also a bit of magic going on to make these transactions play well with transactional fixtures during testing.

    +
    +

    3.2. Dynamic Scopes

    +

    You know about dynamic finders in Rails (which allow you to concoct methods like find_by_color_and_flavor on the fly) and named scopes (which allow you to encapsulate reusable query conditions into friendly names like currently_active). Well, now you can have dynamic scope methods. The idea is to put together syntax that allows filtering on the fly <i>and</i> method chaining. For example:

    +
    +
    +
    Order.scoped_by_customer_id(12)
    +Order.scoped_by_customer_id(12).find(:all,
    +  :conditions => "status = 'open'")
    +Order.scoped_by_customer_id(12).scoped_by_status("open")
    +

    There’s nothing to define to use dynamic scopes: they just work.

    +
    +

    3.3. Default Scopes

    +

    Rails 2.3 will introduce the notion of default scopes similar to named scopes, but applying to all named scopes or find methods within the model. For example, you can write default_scope :order => name ASC and any time you retrieve records from that model they’ll come out sorted by name (unless you override the option, of course).

    +
    +

    3.4. Multiple Conditions for Callbacks

    +

    When using Active Record callbacks, you can now combine :if and :unless options on the same callback, and supply multiple conditions as an array:

    +
    +
    +
    before_save :update_credit_rating, :if => :active,
    +  :unless => [:admin, :cash_only]
    +
      +
    • +

      +Lead Contributor: L. Caviola +

      +
    • +
    +

    3.5. Find with having

    +

    Rails now has a :having option on find (as well as on has_many and has_and_belongs_to_many associations) for filtering records in grouped finds. As those with heavy SQL backgrounds know, this allows filtering based on grouped results:

    +
    +
    +
    developers =  Developer.find(:all, :group => "salary",
    +  :having => "sum(salary) >  10000", :select => "salary")
    +
    +

    3.6. Hash Conditions for has_many relationships

    +

    You can once again use a hash in conditions for a has_many relationship:

    +
    +
    +
    has_many :orders, :conditions => {:status => 'confirmed'}
    +

    That worked in Rails 2.1, fails in Rails 2.2, and will now work again in Rails 2.3 (if you’re dealing with this issue in Rails 2.2, you can use a string rather than a hash to specify conditions).

    +
    +

    3.7. Other Active Record Changes

    +
      +
    • +

      +An extra AS was removed from the code for has_and_belongs_to_many preloading, making it work better for some databases. +

      +
    • +
    • +

      +ActiveRecord::Base#new_record? now returns false rather than nil when confronted with an existing record. +

      +
    • +
    • +

      +A bug in quoting table names in some has_many :through associations was fixed. +

      +
    • +
    • +

      +You can now specify a particular timestamp for updated_at timestamps: cust = Customer.create(:name => "ABC Industries", :updated_at => 1.day.ago) +

      +
    • +
    • +

      +Better error messages on failed find_by_attribute! calls. +

      +
    • +
    • +

      +Active Record’s to_xml support gets just a little bit more flexible with the addition of a :camelize option. +

      +
    • +
    • +

      +A bug in canceling callbacks from before_update or +before_create_ was fixed. +

      +
    • +
    +
    +

    4. Action Controller

    +
    +

    Action Controller rolls out some significant changes to rendering, as well as improvements in routing and other areas, in this release.

    +

    4.1. Unified Rendering

    +

    ActionController::Base#render is a lot smarter about deciding what to render. Now you can just tell it what to render and expect to get the right results. In older versions of Rails, you often need to supply explicit information to render:

    +
    +
    +
    render :file => '/tmp/random_file.erb'
    +render :template => 'other_controller/action'
    +render :action => 'show'
    +

    Now in Rails 2.3, you can just supply what you want to render:

    +
    +
    +
    render '/tmp/random_file.erb'
    +render 'other_controller/action'
    +render 'show'
    +render :show
    +

    Rails chooses between file, template, and action depending on whether there is a leading slash, an embedded slash, or no slash at all in what’s to be rendered. Note that you can also use a symbol instead of a string when rendering an action. Other rendering styles (:inline, :text, :update, :nothing, :json, :xml, :js) still require an explicit option.

    +
    +

    4.2. Application Controller Renamed

    +

    If you’re one of the people who has always been bothered by the special-case naming of application.rb, rejoice! It’s been reworked to be application_controller.rb in Rails 2.3. In addition, there’s a new rake task, rake rails:update:application_controller to do this automatically for you - and it will be run as part of the normal rake rails:update process.

    +
    +

    4.3. More Efficient Routing

    +

    There are a couple of significant routing changes in Rails 2.3. The formatted_ route helpers are gone, in favor just passing in :format as an option. This cuts down the route generation process by 50% for any resource - and can save a substantial amount of memory (up to 100MB on large applications). If your code uses the formatted_ helpers, it will still work for the time being - but that behavior is deprecated and your application will be more efficient if you rewrite those routes using the new standard. Another big change is that Rails now supports multiple routing files, not just routes.rb. You can use RouteSet#add_configuration_file to bring in more routes at any time - without clearing the currently-loaded routes. While this change is most useful for Engines, you can use it in any application that needs to load routes in batches.

    + +

    4.4. Rack-based Lazy-loaded Sessions

    +

    A big change pushed the underpinnings of Action Controller session storage down to the Rack level. This involved a good deal of work in the code, though it should be completely transparent to your Rails applications (as a bonus, some icky patches around the old CGI session handler got removed). It’s still significant, though, for one simple reason: non-Rails Rack applications have access to the same session storage handlers (and therefore the same session) as your Rails applications. In addition, sessions are now lazy-loaded (in line with the loading improvements to the rest of the framework). This means that you no longer need to explicitly disable sessions if you don’t want them; just don’t refer to them and they won’t load.

    +
    +

    4.5. MIME Type Handling Changes

    +

    There are a couple of changes to the code for handling MIME types in Rails. First, MIME::Type now implements the =~ operator, making things much cleaner when you need to check for the presence of a type that has synonyms:

    +
    +
    +
    if content_type && Mime::JS =~ content_type
    +  # do something cool
    +end
    +
    +Mime::JS =~ "text/javascript"        => true
    +Mime::JS =~ "application/javascript" => true
    +

    The other change is that the framework now uses the Mime::JS when checking for javascript in various spots, making it handle those alternatives cleanly.

    +

    Lead Contributor: Seth Fitzsimmons

    +

    4.6. Optimization of respond_to

    +

    In some of the first fruits of the Rails-Merb team merger, Rails 2.3 includes some optimizations for the respond_to method, which is of course heavily used in many Rails applications to allow your controller to format results differently based on the MIME type of the incoming request. After eliminating a call to method_missing and some profiling and tweaking, we’re seeing an 8% improvement in the number of requests per second served with a simple respond_to that switches between three formats. The best part? No change at all required to the code of your application to take advantage of this speedup.

    +
    +

    4.7. Improved Caching Performance

    +

    Rails now keeps a per-request local cache of requests, cutting down on unnecessary reads and leading to better site performance. While this work was originally limited to MemCacheStore, it is available to any remote store than implements the required methods.

    +
    +

    4.8. Other Action Controller Changes

    +
      +
    • +

      +ETag handling has been cleaned up a bit: Rails will now skip sending an ETag header when there’s no body to the response or when sending files with send_file. +

      +
    • +
    • +

      +The fact that Rails checks for IP spoofing can be a nuisance for sites that do heavy traffic with cell phones, because their proxies don’t generally set things up right. If that’s you, you can now set ActionController::Base.ip_spoofing_check = false to disable the check entirely. +

      +
    • +
    • +

      +ActionController::Dispatcher now implements its own middleware stack, which you can see by running rake middleware. +

      +
    • +
    • +

      +Cookie sessions now have persistent session identifiers, with API compatibility with the server-side stores. +

      +
    • +
    • +

      +You can now use symbols for the :type option of send_file and send_data, like this: send_file("fabulous.png", :type => :png). +

      +
    • +
    +
    +

    5. Action View

    +
    +

    Action View in Rails 2.3 picks up improvements to render, more flexible prompts for the date select helpers, and a speedup in asset caching, among other things.

    +

    5.1. Smart Rendering

    +

    The render method has been getting smarter over the years, and it’s even smarter now. If you have an object or a collection and an appropriate partial, and the naming matches up, you can now just render the object and things will work. For example, in Rails 2.3, these render calls will work in your view (assuming sensible naming):

    +
    +
    +
    <% render @article %>
    +<% render @articles %>
    +
    +

    5.2. Prompts for Date Select Helpers

    +

    In Rails 2.3, you can supply custom prompts for the various date select helpers (date_select, time_select, and datetime_select), the same way you can with collection select helpers. You can supply a prompt string or a hash of individual prompt strings for the various components. You can also just set :prompt to true to use the custom generic prompt:

    +
    +
    +
    select_datetime(DateTime.now, :prompt => true)
    +
    +select_datetime(DateTime.now, :prompt => "Choose date and time")
    +
    +select_datetime(DateTime.now, :prompt =>
    +  {:day => 'Choose day', :month => 'Choose month',
    +   :year => 'Choose year', :hour => 'Choose hour',
    +   :minute => 'Choose minute'})
    +

    Lead Contributor: Sam Oliver

    +

    5.3. AssetTag Timestamp Caching

    +

    You’re likely familiar with Rails' practice of adding timestamps to static asset paths as a "cache buster." This helps ensure that stale copies of things like images and stylesheets don’t get served out of the user’s browser cache when you change them on the server. You can now modify this behavior with the cache_asset_timestamps configuration option for Action View. If you enable the cache, then Rails will calculate the timestamp once when it first serves an asset, and save that value. This means fewer (expensive) file system calls to serve static assets - but it also means that you can’t modify any of the assets while the server is running and expect the changes to get picked up by clients.

    +
    +

    5.4. Asset Hosts as Objects

    +

    Asset hosts get more flexible in edge Rails with the ability to declare an asset host as a specific object that responds to a call. This allows you to to implement any complex logic you need in your asset hosting.

    +
    +

    5.5. Other Action View Changes

    +
      +
    • +

      +Token generation for CSRF protection has been simplified; now Rails uses a simple random string generated by ActiveSupport::SecureRandom rather than mucking around with session IDs. +

      +
    • +
    • +

      +auto_link now properly applies options (such as :target and :class) to generated e-mail links. +

      +
    • +
    • +

      +The autolink helper has been refactored to make it a bit less messy and more intuitive. +

      +
    • +
    +
    +

    6. Active Support

    +
    +

    Active Support has a few interesting changes, including the introduction of Object#try.

    +

    6.1. Object#try

    +

    A lot of folks have adopted the notion of using try() to attempt operations on objects - It’s especially helpful in views where you can avoid nil-checking by writing code like <%= @person.try(:name) %>. Well, now it’s baked right into Rails. As implemented in Rails, it raises NoMethodError on private methods and always returns nil if the object is nil.

    +
    +

    6.2. Object#tap Backport

    +

    Object#tap is an addition to "Ruby 1.9":http://www.ruby-doc.org/core-1.9/classes/Object.html#M000309 and 1.8.7 that is similar to the returning method that Rails has had for a while: it yields to a block, and then returns the object that was yielded. Rails now includes code to make this available under older versions of Ruby as well.

    +

    Lead Contributor: Jeremy Kemper

    +

    6.3. Fractional seconds for TimeWithZone

    +

    The Time and TimeWithZone classes include an xmlschema method to return the time in an XML-friendly string. As of Rails 2.3, TimeWithZone supports the same argument for specifying the number of digits in the fractional second part of the returned string that Time does:

    +
    +
    +
    >> Time.zone.now.xmlschema(6)
    +=> "2009-01-16T13:00:06.13653Z"
    +

    Lead Contributor: Nicholas Dainty

    +

    6.4. JSON Key Quoting

    +

    If you look up the spec on the "json.org" site, you’ll discover that all keys in a JSON structure must be strings, and they must be quoted with double quotes. Starting with Rails 2.3, we doe the right thing here, even with numeric keys.

    +

    Lead Contributor: Michael Koziarski

    +

    6.5. Other Active Support Changes

    +
      +
    • +

      +You can use Enumerable#none? to check that none of the elements match the supplied block. +

      +
    • +
    • +

      +If you’re using Active Support delegates, the new :allow_nil option lets you return nil instead of raising an exception when the target object is nil. +

      +
    • +
    • +

      +ActiveSupport::OrderedHash: now implements each_key and each_value. +

      +
    • +
    • +

      +ActiveSupport::MessageEncryptor provides a simple way to encrypt information for storage in an untrusted location (like cookies). +

      +
    • +
    • +

      +Active Support’s from_xml no longer depends on XmlSimple. Instead, Rails now includes its own XmlMini implementation, with just the functionality that it requires. This lets Rails dispense with the bundled copy of XmlSimple that it’s been carting around. +

      +
    • +
    +
    +

    7. Railties

    +
    +

    In addition to the Rack changes covered above, Railties (the core code of Rails itself) sports a number of significant changes, including Rails Metal, application templates, and quiet backtraces.

    +

    7.1. Rails Metal

    +

    Rails Metal is a new mechanism that provides superfast endpoints inside of your Rails applications. Metal classes bypass routing and Action Controller to give you raw speed (at the cost of all the things in Action Controller, of course). This builds on all of the recent foundation work to make Rails a Rack application with an exposed middleware stack.

    + +

    7.2. Application Templates

    +

    Rails 2.3 incorporates Jeremy McAnally’s "rg":http://github.com/jeremymcanally/rg/tree/master application generator. What this means is that we now have template-based application generation built right into Rails; if you have a set of plugins you include in every application (among many other use cases), you can just set up a template once and use it over and over again when you run the rails command. There’s also a rake task to apply a template to an existing application:

    +
    +
    +
    rake rails:template LOCATION=~/template.rb
    +

    This will layer the changes from the template on top of whatever code the project already contains.

    +
    +

    7.3. Quieter Backtraces

    +

    Building on Thoughtbot’s "Quiet Backtrace":http://www.thoughtbot.com/projects/quietbacktrace plugin, which allows you to selectively remove lines from Test::Unit backtraces, Rails 2.3 implements ActiveSupport::BacktraceCleaner and Rails::BacktraceCleaner in core. This supports both filters (to perform regex-based substitutions on backtrace lines) and silencers (to remove backtrace lines entirely). Rails automatically adds silencers to get rid of the most common noise in a new application, and builds a config/backtrace_silencers.rb file to hold your own additions. This feature also enables prettier printing from any gem in the backtrace.

    +
    +

    7.4. Faster Boot Time in Development Mode with Lazy Loading/Autoload

    +

    Quite a bit of work was done to make sure that bits of Rails (and its dependencies) are only brought into memory when they’re actually needed. The core frameworks - Active Support, Active Record, Action Controller, Action Mailer and Action View - are now using autoload to lazy-load their individual classes. This work should help keep the memory footprint down and improve overall Rails performance.

    +

    You can also specify (by using the new preload_frameworks option) whether the core libraries should be autoloaded at startup. This defaults to false so that Rails autoloads itself piece-by-piece, but there are some circumstances where you still need to bring in everything at once - Passenger and JRuby both want to see all of Rails loaded together.

    +
    +

    7.5. Other Railties Changes

    +
      +
    • +

      +The instructions for updating a CI server to build Rails have been updated and expanded. +

      +
    • +
    • +

      +Internal Rails testing has been switched from Test::Unit::TestCase to ActiveSupport::TestCase, and the Rails core requires Mocha to test. +

      +
    • +
    • +

      +The default environment.rb file has been decluttered. +

      +
    • +
    • +

      +The dbconsole script now lets you use an all-numeric password without crashing. +

      +
    • +
    • +

      +Rails.root now returns a Pathname object, which means you can use it directly with the join method to clean up existing code that uses File.join. +

      +
    • +
    • +

      +Various files in /public that deal with CGI and FCGI dispatching are no longer generated in every Rails application by default (you can still get them if you need them by adding --with-dispatches when you run the rails command, or add them later with rake rails:generate_dispatchers). +

      +
    • +
    +
    +

    8. Deprecated

    +
    +

    A few pieces of older code are deprecated in this release:

    +
      +
    • +

      +If you’re one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you’ll need to know that those scripts are no longer included in core Rails. If you need them, you’ll be able to pick up copies via the irs_process_scripts plugin. +

      +
    • +
    • +

      +render_component goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the render_component plugin. +

      +
    • +
    • +

      +Support for Rails components has been removed. +

      +
    • +
    • +

      +If you were one of the people who got used to running script/performance/request to look at performance based on integration tests, you need to learn a new trick: that script has been removed from core Rails now. There’s a new request_profiler plugin that you can install to get the exact same functionality back. +

      +
    • +
    • +

      +ActionController::Base#session_enabled? is deprecated because sessions are lazy-loaded now. +

      +
    • +
    • +

      +The :digest and :secret options to protect_from_forgery are deprecated and have no effect. +

      +
    • +
    • +

      +Some integration test helpers have been removed. response.headers["Status"] and headers["Status"] will no longer return anything. Rack does not allow "Status" in its return headers. However you can still use the status and status_message helpers. response.headers["cookie"] and headers["cookie"] will no longer return any CGI cookies. You can inspect headers["Set-Cookie"] to see the raw cookie header or use the cookies helper to get a hash of the cookies sent to the client. +

      +
    • +
    +
    +

    9. Credits

    +
    +

    Release notes compiled by Mike Gunderloy

    +
    + +
    +
    + + diff --git a/railties/doc/guides/html/index.html b/railties/doc/guides/html/index.html index 65dd682754..16ce603cec 100644 --- a/railties/doc/guides/html/index.html +++ b/railties/doc/guides/html/index.html @@ -212,7 +212,7 @@ of your code.

    This guide covers the command line tools and rake tasks provided by Rails.

    Authors who have contributed to complete guides are listed here.

    - + diff --git a/railties/doc/guides/source/index.txt b/railties/doc/guides/source/index.txt index 8e8298ebb8..bb43b0175b 100644 --- a/railties/doc/guides/source/index.txt +++ b/railties/doc/guides/source/index.txt @@ -142,4 +142,4 @@ This guide covers the command line tools and rake tasks provided by Rails. Authors who have contributed to complete guides are listed link:authors.html[here]. -This work is licensed under a link:http://creativecommons.org/licenses/by-nc-sa/3.0/[Creative Commons Attribution-Noncommercial-Share Alike 3.0 License] +This work is licensed under a link:http://creativecommons.org/licenses/by-sa/3.0[Creative Commons Attribution-Share Alike 3.0 License] -- cgit v1.2.3 From 2ae8d3079b96d343f8cea8513929d656013f880e Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 28 Jan 2009 05:05:07 +0000 Subject: Session cookie header should always be set if :expire_after option is specified --- .../action_controller/session/abstract_store.rb | 6 ++-- .../lib/action_controller/session/cookie_store.rb | 6 ++-- .../test/controller/session/cookie_store_test.rb | 36 +++++++++++++++++++--- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/actionpack/lib/action_controller/session/abstract_store.rb b/actionpack/lib/action_controller/session/abstract_store.rb index bf09fd33c5..9434c2e05e 100644 --- a/actionpack/lib/action_controller/session/abstract_store.rb +++ b/actionpack/lib/action_controller/session/abstract_store.rb @@ -102,8 +102,10 @@ module ActionController response = @app.call(env) session_data = env[ENV_SESSION_KEY] - if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) - options = env[ENV_SESSION_OPTIONS_KEY] + options = env[ENV_SESSION_OPTIONS_KEY] + + if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after] + session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?) if session_data.is_a?(AbstractStore::SessionHash) sid = session_data.id diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb index 6ad6369950..5a728d1877 100644 --- a/actionpack/lib/action_controller/session/cookie_store.rb +++ b/actionpack/lib/action_controller/session/cookie_store.rb @@ -93,12 +93,14 @@ module ActionController status, headers, body = @app.call(env) session_data = env[ENV_SESSION_KEY] - if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) + options = env[ENV_SESSION_OPTIONS_KEY] + + if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after] + session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?) session_data = marshal(session_data.to_hash) raise CookieOverflow if session_data.size > MAX - options = env[ENV_SESSION_OPTIONS_KEY] cookie = Hash.new cookie[:value] = session_data unless options[:expire_after].nil? diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb index b6a38f47aa..95d2eb11c4 100644 --- a/actionpack/test/controller/session/cookie_store_test.rb +++ b/actionpack/test/controller/session/cookie_store_test.rb @@ -6,13 +6,11 @@ class CookieStoreTest < ActionController::IntegrationTest SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33' DispatcherApp = ActionController::Dispatcher.new - CookieStoreApp = ActionController::Session::CookieStore.new(DispatcherApp, - :key => SessionKey, :secret => SessionSecret) + CookieStoreApp = ActionController::Session::CookieStore.new(DispatcherApp, :key => SessionKey, :secret => SessionSecret) Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1') - SignedBar = "BAh7BjoIZm9vIghiYXI%3D--" + - "fef868465920f415f2c0652d6910d3af288a0367" + SignedBar = "BAh7BjoIZm9vIghiYXI%3D--fef868465920f415f2c0652d6910d3af288a0367" class TestController < ActionController::Base def no_session_access @@ -177,6 +175,36 @@ class CookieStoreTest < ActionController::IntegrationTest end end + def test_session_store_with_expire_after + app = ActionController::Session::CookieStore.new(DispatcherApp, :key => SessionKey, :secret => SessionSecret, :expire_after => 5.hours) + @integration_session = open_session(app) + + with_test_route_set do + # First request accesses the session + time = Time.local(2008, 4, 24) + Time.stubs(:now).returns(time) + expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") + + cookies[SessionKey] = SignedBar + + get '/set_session_value' + assert_response :success + + cookie_body = response.body + assert_equal ["_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; httponly"], headers['Set-Cookie'] + + # Second request does not access the session + time = Time.local(2008, 4, 25) + Time.stubs(:now).returns(time) + expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") + + get '/no_session_access' + assert_response :success + + assert_equal ["_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; httponly"], headers['Set-Cookie'] + end + end + private def with_test_route_set with_routing do |set| -- cgit v1.2.3 From 74871961eccdb455f18e8ef66716041f2f828ba8 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 28 Jan 2009 19:20:50 +0000 Subject: =?UTF-8?q?Add=20array=20id=20support=20to=20Model.update=5Fcounte?= =?UTF-8?q?rs.=20[#1254=20state:resolved]=20[Carlos=20J=C3=BAnior]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- activerecord/lib/active_record/base.rb | 19 +++++++++++++++++-- activerecord/test/cases/base_test.rb | 7 +++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 8db3909d9a..479296ebae 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -927,7 +927,7 @@ module ActiveRecord #:nodoc: # # ==== Parameters # - # * +id+ - The id of the object you wish to update a counter on. + # * +id+ - The id of the object you wish to update a counter on or an Array of ids. # * +counters+ - An Array of Hashes containing the names of the fields # to update as keys and the amount to update the field by as values. # @@ -941,12 +941,27 @@ module ActiveRecord #:nodoc: # # SET comment_count = comment_count - 1, # # action_count = action_count + 1 # # WHERE id = 5 + # + # # For the Posts with id of 10 and 15, increment the comment_count by 1 + # Post.update_counters [10, 15], :comment_count => 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = comment_count + 1, + # # WHERE id IN (10, 15) def update_counters(id, counters) updates = counters.inject([]) { |list, (counter_name, increment)| sign = increment < 0 ? "-" : "+" list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}" }.join(", ") - update_all(updates, "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}") + + if id.is_a?(Array) + ids_list = id.map {|i| quote_value(i)}.join(', ') + condition = "IN (#{ids_list})" + else + condition = "= #{quote_value(id)}" + end + + update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}") end # Increment a number field by one, usually representing a count. diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 0f03dae829..973bb567bd 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -639,6 +639,13 @@ class BasicsTest < ActiveRecord::TestCase category.reload assert_not_nil category.categorizations_count assert_equal 4, category.categorizations_count + + category_2 = categories(:technology) + count_1, count_2 = (category.categorizations_count || 0), (category_2.categorizations_count || 0) + Category.update_counters([category.id, category_2.id], "categorizations_count" => 2) + category.reload; category_2.reload + assert_equal count_1 + 2, category.categorizations_count + assert_equal count_2 + 2, category_2.categorizations_count end def test_update_all -- cgit v1.2.3 From 32eeb3e5211a4a7bfc7a1d0aa0cab1486bed3581 Mon Sep 17 00:00:00 2001 From: Nathan de Vries Date: Wed, 28 Jan 2009 19:31:48 +0000 Subject: Ensure that when UrlWriter is included in multiple classes, the default_url_options of one don't affect the other. [#1277 state:resolved] Signed-off-by: Pratik Naik --- actionpack/lib/action_controller/url_rewriter.rb | 9 +++------ actionpack/test/controller/url_rewriter_test.rb | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb index d86e2db67d..bb6cb437b7 100644 --- a/actionpack/lib/action_controller/url_rewriter.rb +++ b/actionpack/lib/action_controller/url_rewriter.rb @@ -92,15 +92,12 @@ module ActionController # end # end module UrlWriter - # The default options for urls written by this writer. Typically a :host - # pair is provided. - mattr_accessor :default_url_options - self.default_url_options = {} - def self.included(base) #:nodoc: ActionController::Routing::Routes.install_helpers(base) base.mattr_accessor :default_url_options - base.default_url_options ||= default_url_options + + # The default options for urls written by this writer. Typically a :host pair is provided. + base.default_url_options ||= {} end # Generate a url based on the options provided, default_url_options and the diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index e9d372544e..09a8356fec 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -303,7 +303,6 @@ class UrlWriterTests < ActionController::TestCase def test_named_routes_with_nil_keys ActionController::Routing::Routes.clear! - add_host! ActionController::Routing::Routes.draw do |map| map.main '', :controller => 'posts' map.resources :posts @@ -311,6 +310,8 @@ class UrlWriterTests < ActionController::TestCase end # We need to create a new class in order to install the new named route. kls = Class.new { include ActionController::UrlWriter } + kls.default_url_options[:host] = 'www.basecamphq.com' + controller = kls.new params = {:action => :index, :controller => :posts, :format => :xml} assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params)) @@ -337,6 +338,20 @@ class UrlWriterTests < ActionController::TestCase ensure ActionController::Routing::Routes.load! end + + def test_multiple_includes_maintain_distinct_options + first_class = Class.new { include ActionController::UrlWriter } + second_class = Class.new { include ActionController::UrlWriter } + + first_host, second_host = 'firsthost.com', 'secondhost.com' + + first_class.default_url_options[:host] = first_host + second_class.default_url_options[:host] = second_host + + assert_equal first_class.default_url_options[:host], first_host + assert_equal second_class.default_url_options[:host], second_host + end + private def extract_params(url) url.split('?', 2).last.split('&') -- cgit v1.2.3 From 9a8e2a059c823498e0fb318f25b926a6d015ef87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Rodr=C3=ADguez=20Troiti=C3=B1o?= Date: Fri, 12 Sep 2008 23:56:56 +0200 Subject: Ensure selected option is not ignored for collection_select. [#1037 state:resolved] Signed-off-by: Pratik Naik --- .../lib/action_view/helpers/form_options_helper.rb | 3 ++- actionpack/test/template/form_options_helper_test.rb | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 9ed50a9653..3991c5c0cc 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -349,8 +349,9 @@ module ActionView html_options = html_options.stringify_keys add_default_name_and_id(html_options) value = value(object) + selected_value = options.has_key?(:selected) ? options[:selected] : value content_tag( - "select", add_options(options_from_collection_for_select(collection, value_method, text_method, value), options, value), html_options + "select", add_options(options_from_collection_for_select(collection, value_method, text_method, selected_value), options, value), html_options ) end diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 86a0bb6a79..78548864ff 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -473,6 +473,22 @@ uses_mocha "FormOptionsHelperTest" do assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true, :name => 'post[author_name][]' }, :multiple => true) end + def test_collection_select_with_blank_and_selected + @posts = [ + Post.new(" went home", "", "To a little house", "shh!"), + Post.new("Babe went home", "Babe", "To a little house", "shh!"), + Post.new("Cabe went home", "Cabe", "To a little house", "shh!") + ] + + @post = Post.new + @post.author_name = "Babe" + + assert_dom_equal( + %{}, + collection_select("post", "author_name", @posts, "author_name", "author_name", {:include_blank => true, :selected => ""}) + ) + end + def test_time_zone_select @firm = Firm.new("D") html = time_zone_select( "firm", "time_zone" ) -- cgit v1.2.3 From 6079ec1f77daf364a2b25cf651e9b3c9e1b95a16 Mon Sep 17 00:00:00 2001 From: Rasik Pandey Date: Wed, 28 Jan 2009 19:39:06 +0000 Subject: ActiveResource#eqls? and == should not take into account object identity and prefix options should be considered. [#1098 state:resolved] Signed-off-by: Pratik Naik --- activeresource/lib/active_resource/base.rb | 4 ++-- activeresource/test/base/equality_test.rb | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 4192fab525..54dde43087 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -746,8 +746,8 @@ module ActiveResource # # => true # def ==(other) - other.equal?(self) || (other.instance_of?(self.class) && !other.new? && other.id == id) - end + other.equal?(self) || (other.instance_of?(self.class) && other.id == id && other.prefix_options == prefix_options) + end # Tests for equality (delegates to ==). def eql?(other) diff --git a/activeresource/test/base/equality_test.rb b/activeresource/test/base/equality_test.rb index 1fb8938e95..84f1a7b998 100644 --- a/activeresource/test/base/equality_test.rb +++ b/activeresource/test/base/equality_test.rb @@ -40,4 +40,13 @@ class BaseEqualityTest < Test::Unit::TestCase assert_equal resource.id.hash, resource.hash end end + + def test_with_prefix_options + assert_equal @one == @one, @one.eql?(@one) + assert_equal @one == @one.dup, @one.eql?(@one.dup) + new_one = @one.dup + new_one.prefix_options = {:foo => 'bar'} + assert_not_equal @one, new_one + end + end -- cgit v1.2.3 From f725b1971072a2203d6d433149730289cbb80128 Mon Sep 17 00:00:00 2001 From: Bryan Ray Date: Wed, 28 Jan 2009 19:47:45 +0000 Subject: Added options to script/dbconsole to sqlite3 console in various different modes. [#607 state:resolved] Signed-off-by: Pratik Naik --- railties/lib/commands/dbconsole.rb | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/railties/lib/commands/dbconsole.rb b/railties/lib/commands/dbconsole.rb index 06848d3c91..8002264f7e 100644 --- a/railties/lib/commands/dbconsole.rb +++ b/railties/lib/commands/dbconsole.rb @@ -3,12 +3,23 @@ require 'yaml' require 'optparse' include_password = false +options = {} OptionParser.new do |opt| opt.banner = "Usage: dbconsole [options] [environment]" opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v| include_password = true end + + opt.on("--mode [MODE]", ['html', 'list', 'line', 'column'], + "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode| + options['mode'] = mode + end + + opt.on("-h", "--header") do |h| + options['header'] = h + end + opt.parse!(ARGV) abort opt.to_s unless (0..1).include?(ARGV.size) end @@ -60,8 +71,13 @@ when "sqlite" exec(find_cmd('sqlite'), config["database"]) when "sqlite3" - exec(find_cmd('sqlite3'), config["database"]) + args = [] + + args << "-#{options['mode']}" if options['mode'] + args << "-header" if options['header'] + args << config['database'] + exec(find_cmd('sqlite3'), *args) else abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!" end -- cgit v1.2.3 From a0d8202bb9fa55c328a3170836b0cddb40631e83 Mon Sep 17 00:00:00 2001 From: "Filip H.F. \"FiXato\" Slagter" Date: Thu, 30 Oct 2008 11:40:09 +0100 Subject: Ensure whitespaces are stripped when merging string joins. [#1297 state:resolved] Signed-off-by: Pratik Naik --- activerecord/lib/active_record/base.rb | 2 +- activerecord/test/cases/method_scoping_test.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 479296ebae..0efccb66ee 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1715,7 +1715,7 @@ module ActiveRecord #:nodoc: end join end - joins.flatten.uniq + joins.flatten.map{|j| j.strip}.uniq else joins.collect{|j| safe_to_array(j)}.flatten.uniq end diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 80a06116ad..71e2ce8790 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -186,6 +186,16 @@ class MethodScopingTest < ActiveRecord::TestCase assert_equal authors(:david).attributes, scoped_authors.first.attributes end + def test_scoped_find_strips_spaces_from_string_joins_and_eliminates_duplicate_string_joins + scoped_authors = Author.with_scope(:find => { :joins => ' INNER JOIN posts ON posts.author_id = authors.id '}) do + Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN posts ON posts.author_id = authors.id'], :conditions => 'posts.id = 1') + end + assert scoped_authors.include?(authors(:david)) + assert !scoped_authors.include?(authors(:mary)) + assert_equal 1, scoped_authors.size + assert_equal authors(:david).attributes, scoped_authors.first.attributes + end + def test_scoped_count_include # with the include, will retrieve only developers for the given project Developer.with_scope(:find => { :include => :projects }) do -- cgit v1.2.3 From 91eeb0ff119d34d0fcdb44d3d7fcbb7924208e05 Mon Sep 17 00:00:00 2001 From: Dan Weinand Date: Thu, 30 Oct 2008 12:52:12 -0500 Subject: Using the highlight helper on text with html shouldn't highlight text inside html attributes. [#1302 state:resolved] Signed-off-by: Pratik Naik --- actionpack/lib/action_view/helpers/text_helper.rb | 2 +- actionpack/test/template/text_helper_test.rb | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 1d9e4fe9b8..b1eb6891fa 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -107,7 +107,7 @@ module ActionView text else match = Array(phrases).map { |p| Regexp.escape(p) }.join('|') - text.gsub(/(#{match})/i, options[:highlighter]) + text.gsub(/(#{match})(?!(?:[^<]*?)?(?:["'])[^<>]*>)/i, options[:highlighter]) end end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index a6200fbdd7..564845779f 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -122,6 +122,29 @@ class TextHelperTest < ActionView::TestCase ) end + def test_highlight_with_html + assert_equal( + "

    This is a beautiful morning, but also a beautiful day

    ", + highlight("

    This is a beautiful morning, but also a beautiful day

    ", "beautiful") + ) + assert_equal( + "

    This is a beautiful morning, but also a beautiful day

    ", + highlight("

    This is a beautiful morning, but also a beautiful day

    ", "beautiful") + ) + assert_equal( + "

    This is a beautiful morning, but also a beautiful day

    ", + highlight("

    This is a beautiful morning, but also a beautiful day

    ", "beautiful") + ) + assert_equal( + "

    This is a beautiful morning, but also a beautiful day

    ", + highlight("

    This is a beautiful morning, but also a beautiful day

    ", "beautiful") + ) + assert_equal( + "

    This is a beautiful morning, but also a beautiful day

    ", + highlight("

    This is a beautiful morning, but also a beautiful day

    ", "beautiful") + ) + end + def test_excerpt assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", 5)) assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5)) -- cgit v1.2.3 From 166c63818e901e64786a76029febf7c9cdb40f2d Mon Sep 17 00:00:00 2001 From: Bob Aman Date: Wed, 28 Jan 2009 20:56:02 +0000 Subject: Improve exception handling when Location header is invalid. [#1192 state:resolved] Signed-off-by: Pratik Naik --- activeresource/lib/active_resource/base.rb | 2 +- activeresource/test/base_test.rb | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 54dde43087..94418fb559 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -1006,7 +1006,7 @@ module ActiveResource # Takes a response from a typical create post and pulls the ID out def id_from_response(response) - response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1] + response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1] if response['Location'] end def element_path(options = nil) diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb index d37a6d4ed2..e22388f4a7 100644 --- a/activeresource/test/base_test.rb +++ b/activeresource/test/base_test.rb @@ -627,6 +627,12 @@ class BaseTest < Test::Unit::TestCase assert_equal '1', p.__send__(:id_from_response, resp) end + def test_id_from_response_without_location + p = Person.new + resp = {} + assert_equal nil, p.__send__(:id_from_response, resp) + end + def test_create_with_custom_prefix matzs_house = StreetAddress.new(:person_id => 1) matzs_house.save @@ -670,7 +676,6 @@ class BaseTest < Test::Unit::TestCase assert_equal person, person.reload end - def test_create rick = Person.create(:name => 'Rick') assert rick.valid? @@ -687,6 +692,14 @@ class BaseTest < Test::Unit::TestCase assert_raises(ActiveResource::ResourceConflict) { Person.create(:name => 'Rick') } end + def test_create_without_location + ActiveResource::HttpMock.respond_to do |mock| + mock.post "/people.xml", {}, nil, 201 + end + person = Person.create(:name => 'Rick') + assert_equal nil, person.id + end + def test_clone matz = Person.find(1) matz_c = matz.clone -- cgit v1.2.3 From a1a9889b06faf5e994fc29d462c7063b5ff37a3a Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 29 Jan 2009 01:59:55 +0000 Subject: Move basic auth test controller inside the test class --- .../controller/http_basic_authentication_test.rb | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index 08a25bfdb8..fbc94a0df7 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -1,35 +1,35 @@ require 'abstract_unit' -class DummyController < ActionController::Base - before_filter :authenticate, :only => :index - before_filter :authenticate_with_request, :only => :display +class HttpBasicAuthenticationTest < ActionController::TestCase + class DummyController < ActionController::Base + before_filter :authenticate, :only => :index + before_filter :authenticate_with_request, :only => :display - def index - render :text => "Hello Secret" - end + def index + render :text => "Hello Secret" + end - def display - render :text => 'Definitely Maybe' - end + def display + render :text => 'Definitely Maybe' + end - private + private - def authenticate - authenticate_or_request_with_http_basic do |username, password| - username == 'lifo' && password == 'world' + def authenticate + authenticate_or_request_with_http_basic do |username, password| + username == 'lifo' && password == 'world' + end end - end - def authenticate_with_request - if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' } - @logged_in = true - else - request_http_basic_authentication("SuperSecret") + def authenticate_with_request + if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' } + @logged_in = true + else + request_http_basic_authentication("SuperSecret") + end end end -end -class HttpBasicAuthenticationTest < ActionController::TestCase AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'] tests DummyController -- cgit v1.2.3 From 2e69db18ce2815c25eee64fc2978e7f6b57f6e1f Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 28 Jan 2009 21:20:46 -0600 Subject: Only dup local cache values if duplicable [#1653 state:resolved] --- .../lib/active_support/cache/strategy/local_cache.rb | 2 +- activesupport/test/caching_test.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index 621358d701..d83e259a2a 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -41,7 +41,7 @@ module ActiveSupport value else # forcing the value to be immutable - value.dup + value.duplicable? ? value.dup : value end end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index e8e0b41d4d..4e212f1661 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -81,6 +81,11 @@ module CacheStoreBehavior assert_equal({:a => "b"}, @cache.read('foo')) end + def test_should_read_and_write_integer + @cache.write('foo', 1) + assert_equal 1, @cache.read('foo') + end + def test_should_read_and_write_nil @cache.write('foo', nil) assert_equal nil, @cache.read('foo') @@ -200,6 +205,13 @@ uses_memcached 'memcached backed store' do end end + def test_local_cache_should_read_and_write_integer + @cache.with_local_cache do + @cache.write('foo', 1) + assert_equal 1, @cache.read('foo') + end + end + def test_local_cache_of_delete @cache.with_local_cache do @cache.write('foo', 'bar') -- cgit v1.2.3 From feed7b4cfd860bdc9f5b4efc2ca2af76cb01d55d Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Thu, 29 Jan 2009 17:07:29 +1300 Subject: Move to use pg instead of postgres on the CI server --- ci/geminstaller.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/geminstaller.yml b/ci/geminstaller.yml index 4251518999..d11bf04485 100644 --- a/ci/geminstaller.yml +++ b/ci/geminstaller.yml @@ -11,8 +11,8 @@ gems: - name: mysql #version: >= 2.7 version: = 2.7 -- name: postgres - version: >= 0.7.9.2008.01.28 +- name: pg + version: >= 0.7.9.2008.10.13 - name: rack version: '~> 0.9.0' - name: rake -- cgit v1.2.3 From e6493eb9b76de73afef2706493efd090dfff4ecc Mon Sep 17 00:00:00 2001 From: Jacob Dunphy Date: Thu, 15 Jan 2009 13:30:44 -0800 Subject: Sqlite adapter's copy_table incorrectly attempts to recreate a primary key id (:id => true in the create_table) if an :id column is present, even if it isn't a primary_key. This fix sets :id => false if there is an :id column, but it's not the primary_key. Signed-off-by: Michael Koziarski [#1766 state:committed] --- .../lib/active_record/connection_adapters/sqlite_adapter.rb | 2 +- activerecord/test/cases/copy_table_test_sqlite.rb | 11 +++++++++++ activerecord/test/schema/schema.rb | 5 +++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 9387cf8827..5390f49f04 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -306,7 +306,7 @@ module ActiveRecord end def copy_table(from, to, options = {}) #:nodoc: - options = options.merge(:id => !columns(from).detect{|c| c.name == 'id'}.nil?) + options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s)) create_table(to, options) do |definition| @definition = definition columns(from).each do |column| diff --git a/activerecord/test/cases/copy_table_test_sqlite.rb b/activerecord/test/cases/copy_table_test_sqlite.rb index f0cfb67866..72bd7e2dab 100644 --- a/activerecord/test/cases/copy_table_test_sqlite.rb +++ b/activerecord/test/cases/copy_table_test_sqlite.rb @@ -46,6 +46,17 @@ class CopyTableTest < ActiveRecord::TestCase test_copy_table('developers_projects', 'programmers_projects') end + def test_copy_table_with_id_col_that_is_not_primary_key + test_copy_table('goofy_string_id', 'goofy_string_id2') do |from, to, options| + original_id = @connection.columns('goofy_string_id').detect{|col| col.name == 'id' } + copied_id = @connection.columns('goofy_string_id2').detect{|col| col.name == 'id' } + assert_equal original_id.type, copied_id.type + assert_equal original_id.sql_type, copied_id.sql_type + assert_equal original_id.limit, copied_id.limit + assert_equal original_id.primary, copied_id.primary + end + end + protected def copy_table(from, to, options = {}) @connection.copy_table(from, to, {:temporary => true}.merge(options)) diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 094932d375..d44faf04cc 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -154,6 +154,11 @@ ActiveRecord::Schema.define do t.string :name end + create_table :goofy_string_id, :force => true, :id => false do |t| + t.string :id, :null => false + t.string :info + end + create_table :items, :force => true do |t| t.column :name, :integer end -- cgit v1.2.3 From 306cc2b920203cfa51cee82d2fc452484efc72f8 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 29 Jan 2009 16:00:07 +0000 Subject: Implement HTTP Digest authentication. [#1230 state:resolved] [Gregg Kellogg, Pratik Naik] Signed-off-by: Pratik Naik --- actionpack/CHANGELOG | 21 +++ actionpack/lib/action_controller/base.rb | 4 +- .../lib/action_controller/http_authentication.rb | 196 ++++++++++++++++++--- .../controller/http_digest_authentication_test.rb | 130 ++++++++++++++ 4 files changed, 326 insertions(+), 25 deletions(-) create mode 100644 actionpack/test/controller/http_digest_authentication_test.rb diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 779918ae89..6bcb595cdb 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,26 @@ *2.3.0 [Edge]* +* Implement HTTP Digest authentication. #1230 [Gregg Kellogg, Pratik Naik] Example : + + class DummyDigestController < ActionController::Base + USERS = { "lifo" => 'world' } + + before_filter :authenticate + + def index + render :text => "Hello Secret" + end + + private + + def authenticate + authenticate_or_request_with_http_digest("Super Secret") do |username| + # Return the user's password + USERS[username] + end + end + end + * Improved i18n support for the number_to_human_size helper. Changes the storage_units translation data; update your translations accordingly. #1634 [Yaroslav Markin] storage_units: # %u is the storage unit, %n is the number (default: 2 MB) diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 9f418bb2f3..36b80d5780 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1344,8 +1344,8 @@ module ActionController #:nodoc: Base.class_eval do [ Filters, Layout, Benchmarking, Rescue, Flash, MimeResponds, Helpers, Cookies, Caching, Verification, Streaming, SessionManagement, - HttpAuthentication::Basic::ControllerMethods, RecordIdentifier, - RequestForgeryProtection, Translation + HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods, + RecordIdentifier, RequestForgeryProtection, Translation ].each do |mod| include mod end diff --git a/actionpack/lib/action_controller/http_authentication.rb b/actionpack/lib/action_controller/http_authentication.rb index 2ed810db7d..c91ef2ca48 100644 --- a/actionpack/lib/action_controller/http_authentication.rb +++ b/actionpack/lib/action_controller/http_authentication.rb @@ -1,42 +1,42 @@ module ActionController module HttpAuthentication # Makes it dead easy to do HTTP Basic authentication. - # + # # Simple Basic example: - # + # # class PostsController < ApplicationController # USER_NAME, PASSWORD = "dhh", "secret" - # + # # before_filter :authenticate, :except => [ :index ] - # + # # def index # render :text => "Everyone can see me!" # end - # + # # def edit # render :text => "I'm only accessible if you know the password" # end - # + # # private # def authenticate - # authenticate_or_request_with_http_basic do |user_name, password| + # authenticate_or_request_with_http_basic do |user_name, password| # user_name == USER_NAME && password == PASSWORD # end # end # end - # - # - # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication, + # + # + # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication, # the regular HTML interface is protected by a session approach: - # + # # class ApplicationController < ActionController::Base # before_filter :set_account, :authenticate - # + # # protected # def set_account # @account = Account.find_by_url_name(request.subdomains.first) # end - # + # # def authenticate # case request.format # when Mime::XML, Mime::ATOM @@ -54,24 +54,48 @@ module ActionController # end # end # end - # - # + # # In your integration tests, you can do something like this: - # + # # def test_access_granted_from_xml # get( - # "/notes/1.xml", nil, + # "/notes/1.xml", nil, # :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) # ) - # + # # assert_equal 200, status # end - # - # + # + # Simple Digest example: + # + # class PostsController < ApplicationController + # USERS = {"dhh" => "secret"} + # + # before_filter :authenticate, :except => [:index] + # + # def index + # render :text => "Everyone can see me!" + # end + # + # def edit + # render :text => "I'm only accessible if you know the password" + # end + # + # private + # def authenticate + # authenticate_or_request_with_http_digest(realm) do |username| + # USERS[username] + # end + # end + # end + # + # NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password so the framework can appropriately + # hash it to check the user's credentials. Returning +nil+ will cause authentication to fail. + # # On shared hosts, Apache sometimes doesn't pass authentication headers to # FCGI instances. If your environment matches this description and you cannot # authenticate, try this rule in your Apache setup: - # + # # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] module Basic extend self @@ -99,14 +123,14 @@ module ActionController def user_name_and_password(request) decode_credentials(request).split(/:/, 2) end - + def authorization(request) request.env['HTTP_AUTHORIZATION'] || request.env['X-HTTP_AUTHORIZATION'] || request.env['X_HTTP_AUTHORIZATION'] || request.env['REDIRECT_X_HTTP_AUTHORIZATION'] end - + def decode_credentials(request) ActiveSupport::Base64.decode64(authorization(request).split.last || '') end @@ -120,5 +144,131 @@ module ActionController controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized end end + + module Digest + extend self + + module ControllerMethods + def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure) + authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm) + end + + # Authenticate with HTTP Digest, returns true or false + def authenticate_with_http_digest(realm = "Application", &password_procedure) + HttpAuthentication::Digest.authenticate(self, realm, &password_procedure) + end + + # Render output including the HTTP Digest authentication header + def request_http_digest_authentication(realm = "Application", message = nil) + HttpAuthentication::Digest.authentication_request(self, realm, message) + end + end + + # Returns false on a valid response, true otherwise + def authenticate(controller, realm, &password_procedure) + authorization(controller.request) && validate_digest_response(controller, realm, &password_procedure) + end + + def authorization(request) + request.env['HTTP_AUTHORIZATION'] || + request.env['X-HTTP_AUTHORIZATION'] || + request.env['X_HTTP_AUTHORIZATION'] || + request.env['REDIRECT_X_HTTP_AUTHORIZATION'] + end + + # Raises error unless the request credentials response value matches the expected value. + def validate_digest_response(controller, realm, &password_procedure) + credentials = decode_credentials_header(controller.request) + valid_nonce = validate_nonce(controller.request, credentials[:nonce]) + + if valid_nonce && realm == credentials[:realm] && opaque(controller.request.session.session_id) == credentials[:opaque] + password = password_procedure.call(credentials[:username]) + expected = expected_response(controller.request.env['REQUEST_METHOD'], controller.request.url, credentials, password) + expected == credentials[:response] + end + end + + # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+ + def expected_response(http_method, uri, credentials, password) + ha1 = ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':')) + ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':')) + ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':')) + end + + def encode_credentials(http_method, credentials, password) + credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password) + "Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ') + end + + def decode_credentials_header(request) + decode_credentials(authorization(request)) + end + + def decode_credentials(header) + header.to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair| + key, value = pair.split('=', 2) + hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '') + hash + end + end + + def authentication_header(controller, realm) + session_id = controller.request.session.session_id + controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(session_id)}", opaque="#{opaque(session_id)}") + end + + def authentication_request(controller, realm, message = nil) + message ||= "HTTP Digest: Access denied.\n" + authentication_header(controller, realm) + controller.__send__ :render, :text => message, :status => :unauthorized + end + + # Uses an MD5 digest based on time to generate a value to be used only once. + # + # A server-specified data string which should be uniquely generated each time a 401 response is made. + # It is recommended that this string be base64 or hexadecimal data. + # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed. + # + # The contents of the nonce are implementation dependent. + # The quality of the implementation depends on a good choice. + # A nonce might, for example, be constructed as the base 64 encoding of + # + # => time-stamp H(time-stamp ":" ETag ":" private-key) + # + # where time-stamp is a server-generated time or other non-repeating value, + # ETag is the value of the HTTP ETag header associated with the requested entity, + # and private-key is data known only to the server. + # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and + # reject the request if it did not match the nonce from that header or + # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity. + # The inclusion of the ETag prevents a replay request for an updated version of the resource. + # (Note: including the IP address of the client in the nonce would appear to offer the server the ability + # to limit the reuse of the nonce to the same client that originally got it. + # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm. + # Also, IP address spoofing is not that hard.) + # + # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to + # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for + # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4 + # of this document. + # + # The nonce is opaque to the client. + def nonce(session_id, time = Time.now) + t = time.to_i + hashed = [t, session_id] + digest = ::Digest::MD5.hexdigest(hashed.join(":")) + Base64.encode64("#{t}:#{digest}").gsub("\n", '') + end + + def validate_nonce(request, value) + t = Base64.decode64(value).split(":").first.to_i + nonce(request.session.session_id, t) == value && (t - Time.now.to_i).abs <= 10 * 60 + end + + # Opaque based on digest of session_id + def opaque(session_id) + Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub("\n", '') + end + end end end diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb new file mode 100644 index 0000000000..59f7a403b5 --- /dev/null +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -0,0 +1,130 @@ +require 'abstract_unit' + +class HttpDigestAuthenticationTest < ActionController::TestCase + class DummyDigestController < ActionController::Base + before_filter :authenticate, :only => :index + before_filter :authenticate_with_request, :only => :display + + USERS = { 'lifo' => 'world', 'pretty' => 'please' } + + def index + render :text => "Hello Secret" + end + + def display + render :text => 'Definitely Maybe' + end + + private + + def authenticate + authenticate_or_request_with_http_digest("SuperSecret") do |username| + # Return the password + USERS[username] + end + end + + def authenticate_with_request + if authenticate_with_http_digest("SuperSecret") { |username| USERS[username] } + @logged_in = true + else + request_http_digest_authentication("SuperSecret", "Authentication Failed") + end + end + end + + AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'] + + tests DummyDigestController + + AUTH_HEADERS.each do |header| + test "successful authentication with #{header.downcase}" do + @request.env[header] = encode_credentials(:username => 'lifo', :password => 'world') + get :index + + assert_response :success + assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}" + end + end + + AUTH_HEADERS.each do |header| + test "unsuccessful authentication with #{header.downcase}" do + @request.env[header] = encode_credentials(:username => 'h4x0r', :password => 'world') + get :index + + assert_response :unauthorized + assert_equal "HTTP Digest: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}" + end + end + + test "authentication request without credential" do + get :display + + assert_response :unauthorized + assert_equal "Authentication Failed", @response.body + credentials = decode_credentials(@response.headers['WWW-Authenticate']) + assert_equal 'SuperSecret', credentials[:realm] + end + + test "authentication request with invalid password" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo') + get :display + + assert_response :unauthorized + assert_equal "Authentication Failed", @response.body + end + + test "authentication request with invalid nonce" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please', :nonce => "xxyyzz") + get :display + + assert_response :unauthorized + assert_equal "Authentication Failed", @response.body + end + + test "authentication request with invalid opaque" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :opaque => "xxyyzz") + get :display + + assert_response :unauthorized + assert_equal "Authentication Failed", @response.body + end + + test "authentication request with invalid realm" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :realm => "NotSecret") + get :display + + assert_response :unauthorized + assert_equal "Authentication Failed", @response.body + end + + test "authentication request with valid credential" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + get :display + + assert_response :success + assert assigns(:logged_in) + assert_equal 'Definitely Maybe', @response.body + end + + private + + def encode_credentials(options) + options.reverse_merge!(:nc => "00000001", :cnonce => "0a4f113b") + password = options.delete(:password) + + # Perform unautheticated get to retrieve digest parameters to use on subsequent request + get :index + + assert_response :unauthorized + + credentials = decode_credentials(@response.headers['WWW-Authenticate']) + credentials.merge!(options) + credentials.merge!(:uri => "http://#{@request.host}#{@request.env['REQUEST_URI']}") + ActionController::HttpAuthentication::Digest.encode_credentials("GET", credentials, password) + end + + def decode_credentials(header) + ActionController::HttpAuthentication::Digest.decode_credentials(@response.headers['WWW-Authenticate']) + end +end \ No newline at end of file -- cgit v1.2.3 From 6932ae4b2978de6771e6d1c84cfc3595cf9d8bab Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 29 Jan 2009 16:12:10 +0000 Subject: Add Digest authentication --- .../doc/guides/html/actioncontroller_basics.html | 52 ++++++++++++++++++++-- .../source/actioncontroller_basics/http_auth.txt | 39 ++++++++++++++-- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/railties/doc/guides/html/actioncontroller_basics.html b/railties/doc/guides/html/actioncontroller_basics.html index f5b25a4d7a..201a2c62f0 100644 --- a/railties/doc/guides/html/actioncontroller_basics.html +++ b/railties/doc/guides/html/actioncontroller_basics.html @@ -88,7 +88,14 @@
  • - HTTP Basic Authentication + HTTP Authentications +
  • Streaming and File Downloads @@ -803,9 +810,23 @@ http://www.lorenzobettini.it http://www.gnu.org/software/src-highlite -->
    response.headers["Content-Type"] = "application/pdf"
    -

    10. HTTP Basic Authentication

    +

    10. HTTP Authentications

    -

    Rails comes with built-in HTTP Basic authentication. This is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser’s HTTP Basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, authenticate_or_request_with_http_basic.

    +

    Rails comes with two built-in HTTP authentication mechanisms :

    +
      +
    • +

      +Basic Authentication +

      +
    • +
    • +

      +Digest Authentication +

      +
    • +
    +

    10.1. HTTP Basic Authentication

    +

    HTTP Basic authentication is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser’s HTTP Basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, authenticate_or_request_with_http_basic.

    before_filter :authenticate -private + private def authenticate authenticate_or_request_with_http_basic do |username, password| @@ -827,6 +848,29 @@ private end

    With this in place, you can create namespaced controllers that inherit from AdminController. The before filter will thus be run for all actions in those controllers, protecting them with HTTP Basic authentication.

    +

    10.2. HTTP Digest Authentication

    +

    HTTP Digest authentication is superior to the Basic authentication as it does not require the client to send unencrypted password over the network. Using Digest authentication with Rails is quite easy and only requires using one method, authenticate_or_request_with_http_digest.

    +
    +
    +
    class AdminController < ApplicationController
    +
    +  USERS = { "lifo" => "world" }
    +
    +  before_filter :authenticate
    +
    +  private
    +
    +  def authenticate
    +    authenticate_or_request_with_http_digest do |username|
    +      USERS[username]
    +    end
    +  end
    +
    +end
    +

    As seen in the example above, authenticate_or_request_with_http_digest block takes only one argument - the username. And the block returns the password. Returning false or nil from the authenticate_or_request_with_http_digest will cause authentication failure.

    11. Streaming and File Downloads

    diff --git a/railties/doc/guides/source/actioncontroller_basics/http_auth.txt b/railties/doc/guides/source/actioncontroller_basics/http_auth.txt index 8deb40c2c9..63e7c0f061 100644 --- a/railties/doc/guides/source/actioncontroller_basics/http_auth.txt +++ b/railties/doc/guides/source/actioncontroller_basics/http_auth.txt @@ -1,6 +1,13 @@ -== HTTP Basic Authentication == +== HTTP Authentications == -Rails comes with built-in HTTP Basic authentication. This is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP Basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, `authenticate_or_request_with_http_basic`. +Rails comes with two built-in HTTP authentication mechanisms : + + * Basic Authentication + * Digest Authentication + +=== HTTP Basic Authentication === + +HTTP Basic authentication is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser's HTTP Basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, `authenticate_or_request_with_http_basic`. [source, ruby] ------------------------------------- @@ -10,7 +17,7 @@ class AdminController < ApplicationController before_filter :authenticate -private + private def authenticate authenticate_or_request_with_http_basic do |username, password| @@ -22,3 +29,29 @@ end ------------------------------------- With this in place, you can create namespaced controllers that inherit from AdminController. The before filter will thus be run for all actions in those controllers, protecting them with HTTP Basic authentication. + +=== HTTP Digest Authentication === + +HTTP Digest authentication is superior to the Basic authentication as it does not require the client to send unencrypted password over the network. Using Digest authentication with Rails is quite easy and only requires using one method, +authenticate_or_request_with_http_digest+. + +[source, ruby] +------------------------------------- +class AdminController < ApplicationController + + USERS = { "lifo" => "world" } + + before_filter :authenticate + + private + + def authenticate + authenticate_or_request_with_http_digest do |username| + USERS[username] + end + end + +end +------------------------------------- + + +As seen in the example above, +authenticate_or_request_with_http_digest+ block takes only one argument - the username. And the block returns the password. Returning +false+ or +nil+ from the +authenticate_or_request_with_http_digest+ will cause authentication failure. -- cgit v1.2.3 From 8761663a68bd7ddd918f78fb3def4697784024f2 Mon Sep 17 00:00:00 2001 From: Jon Crawford Date: Thu, 29 Jan 2009 17:59:44 +0000 Subject: Added grouped_options_for_select helper method for wrapping option tags in optgroups. [#977 state:resolved] Signed-off-by: Pratik Naik --- actionpack/CHANGELOG | 2 + .../lib/action_view/helpers/form_options_helper.rb | 56 ++++++++++++++++++++++ .../test/template/form_options_helper_test.rb | 26 ++++++++++ 3 files changed, 84 insertions(+) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 6bcb595cdb..e9e18a8f6b 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* Added grouped_options_for_select helper method for wrapping option tags in optgroups. #977 [Jon Crawford] + * Implement HTTP Digest authentication. #1230 [Gregg Kellogg, Pratik Naik] Example : class DummyDigestController < ActionController::Base diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 3991c5c0cc..54c82cbd1d 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -277,6 +277,62 @@ module ActionView end end + # Returns a string of tags, like options_for_select, but + # wraps them with tags. + # + # Parameters: + # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the + # label while the second value must be an array of options. The second value can be a + # nested array of text-value pairs. See options_for_select for more info. + # Ex. ["North America",[["United States","US"],["Canada","CA"]]] + # * +selected_key+ - A value equal to the +value+ attribute for one of the tags, + # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options + # as you might have the same option in multiple groups. Each will then get selected="selected". + # * +prompt+ - set to true or a prompt string. When the select element doesn’t have a value yet, this + # prepends an option with a generic prompt — "Please select" — or the given prompt string. + # + # Sample usage (Array): + # grouped_options = [ + # ['North America', + # [['United States','US'],'Canada']], + # ['Europe', + # ['Denmark','Germany','France']] + # ] + # grouped_options_for_select(grouped_options) + # + # Sample usage (Hash): + # grouped_options = { + # 'North America' => [['United States','US], 'Canada'], + # 'Europe' => ['Denmark','Germany','France'] + # } + # grouped_options_for_select(grouped_options) + # + # Possible output: + # + # + # + # + # + # + # + # + # + # + # Note: Only the and tags are returned, so you still have to + # wrap the output in an appropriate