From 92f49b5f1ebf42514c58e1fda87c0b8a1b33d08f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 16 Jan 2010 13:17:03 +0100 Subject: [PATCH 01/82] Split ActionDispatch http in smaller chunks. --- actionpack/lib/action_dispatch.rb | 10 +- actionpack/lib/action_dispatch/http/cache.rb | 123 ++++++ .../action_dispatch/http/mime_negotiation.rb | 101 +++++ .../lib/action_dispatch/http/parameters.rb | 50 +++ .../lib/action_dispatch/http/request.rb | 379 ++---------------- .../lib/action_dispatch/http/response.rb | 80 +--- actionpack/lib/action_dispatch/http/upload.rb | 48 +++ actionpack/lib/action_dispatch/http/url.rb | 129 ++++++ 8 files changed, 489 insertions(+), 431 deletions(-) create mode 100644 actionpack/lib/action_dispatch/http/cache.rb create mode 100644 actionpack/lib/action_dispatch/http/mime_negotiation.rb create mode 100644 actionpack/lib/action_dispatch/http/parameters.rb create mode 100644 actionpack/lib/action_dispatch/http/upload.rb create mode 100644 actionpack/lib/action_dispatch/http/url.rb diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 71bb67d3ffebb..7b4421231078b 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -55,7 +55,15 @@ module ActionDispatch autoload :Routing module Http - autoload :Headers, 'action_dispatch/http/headers' + extend ActiveSupport::Autoload + + autoload :Cache + autoload :Headers + autoload :MimeNegotiation + autoload :Parameters + autoload :Upload + autoload :UploadedFile, 'action_dispatch/http/upload' + autoload :URL end module Session diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb new file mode 100644 index 0000000000000..428e62dc6bc97 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -0,0 +1,123 @@ +module ActionDispatch + module Http + module Cache + module Request + def if_modified_since + if since = env['HTTP_IF_MODIFIED_SINCE'] + Time.rfc2822(since) rescue nil + end + end + + def if_none_match + env['HTTP_IF_NONE_MATCH'] + end + + def not_modified?(modified_at) + if_modified_since && modified_at && if_modified_since >= modified_at + end + + def etag_matches?(etag) + if_none_match && if_none_match == etag + end + + # Check response freshness (Last-Modified and ETag) against request + # If-Modified-Since and If-None-Match conditions. If both headers are + # supplied, both must match, or the request is not considered fresh. + def fresh?(response) + last_modified = if_modified_since + etag = if_none_match + + return false unless last_modified || etag + + success = true + success &&= not_modified?(response.last_modified) if last_modified + success &&= etag_matches?(response.etag) if etag + success + end + end + + module Response + def cache_control + @cache_control ||= {} + end + + def last_modified + if last = headers['Last-Modified'] + Time.httpdate(last) + end + end + + def last_modified? + headers.include?('Last-Modified') + end + + def last_modified=(utc_time) + headers['Last-Modified'] = utc_time.httpdate + end + + def etag + @etag + end + + def etag? + @etag + end + + def etag=(etag) + key = ActiveSupport::Cache.expand_cache_key(etag) + @etag = %("#{Digest::MD5.hexdigest(key)}") + end + + private + + def handle_conditional_get! + if etag? || last_modified? || !@cache_control.empty? + set_conditional_cache_control! + elsif nonempty_ok_response? + self.etag = @body + + if request && request.etag_matches?(etag) + self.status = 304 + self.body = [] + end + + set_conditional_cache_control! + else + headers["Cache-Control"] = "no-cache" + end + end + + def nonempty_ok_response? + @status == 200 && string_body? + end + + def string_body? + !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) } + end + + DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" + + def set_conditional_cache_control! + control = @cache_control + + if control.empty? + headers["Cache-Control"] = DEFAULT_CACHE_CONTROL + elsif @cache_control[:no_cache] + headers["Cache-Control"] = "no-cache" + else + extras = control[:extras] + max_age = control[:max_age] + + options = [] + options << "max-age=#{max_age.to_i}" if max_age + options << (control[:public] ? "public" : "private") + options << "must-revalidate" if control[:must_revalidate] + options.concat(extras) if extras + + headers["Cache-Control"] = options.join(", ") + end + end + end + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb new file mode 100644 index 0000000000000..40617e239ac30 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -0,0 +1,101 @@ +module ActionDispatch + module Http + module MimeNegotiation + # The MIME type of the HTTP request, such as Mime::XML. + # + # For backward compatibility, the post \format is extracted from the + # X-Post-Data-Format HTTP header if present. + def content_type + @env["action_dispatch.request.content_type"] ||= begin + if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/ + Mime::Type.lookup($1.strip.downcase) + else + nil + end + end + end + + # Returns the accepted MIME type for the request. + def accepts + @env["action_dispatch.request.accepts"] ||= begin + header = @env['HTTP_ACCEPT'].to_s.strip + + if header.empty? + [content_type] + else + Mime::Type.parse(header) + end + end + end + + # Returns the Mime type for the \format used in the request. + # + # GET /posts/5.xml | request.format => Mime::XML + # GET /posts/5.xhtml | request.format => Mime::HTML + # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of ActionController::Base.use_accept_header + # + def format(view_path = []) + formats.first + end + + def formats + accept = @env['HTTP_ACCEPT'] + + @env["action_dispatch.request.formats"] ||= + if parameters[:format] + Array(Mime[parameters[:format]]) + elsif xhr? || (accept && !accept.include?(?,)) + accepts + else + [Mime::HTML] + end + end + + # Sets the \format by string extension, which can be used to force custom formats + # that are not controlled by the extension. + # + # class ApplicationController < ActionController::Base + # before_filter :adjust_format_for_iphone + # + # private + # def adjust_format_for_iphone + # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] + # end + # end + def format=(extension) + parameters[:format] = extension.to_s + @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])] + end + + # Returns a symbolized version of the :format parameter of the request. + # If no \format is given it returns :jsfor Ajax requests and :html + # otherwise. + def template_format + parameter_format = parameters[:format] + + if parameter_format + parameter_format + elsif xhr? + :js + else + :html + end + end + + # Receives an array of mimes and return the first user sent mime that + # matches the order array. + # + def negotiate_mime(order) + formats.each do |priority| + if priority == Mime::ALL + return order.first + elsif order.include?(priority) + return priority + end + end + + order.include?(Mime::ALL) ? formats.first : nil + end + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb new file mode 100644 index 0000000000000..97546d5f93b4b --- /dev/null +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -0,0 +1,50 @@ +require 'active_support/core_ext/hash/keys' + +module ActionDispatch + module Http + module Parameters + # Returns both GET and POST \parameters in a single hash. + def parameters + @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access + end + alias :params :parameters + + def path_parameters=(parameters) #:nodoc: + @env.delete("action_dispatch.request.symbolized_path_parameters") + @env.delete("action_dispatch.request.parameters") + @env["action_dispatch.request.path_parameters"] = parameters + end + + # The same as path_parameters with explicitly symbolized keys. + def symbolized_path_parameters + @env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys + end + + # Returns a hash with the \parameters used to form the \path of the request. + # Returned hash keys are strings: + # + # {'action' => 'my_action', 'controller' => 'my_controller'} + # + # See symbolized_path_parameters for symbolized keys. + def path_parameters + @env["action_dispatch.request.path_parameters"] ||= {} + end + + private + + # Convert nested Hashs to HashWithIndifferentAccess + def normalize_parameters(value) + case value + when Hash + h = {} + value.each { |k, v| h[k] = normalize_parameters(v) } + h.with_indifferent_access + when Array + value.map { |e| normalize_parameters(e) } + else + value + end + end + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 22a08ec10de10..187ce7c15d411 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -2,14 +2,17 @@ require 'stringio' require 'strscan' -require 'active_support/memoizable' -require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/string/access' require 'action_dispatch/http/headers' module ActionDispatch class Request < Rack::Request + include ActionDispatch::Http::Cache::Request + include ActionDispatch::Http::MimeNegotiation + include ActionDispatch::Http::Parameters + include ActionDispatch::Http::Upload + include ActionDispatch::Http::URL %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_TRANSLATED REMOTE_HOST @@ -19,9 +22,11 @@ class Request < Rack::Request HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_NEGOTIATE HTTP_PRAGMA ].each do |env| - define_method(env.sub(/^HTTP_/n, '').downcase) do - @env[env] - end + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{env.sub(/^HTTP_/n, '').downcase} + @env["#{env}"] + end + METHOD end def key?(key) @@ -81,25 +86,6 @@ def headers Http::Headers.new(@env) end - # Returns the content length of the request as an integer. - def content_length - super.to_i - end - - # The MIME type of the HTTP request, such as Mime::XML. - # - # For backward compatibility, the post \format is extracted from the - # X-Post-Data-Format HTTP header if present. - def content_type - @env["action_dispatch.request.content_type"] ||= begin - if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/ - Mime::Type.lookup($1.strip.downcase) - else - nil - end - end - end - def forgery_whitelisted? method == :get || xhr? || content_type.nil? || !content_type.verify_request? end @@ -108,104 +94,9 @@ def media_type content_type.to_s end - # Returns the accepted MIME type for the request. - def accepts - @env["action_dispatch.request.accepts"] ||= begin - header = @env['HTTP_ACCEPT'].to_s.strip - - if header.empty? - [content_type] - else - Mime::Type.parse(header) - end - end - end - - def if_modified_since - if since = env['HTTP_IF_MODIFIED_SINCE'] - Time.rfc2822(since) rescue nil - end - end - - def if_none_match - env['HTTP_IF_NONE_MATCH'] - end - - def not_modified?(modified_at) - if_modified_since && modified_at && if_modified_since >= modified_at - end - - def etag_matches?(etag) - if_none_match && if_none_match == etag - end - - # Check response freshness (Last-Modified and ETag) against request - # If-Modified-Since and If-None-Match conditions. If both headers are - # supplied, both must match, or the request is not considered fresh. - def fresh?(response) - last_modified = if_modified_since - etag = if_none_match - - return false unless last_modified || etag - - success = true - success &&= not_modified?(response.last_modified) if last_modified - success &&= etag_matches?(response.etag) if etag - success - end - - # Returns the Mime type for the \format used in the request. - # - # GET /posts/5.xml | request.format => Mime::XML - # GET /posts/5.xhtml | request.format => Mime::HTML - # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of ActionController::Base.use_accept_header - # - def format(view_path = []) - formats.first - end - - def formats - accept = @env['HTTP_ACCEPT'] - - @env["action_dispatch.request.formats"] ||= - if parameters[:format] - Array.wrap(Mime[parameters[:format]]) - elsif xhr? || (accept && !accept.include?(?,)) - accepts - else - [Mime::HTML] - end - end - - # Sets the \format by string extension, which can be used to force custom formats - # that are not controlled by the extension. - # - # class ApplicationController < ActionController::Base - # before_filter :adjust_format_for_iphone - # - # private - # def adjust_format_for_iphone - # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] - # end - # end - def format=(extension) - parameters[:format] = extension.to_s - @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])] - end - - # Returns a symbolized version of the :format parameter of the request. - # If no \format is given it returns :jsfor Ajax requests and :html - # otherwise. - def template_format - parameter_format = parameters[:format] - - if parameter_format - parameter_format - elsif xhr? - :js - else - :html - end + # Returns the content length of the request as an integer. + def content_length + super.to_i end # Returns true if the request's "X-Requested-With" header contains @@ -238,7 +129,7 @@ def remote_ip if @env.include? 'HTTP_CLIENT_IP' if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP']) # We don't know which came from the proxy, and which from the user - raise ActionController::ActionControllerError.new(<tld_length, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk". - def domain(tld_length = 1) - return nil unless named_host?(host) - - host.split('.').last(1 + tld_length).join('.') - end - - # Returns all the \subdomains as an array, so ["dev", "www"] would be - # returned for "dev.www.rubyonrails.org". You can specify a different tld_length, - # such as 2 to catch ["www"] instead of ["www", "rubyonrails"] - # in "www.rubyonrails.co.uk". - def subdomains(tld_length = 1) - return [] unless named_host?(host) - parts = host.split('.') - parts[0..-(tld_length+2)] - end - - # Returns the query string, accounting for server idiosyncrasies. - def query_string - @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '') - end - - # Returns the request URI, accounting for server idiosyncrasies. - # WEBrick includes the full URL. IIS leaves REQUEST_URI blank. - def request_uri - if uri = @env['REQUEST_URI'] - # Remove domain, which webrick puts into the request_uri. - (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri - else - # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO. - uri = @env['PATH_INFO'].to_s - - if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$}) - uri = uri.sub(/#{script_filename}\//, '') - end - - env_qs = @env['QUERY_STRING'].to_s - uri += "?#{env_qs}" unless env_qs.empty? - - if uri.blank? - @env.delete('REQUEST_URI') - else - @env['REQUEST_URI'] = uri - end - end - end - - # Returns the interpreted \path to requested resource after all the installation - # directory of this application was taken into account. - def path - path = request_uri.to_s[/\A[^\?]*/] - path.sub!(/\A#{ActionController::Base.relative_url_root}/, '') - path - end - # Read the request \body. This is useful for web services that need to # work with raw requests directly. def raw_post @@ -392,33 +165,6 @@ def raw_post @env['RAW_POST_DATA'] end - # Returns both GET and POST \parameters in a single hash. - def parameters - @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access - end - alias_method :params, :parameters - - def path_parameters=(parameters) #:nodoc: - @env.delete("action_dispatch.request.symbolized_path_parameters") - @env.delete("action_dispatch.request.parameters") - @env["action_dispatch.request.path_parameters"] = parameters - end - - # The same as path_parameters with explicitly symbolized keys. - def symbolized_path_parameters - @env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys - end - - # Returns a hash with the \parameters used to form the \path of the request. - # Returned hash keys are strings: - # - # {'action' => 'my_action', 'controller' => 'my_controller'} - # - # See symbolized_path_parameters for symbolized keys. - def path_parameters - @env["action_dispatch.request.path_parameters"] ||= {} - end - # The request body is an IO input stream. If the RAW_POST_DATA environment # variable is already set, wrap it in a StringIO. def body @@ -434,18 +180,6 @@ def form_data? FORM_DATA_MEDIA_TYPES.include?(content_type.to_s) end - # Override Rack's GET method to support indifferent access - def GET - @env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super) - end - alias_method :query_parameters, :GET - - # Override Rack's POST method to support indifferent access - def POST - @env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super) - end - alias_method :request_parameters, :POST - def body_stream #:nodoc: @env['rack.input'] end @@ -463,6 +197,19 @@ def session_options=(options) @env['rack.session.options'] = options end + # Override Rack's GET method to support indifferent access + def GET + @env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super) + end + alias :query_parameters :GET + + # Override Rack's POST method to support indifferent access + def POST + @env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super) + end + alias :request_parameters :POST + + # Returns the authorization header regardless of whether it was specified directly or through one of the # proxy alternatives. def authorization @@ -471,77 +218,5 @@ def authorization @env['X_HTTP_AUTHORIZATION'] || @env['REDIRECT_X_HTTP_AUTHORIZATION'] end - - # Receives an array of mimes and return the first user sent mime that - # matches the order array. - # - def negotiate_mime(order) - formats.each do |priority| - if priority == Mime::ALL - return order.first - elsif order.include?(priority) - return priority - end - end - - order.include?(Mime::ALL) ? formats.first : nil - end - - private - - def named_host?(host) - !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) - end - - module UploadedFile - def self.extended(object) - object.class_eval do - attr_accessor :original_path, :content_type - alias_method :local_path, :path if method_defined?(:path) - end - end - - # Take the basename of the upload's original filename. - # This handles the full Windows paths given by Internet Explorer - # (and perhaps other broken user agents) without affecting - # those which give the lone filename. - # The Windows regexp is adapted from Perl's File::Basename. - def original_filename - unless defined? @original_filename - @original_filename = - unless original_path.blank? - if original_path =~ /^(?:.*[:\\\/])?(.*)/m - $1 - else - File.basename original_path - end - end - end - @original_filename - end - end - - # Convert nested Hashs to HashWithIndifferentAccess and replace - # file upload hashs with UploadedFile objects - def normalize_parameters(value) - case value - when Hash - if value.has_key?(:tempfile) - upload = value[:tempfile] - upload.extend(UploadedFile) - upload.original_path = value[:filename] - upload.content_type = value[:type] - upload - else - h = {} - value.each { |k, v| h[k] = normalize_parameters(v) } - h.with_indifferent_access - end - when Array - value.map { |e| normalize_parameters(e) } - else - value - end - end end end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 8524bbd993f77..65df9b1f03470 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,6 +32,8 @@ module ActionDispatch # :nodoc: # end # end class Response < Rack::Response + include ActionDispatch::Http::Cache::Response + attr_accessor :request, :blank attr_writer :header, :sending_file @@ -55,10 +57,6 @@ def initialize yield self if block_given? end - def cache_control - @cache_control ||= {} - end - def status=(status) @status = Rack::Utils.status_code(status) end @@ -114,33 +112,6 @@ def location=(url) # information. attr_accessor :charset, :content_type - def last_modified - if last = headers['Last-Modified'] - Time.httpdate(last) - end - end - - def last_modified? - headers.include?('Last-Modified') - end - - def last_modified=(utc_time) - headers['Last-Modified'] = utc_time.httpdate - end - - def etag - @etag - end - - def etag? - @etag - end - - def etag=(etag) - key = ActiveSupport::Cache.expand_cache_key(etag) - @etag = %("#{Digest::MD5.hexdigest(key)}") - end - CONTENT_TYPE = "Content-Type" cattr_accessor(:default_charset) { "utf-8" } @@ -222,31 +193,6 @@ def delete_cookie(key, value={}) end private - def handle_conditional_get! - if etag? || last_modified? || !@cache_control.empty? - set_conditional_cache_control! - elsif nonempty_ok_response? - self.etag = @body - - if request && request.etag_matches?(etag) - self.status = 304 - self.body = [] - end - - set_conditional_cache_control! - else - headers["Cache-Control"] = "no-cache" - end - end - - def nonempty_ok_response? - @status == 200 && string_body? - end - - def string_body? - !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) } - end - def assign_default_content_type_and_charset! return if headers[CONTENT_TYPE].present? @@ -259,27 +205,5 @@ def assign_default_content_type_and_charset! headers[CONTENT_TYPE] = type end - DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" - - def set_conditional_cache_control! - control = @cache_control - - if control.empty? - headers["Cache-Control"] = DEFAULT_CACHE_CONTROL - elsif @cache_control[:no_cache] - headers["Cache-Control"] = "no-cache" - else - extras = control[:extras] - max_age = control[:max_age] - - options = [] - options << "max-age=#{max_age.to_i}" if max_age - options << (control[:public] ? "public" : "private") - options << "must-revalidate" if control[:must_revalidate] - options.concat(extras) if extras - - headers["Cache-Control"] = options.join(", ") - end - end end end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb new file mode 100644 index 0000000000000..dc6121b911181 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -0,0 +1,48 @@ +module ActionDispatch + module Http + module UploadedFile + def self.extended(object) + object.class_eval do + attr_accessor :original_path, :content_type + alias_method :local_path, :path if method_defined?(:path) + end + end + + # Take the basename of the upload's original filename. + # This handles the full Windows paths given by Internet Explorer + # (and perhaps other broken user agents) without affecting + # those which give the lone filename. + # The Windows regexp is adapted from Perl's File::Basename. + def original_filename + unless defined? @original_filename + @original_filename = + unless original_path.blank? + if original_path =~ /^(?:.*[:\\\/])?(.*)/m + $1 + else + File.basename original_path + end + end + end + @original_filename + end + end + + module Upload + # Convert nested Hashs to HashWithIndifferentAccess and replace + # file upload hashs with UploadedFile objects + def normalize_parameters(value) + if Hash === value && value.has_key?(:tempfile) + upload = value[:tempfile] + upload.extend(UploadedFile) + upload.original_path = value[:filename] + upload.content_type = value[:type] + upload + else + super + end + end + private :normalize_parameters + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb new file mode 100644 index 0000000000000..40ceb5a9b63a2 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -0,0 +1,129 @@ +module ActionDispatch + module Http + module URL + # Returns the complete URL used for this request. + def url + protocol + host_with_port + request_uri + end + + # Returns 'https://' if this is an SSL request and 'http://' otherwise. + def protocol + ssl? ? 'https://' : 'http://' + end + + # Is this an SSL request? + def ssl? + @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https' + end + + # Returns the \host for this request, such as "example.com". + def raw_host_with_port + if forwarded = env["HTTP_X_FORWARDED_HOST"] + forwarded.split(/,\s?/).last + else + env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}" + end + end + + # Returns the host for this request, such as example.com. + def host + raw_host_with_port.sub(/:\d+$/, '') + end + + # Returns a \host:\port string for this request, such as "example.com" or + # "example.com:8080". + def host_with_port + "#{host}#{port_string}" + end + + # Returns the port number of this request as an integer. + def port + if raw_host_with_port =~ /:(\d+)$/ + $1.to_i + else + standard_port + end + end + + # Returns the standard \port number for this request's protocol. + def standard_port + case protocol + when 'https://' then 443 + else 80 + end + end + + # Returns a \port suffix like ":8080" if the \port number of this request + # is not the default HTTP \port 80 or HTTPS \port 443. + def port_string + port == standard_port ? '' : ":#{port}" + end + + def server_port + @env['SERVER_PORT'].to_i + end + + # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify + # a different tld_length, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk". + def domain(tld_length = 1) + return nil unless named_host?(host) + + host.split('.').last(1 + tld_length).join('.') + end + + # Returns all the \subdomains as an array, so ["dev", "www"] would be + # returned for "dev.www.rubyonrails.org". You can specify a different tld_length, + # such as 2 to catch ["www"] instead of ["www", "rubyonrails"] + # in "www.rubyonrails.co.uk". + def subdomains(tld_length = 1) + return [] unless named_host?(host) + parts = host.split('.') + parts[0..-(tld_length+2)] + end + + # Returns the query string, accounting for server idiosyncrasies. + def query_string + @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '') + end + + # Returns the request URI, accounting for server idiosyncrasies. + # WEBrick includes the full URL. IIS leaves REQUEST_URI blank. + def request_uri + if uri = @env['REQUEST_URI'] + # Remove domain, which webrick puts into the request_uri. + (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri + else + # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO. + uri = @env['PATH_INFO'].to_s + + if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$}) + uri = uri.sub(/#{script_filename}\//, '') + end + + env_qs = @env['QUERY_STRING'].to_s + uri += "?#{env_qs}" unless env_qs.empty? + + if uri.blank? + @env.delete('REQUEST_URI') + else + @env['REQUEST_URI'] = uri + end + end + end + + # Returns the interpreted \path to requested resource after all the installation + # directory of this application was taken into account. + def path + path = request_uri.to_s[/\A[^\?]*/] + path.sub!(/\A#{ActionController::Base.relative_url_root}/, '') + path + end + + private + + def named_host?(host) + !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) + end + end + end +end \ No newline at end of file From 488b4c8c5c8e441c0aaaa9d4f71263dd3beacde4 Mon Sep 17 00:00:00 2001 From: Sam Granieri Date: Wed, 13 Jan 2010 18:38:22 -0600 Subject: [PATCH 02/82] Make script/dbconsole work again [#3690 state:resolved] Signed-off-by: Pratik Naik --- railties/lib/rails/commands/dbconsole.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index b836a17a4985f..593e2d8ee3683 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -34,8 +34,8 @@ def start abort opt.to_s unless (0..1).include?(ARGV.size) end - unless config = YAML::load(ERB.new(IO.read("#{@app.root}/config/database.yml")).result)[env] - abort "No database is configured for the environment '#{env}'" + unless config = YAML::load(ERB.new(IO.read("#{@app.root}/config/database.yml")).result)[Rails.env] + abort "No database is configured for the environment '#{Rails.env}'" end From 61e831564aa4b22983646ecdc15d43991bc0e522 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 16 Jan 2010 21:21:20 +0530 Subject: [PATCH 03/82] Add Relation#apply_finder_options for applying old finder options --- activerecord/lib/active_record/base.rb | 30 +------------------ .../active_record/relation/spawn_methods.rb | 23 ++++++++++++++ 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e84130f42e0d9..5bd24ac3ebb08 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1563,24 +1563,8 @@ def type_name_with_module(type_name) end def construct_finder_arel(options = {}, scope = nil) - validate_find_options(options) - - relation = active_relation. - joins(options[:joins]). - where(options[:conditions]). - select(options[:select]). - group(options[:group]). - having(options[:having]). - order(options[:order]). - limit(options[:limit]). - offset(options[:offset]). - from(options[:from]). - includes(options[:include]) - + relation = active_relation.apply_finder_options(options) relation = relation.where(type_condition) if finder_needs_type_condition? - relation = relation.lock(options[:lock]) if options[:lock].present? - relation = relation.readonly(options[:readonly]) if options.has_key?(:readonly) - relation = scope.merge(relation) if scope relation end @@ -1781,11 +1765,6 @@ def with_scope(method_scoping = {}, action = :merge, &block) end method_scoping.assert_valid_keys([ :find, :create ]) - - if f = method_scoping[:find] - f.assert_valid_keys(VALID_FIND_OPTIONS) - end - relation = construct_finder_arel(method_scoping[:find] || {}) if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create] @@ -2047,13 +2026,6 @@ def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc: end end - VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, - :order, :select, :readonly, :group, :having, :from, :lock ] - - def validate_find_options(options) #:nodoc: - options.assert_valid_keys(VALID_FIND_OPTIONS) - end - def encode_quoted_value(value) #:nodoc: quoted_value = connection.quote(value) quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) " diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index a248c72715145..59cfca85aecc9 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -88,5 +88,28 @@ def only(*onlies) result end + VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, + :order, :select, :readonly, :group, :having, :from, :lock ] + + def apply_finder_options(options) + options.assert_valid_keys(VALID_FIND_OPTIONS) + + relation = joins(options[:joins]). + where(options[:conditions]). + select(options[:select]). + group(options[:group]). + having(options[:having]). + order(options[:order]). + limit(options[:limit]). + offset(options[:offset]). + from(options[:from]). + includes(options[:include]) + + relation = relation.lock(options[:lock]) if options[:lock].present? + relation = relation.readonly(options[:readonly]) if options.has_key?(:readonly) + + relation + end + end end From f80be3ea0fa2bb3416e180901d441b0834001c7f Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 16 Jan 2010 22:05:01 +0530 Subject: [PATCH 04/82] Use Relation#apply_finder_options from calculations --- .../lib/active_record/calculations.rb | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index cd7bd185c527e..a79ceb1d054e5 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -163,14 +163,7 @@ def construct_calculation_arel(options = {}, merge_with_relation = nil) join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(joins)) construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation) else - relation = active_relation. - joins(options[:joins]). - where(options[:conditions]). - order(options[:order]). - limit(options[:limit]). - offset(options[:offset]). - group(options[:group]). - having(options[:having]) + relation = active_relation.apply_finder_options(options.slice(:joins, :conditions, :order, :limit, :offset, :group, :having)) if merge_with_relation relation = merge_with_relation.except(:select, :order, :limit, :offset, :group, :from).merge(relation) @@ -205,14 +198,8 @@ def construct_calculation_arel_with_included_associations(options, join_dependen relation = relation.where(type_condition) if finder_needs_type_condition? end - relation = relation.joins(options[:joins]). - select(column_aliases(join_dependency)). - group(options[:group]). - having(options[:having]). - order(options[:order]). - where(options[:conditions]). - from(options[:from]) - + relation = relation.apply_finder_options(options.slice(:joins, :group, :having, :order, :conditions, :from)). + select(column_aliases(join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && (merge_limit || options[:limit]) relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) From cfdfd899262c79c37ac89e030f4d90c8f9868b50 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 16 Jan 2010 22:14:10 +0530 Subject: [PATCH 05/82] Use new finder methods for association preloading --- .../lib/active_record/association_preload.rb | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 9f7b2a60b2667..a43c4d09d65ef 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -188,11 +188,11 @@ def preload_has_and_belongs_to_many_association(records, reflection, preload_opt conditions << append_conditions(reflection, preload_options) associated_records = reflection.klass.with_exclusive_scope do - reflection.klass.find(:all, :conditions => [conditions, ids], - :include => options[:include], - :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}", - :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id", - :order => options[:order]) + reflection.klass.where([conditions, ids]). + includes(options[:include]). + joins("INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}"). + select("#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id"). + order(options[:order]).to_a end set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id') end @@ -327,6 +327,7 @@ def preload_belongs_to_association(records, reflection, preload_options={}) table_name = klass.quoted_table_name primary_key = klass.primary_key column_type = klass.columns.detect{|c| c.name == primary_key}.type + ids = id_map.keys.map do |id| if column_type == :integer id.to_i @@ -336,15 +337,14 @@ def preload_belongs_to_association(records, reflection, preload_options={}) id end end + conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}" conditions << append_conditions(reflection, preload_options) + associated_records = klass.with_exclusive_scope do - klass.find(:all, :conditions => [conditions, ids], - :include => options[:include], - :select => options[:select], - :joins => options[:joins], - :order => options[:order]) + klass.where([conditions, ids]).apply_finder_options(options.slice(:include, :select, :joins, :order)).to_a end + set_association_single_records(id_map, reflection.name, associated_records, primary_key) end end @@ -363,13 +363,12 @@ def find_associated_records(ids, reflection, preload_options) conditions << append_conditions(reflection, preload_options) reflection.klass.with_exclusive_scope do - reflection.klass.find(:all, - :select => (preload_options[:select] || options[:select] || "#{table_name}.*"), - :include => preload_options[:include] || options[:include], - :conditions => [conditions, ids], - :joins => options[:joins], - :group => preload_options[:group] || options[:group], - :order => preload_options[:order] || options[:order]) + reflection.klass.select(preload_options[:select] || options[:select] || "#{table_name}.*"). + includes(preload_options[:include] || options[:include]). + where([conditions, ids]). + joins(options[:joins]). + group(preload_options[:group] || options[:group]). + order(preload_options[:order] || options[:order]) end end From 3968825f5ff6a75cb83400716d56ec10f261e41a Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 16 Jan 2010 23:11:35 +0530 Subject: [PATCH 06/82] Make sure Model#active_relation always adds STI conditions if needed --- activerecord/lib/active_record/base.rb | 5 ++--- activerecord/lib/active_record/calculations.rb | 4 ---- activerecord/lib/active_record/named_scope.rb | 2 +- activerecord/lib/active_record/relation/spawn_methods.rb | 4 +++- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 5bd24ac3ebb08..d0db9fadcd8f0 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -872,7 +872,6 @@ def update_all(updates, conditions = nil, options = {}) relation = active_relation relation = relation.where(conditions) if conditions - relation = relation.where(type_condition) if finder_needs_type_condition? relation = relation.limit(options[:limit]) if options[:limit].present? relation = relation.order(options[:order]) if options[:order].present? @@ -1389,7 +1388,7 @@ def column_methods_hash #:nodoc: def reset_column_information undefine_attribute_methods @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil - @active_relation = @arel_engine = nil + @arel_engine = @active_relation = @arel_engine = nil end def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc: @@ -1504,6 +1503,7 @@ def merge_conditions(*conditions) def active_relation @active_relation ||= Relation.new(self, arel_table) + finder_needs_type_condition? ? @active_relation.where(type_condition) : @active_relation end def arel_table(table_name_alias = nil) @@ -1564,7 +1564,6 @@ def type_name_with_module(type_name) def construct_finder_arel(options = {}, scope = nil) relation = active_relation.apply_finder_options(options) - relation = relation.where(type_condition) if finder_needs_type_condition? relation = scope.merge(relation) if scope relation end diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index a79ceb1d054e5..ed4218807dbf0 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -167,8 +167,6 @@ def construct_calculation_arel(options = {}, merge_with_relation = nil) if merge_with_relation relation = merge_with_relation.except(:select, :order, :limit, :offset, :group, :from).merge(relation) - else - relation = relation.where(type_condition) if finder_needs_type_condition? end from = merge_with_relation.from_value if merge_with_relation && merge_with_relation.from_value.present? @@ -194,8 +192,6 @@ def construct_calculation_arel_with_included_associations(options, join_dependen relation.where_values = merge_with_relation.where_values merge_limit = merge_with_relation.taken - else - relation = relation.where(type_condition) if finder_needs_type_condition? end relation = relation.apply_finder_options(options.slice(:joins, :group, :having, :order, :conditions, :from)). diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 47b69dec6292c..531419fd8e315 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -29,7 +29,7 @@ def scoped(options = {}, &block) current_scope = current_scoped_methods unless current_scope - finder_needs_type_condition? ? active_relation.where(type_condition) : active_relation.spawn + active_relation.spawn else construct_finder_arel({}, current_scoped_methods) end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 59cfca85aecc9..33df8fd06c59a 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -94,7 +94,9 @@ def only(*onlies) def apply_finder_options(options) options.assert_valid_keys(VALID_FIND_OPTIONS) - relation = joins(options[:joins]). + relation = spawn + + relation = relation.joins(options[:joins]). where(options[:conditions]). select(options[:select]). group(options[:group]). From 07e41a83616966fef848797c3474eb7704a62794 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 16 Jan 2010 23:17:14 +0530 Subject: [PATCH 07/82] No need to set @arel_engine to nil twice. Committed by mistake in 3968825f --- activerecord/lib/active_record/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index d0db9fadcd8f0..acebf1e7fd0bd 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1388,7 +1388,7 @@ def column_methods_hash #:nodoc: def reset_column_information undefine_attribute_methods @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil - @arel_engine = @active_relation = @arel_engine = nil + @arel_engine = @active_relation = nil end def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc: From 0fc6418e75626bbbd2a696bd3e354bae583d65ed Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Sat, 16 Jan 2010 23:23:54 +0530 Subject: [PATCH 08/82] Mark title and headers as html_safe! for guides [#3702 state:resolved] Signed-off-by: Pratik Naik --- railties/guides/rails_guides/generator.rb | 6 +++--- railties/guides/source/layout.html.erb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb index 2a4714b13a9bc..cebf6ae866f03 100644 --- a/railties/guides/rails_guides/generator.rb +++ b/railties/guides/rails_guides/generator.rb @@ -71,8 +71,8 @@ def set_header_section(body, view) header = textile(header) - view.content_for(:page_title) { page_title } - view.content_for(:header_section) { header } + view.content_for(:page_title) { page_title.html_safe! } + view.content_for(:header_section) { header.html_safe! } new_body end @@ -103,7 +103,7 @@ def set_index(body, view) index << '' index << '' - view.content_for(:index_section) { index } + view.content_for(:index_section) { index.html_safe! } i.result end diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb index eb66366d07f5c..7dfcf4a507d4e 100644 --- a/railties/guides/source/layout.html.erb +++ b/railties/guides/source/layout.html.erb @@ -87,7 +87,7 @@
- <%= yield %> + <%= yield.html_safe! %>
From e17b23db016c03801ade2666793b4a51583f3785 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Sat, 16 Jan 2010 10:42:04 +1000 Subject: [PATCH 09/82] Make guides generation work for Ruby 1.9.x Signed-off-by: Pratik Naik --- railties/guides/rails_guides/generator.rb | 2 +- railties/guides/rails_guides/indexer.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb index cebf6ae866f03..e3df3be8824ec 100644 --- a/railties/guides/rails_guides/generator.rb +++ b/railties/guides/rails_guides/generator.rb @@ -95,7 +95,7 @@ def set_index(body, view) view.content_tag(:li, l) end - children_ul = view.content_tag(:ul, children) + children_ul = view.content_tag(:ul, children.join(" ")) index << view.content_tag(:li, link + children_ul) end diff --git a/railties/guides/rails_guides/indexer.rb b/railties/guides/rails_guides/indexer.rb index 5b5ad3fee1a97..939404c85f3ed 100644 --- a/railties/guides/rails_guides/indexer.rb +++ b/railties/guides/rails_guides/indexer.rb @@ -19,9 +19,9 @@ def process(string, current_level= 3, counters = [1]) level_hash = ActiveSupport::OrderedHash.new while !s.eos? - s.match?(/\h[0-9]\..*$/) + s.match?(/h[0-9]\..*$/) if matched = s.matched - matched =~ /\h([0-9])\.(.*)$/ + matched =~ /h([0-9])\.(.*)$/ level, title = $1.to_i, $2 if level < current_level From 7f8d4d3c4ed82a90f94251438d61d395a544026c Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 16 Jan 2010 23:59:57 +0530 Subject: [PATCH 10/82] Get rid of Base#merge_includes --- activerecord/lib/active_record/base.rb | 5 ----- activerecord/lib/active_record/calculations.rb | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index acebf1e7fd0bd..bf42a5b2213be 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1583,11 +1583,6 @@ def construct_join(joins) end end - # Merges includes so that the result is a valid +include+ - def merge_includes(first, second) - (Array.wrap(first) + Array.wrap(second)).uniq - end - def build_association_joins(joins) join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, joins, nil) relation = active_relation.table diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index ed4218807dbf0..f2de56ae5e1ac 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -155,7 +155,8 @@ def construct_calculation_arel(options = {}, merge_with_relation = nil) validate_calculation_options(options) options = options.except(:distinct) - includes = merge_includes(merge_with_relation ? merge_with_relation.includes_values : [], options[:include]) + merge_with_includes = merge_with_relation ? merge_with_relation.includes_values : [] + includes = (merge_with_includes + Array.wrap(options[:include])).uniq if includes.any? merge_with_joins = merge_with_relation ? merge_with_relation.joins_values : [] From 468cfcedd311a428339fa01e5f62b935f2995ec0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 02:08:42 +0530 Subject: [PATCH 11/82] Improve the error message for class mismatch on Relation#merge --- activerecord/lib/active_record/relation/spawn_methods.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 33df8fd06c59a..f4abaae43e938 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -15,7 +15,9 @@ def spawn(arel_table = self.table) end def merge(r) - raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass + if r.klass != @klass + raise ArgumentError, "Cannot merge a #{r.klass.name}(##{r.klass.object_id}) relation with #{@klass.name}(##{@klass.object_id}) relation" + end merged_relation = spawn.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values) From e9a1dbe79a6610793a71af227aaf64ff55554cad Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sat, 16 Jan 2010 15:16:22 -0600 Subject: [PATCH 12/82] Allow custom controller for resource(s) [#3703 state:resolved] --- .../lib/action_dispatch/routing/mapper.rb | 2 +- actionpack/test/dispatch/routing_test.rb | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index ce5c56ae1cb04..9aaa4355f2640 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -380,7 +380,7 @@ def name end def controller - plural + options[:controller] || plural end def member_name diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 5845c791a02ab..6dccabdb3f6c1 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -65,7 +65,7 @@ def self.matches?(request) resources :companies do resources :people - resource :avatar + resource :avatar, :controller => :avatar end resources :images do @@ -294,34 +294,34 @@ def test_global def test_projects with_test_routes do get '/projects' - assert_equal 'projects#index', @response.body + assert_equal 'project#index', @response.body assert_equal '/projects', projects_path post '/projects' - assert_equal 'projects#create', @response.body + assert_equal 'project#create', @response.body get '/projects.xml' - assert_equal 'projects#index', @response.body + assert_equal 'project#index', @response.body assert_equal '/projects.xml', projects_path(:format => 'xml') get '/projects/new' - assert_equal 'projects#new', @response.body + assert_equal 'project#new', @response.body assert_equal '/projects/new', new_project_path get '/projects/new.xml' - assert_equal 'projects#new', @response.body + assert_equal 'project#new', @response.body assert_equal '/projects/new.xml', new_project_path(:format => 'xml') get '/projects/1' - assert_equal 'projects#show', @response.body + assert_equal 'project#show', @response.body assert_equal '/projects/1', project_path(:id => '1') get '/projects/1.xml' - assert_equal 'projects#show', @response.body + assert_equal 'project#show', @response.body assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml') get '/projects/1/edit' - assert_equal 'projects#edit', @response.body + assert_equal 'project#edit', @response.body assert_equal '/projects/1/edit', edit_project_path(:id => '1') end end @@ -383,7 +383,7 @@ def test_projects_companies assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1') get '/projects/1/companies/1/avatar' - assert_equal 'avatars#show', @response.body + assert_equal 'avatar#show', @response.body assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1') end end From cd90dcb1bde5c411a55bcec97597a8fe22b56a5d Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 02:59:42 +0530 Subject: [PATCH 13/82] Rename Model.active_relation to Model.unscoped --- .../lib/active_record/associations.rb | 4 ++-- activerecord/lib/active_record/base.rb | 24 +++++++++---------- .../lib/active_record/calculations.rb | 4 ++-- .../lib/active_record/locking/optimistic.rb | 2 +- activerecord/lib/active_record/named_scope.rb | 2 +- .../relation/calculation_methods.rb | 4 ++-- .../active_record/validations/uniqueness.rb | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index b52b298027d15..3034e9d2375ea 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1703,7 +1703,7 @@ def select_all_rows(options, join_dependency) end def construct_finder_arel_with_included_associations(options, join_dependency) - relation = active_relation + relation = unscoped for association in join_dependency.join_associations relation = association.join_relation(relation) @@ -1754,7 +1754,7 @@ def select_limited_ids_array(options, join_dependency) end def construct_finder_sql_for_association_limiting(options, join_dependency) - relation = active_relation + relation = unscoped for association in join_dependency.join_associations relation = association.join_relation(relation) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index bf42a5b2213be..b0c9ed254805e 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -869,7 +869,7 @@ def destroy(id) # # Update all books that match our conditions, but limit it to 5 ordered by date # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5 def update_all(updates, conditions = nil, options = {}) - relation = active_relation + relation = unscoped relation = relation.where(conditions) if conditions relation = relation.limit(options[:limit]) if options[:limit].present? @@ -1388,7 +1388,7 @@ def column_methods_hash #:nodoc: def reset_column_information undefine_attribute_methods @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil - @arel_engine = @active_relation = nil + @arel_engine = @unscoped = nil end def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc: @@ -1501,9 +1501,9 @@ def merge_conditions(*conditions) "(#{segments.join(') AND (')})" unless segments.empty? end - def active_relation - @active_relation ||= Relation.new(self, arel_table) - finder_needs_type_condition? ? @active_relation.where(type_condition) : @active_relation + def unscoped + @unscoped ||= Relation.new(self, arel_table) + finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped end def arel_table(table_name_alias = nil) @@ -1563,7 +1563,7 @@ def type_name_with_module(type_name) end def construct_finder_arel(options = {}, scope = nil) - relation = active_relation.apply_finder_options(options) + relation = unscoped.apply_finder_options(options) relation = scope.merge(relation) if scope relation end @@ -1585,7 +1585,7 @@ def construct_join(joins) def build_association_joins(joins) join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, joins, nil) - relation = active_relation.table + relation = unscoped.table join_dependency.join_associations.map { |association| if (association_relation = association.relation).is_a?(Array) [Arel::InnerJoin.new(relation, association_relation.first, *association.association_join.first).joins(relation), @@ -2193,7 +2193,7 @@ def delete # be made (since they can't be persisted). def destroy unless new_record? - self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).delete_all + self.class.unscoped.where(self.class.unscoped[self.class.primary_key].eq(id)).delete_all end @destroyed = true @@ -2480,7 +2480,7 @@ def create_or_update def update(attribute_names = @attributes.keys) attributes_with_values = arel_attributes_values(false, false, attribute_names) return 0 if attributes_with_values.empty? - self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).update(attributes_with_values) + self.class.unscoped.where(self.class.unscoped[self.class.primary_key].eq(id)).update(attributes_with_values) end # Creates a record with values matching those of the instance attributes @@ -2493,9 +2493,9 @@ def create attributes_values = arel_attributes_values new_id = if attributes_values.empty? - self.class.active_relation.insert connection.empty_insert_statement_value + self.class.unscoped.insert connection.empty_insert_statement_value else - self.class.active_relation.insert attributes_values + self.class.unscoped.insert attributes_values end self.id ||= new_id @@ -2590,7 +2590,7 @@ def arel_attributes_values(include_primary_key = true, include_readonly_attribut if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array)) value = value.to_yaml end - attrs[self.class.active_relation[name]] = value + attrs[self.class.unscoped[name]] = value end end end diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index f2de56ae5e1ac..e4b3caab4e9d6 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -164,7 +164,7 @@ def construct_calculation_arel(options = {}, merge_with_relation = nil) join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(joins)) construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation) else - relation = active_relation.apply_finder_options(options.slice(:joins, :conditions, :order, :limit, :offset, :group, :having)) + relation = unscoped.apply_finder_options(options.slice(:joins, :conditions, :order, :limit, :offset, :group, :having)) if merge_with_relation relation = merge_with_relation.except(:select, :order, :limit, :offset, :group, :from).merge(relation) @@ -182,7 +182,7 @@ def construct_calculation_arel(options = {}, merge_with_relation = nil) end def construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation = nil) - relation = active_relation + relation = unscoped for association in join_dependency.join_associations relation = association.join_relation(relation) diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index f9e538c5863e8..9fcdabdb4456a 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -78,7 +78,7 @@ def update_with_lock(attribute_names = @attributes.keys) #:nodoc: attribute_names.uniq! begin - relation = self.class.active_relation + relation = self.class.unscoped affected_rows = relation.where( relation[self.class.primary_key].eq(quoted_id).and( diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 531419fd8e315..90fd70043734b 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -29,7 +29,7 @@ def scoped(options = {}, &block) current_scope = current_scoped_methods unless current_scope - active_relation.spawn + unscoped.spawn else construct_finder_arel({}, current_scoped_methods) end diff --git a/activerecord/lib/active_record/relation/calculation_methods.rb b/activerecord/lib/active_record/relation/calculation_methods.rb index 2477481ec8703..91de89e6073fc 100644 --- a/activerecord/lib/active_record/relation/calculation_methods.rb +++ b/activerecord/lib/active_record/relation/calculation_methods.rb @@ -53,7 +53,7 @@ def calculate(operation, column_name, options = {}) def execute_simple_calculation(operation, column_name, distinct) #:nodoc: column = if @klass.column_names.include?(column_name.to_s) - Arel::Attribute.new(@klass.active_relation, column_name) + Arel::Attribute.new(@klass.unscoped, column_name) else Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s) end @@ -77,7 +77,7 @@ def execute_grouped_calculation(operation, column_name) #:nodoc: select_statement = if operation == 'count' && column_name == :all "COUNT(*) AS count_all" else - Arel::Attribute.new(@klass.active_relation, column_name).send(operation).as(aggregate_alias).to_sql + Arel::Attribute.new(@klass.unscoped, column_name).send(operation).as(aggregate_alias).to_sql end select_statement << ", #{group_field} AS #{group_alias}" diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 4ff851dfa12b2..e28808df98a08 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -12,7 +12,7 @@ def setup(klass) def validate_each(record, attribute, value) finder_class = find_finder_class_for(record) - table = finder_class.active_relation + table = finder_class.unscoped table_name = record.class.quoted_table_name sql, params = mount_sql_and_params(finder_class, table_name, attribute, value) From 0ab30637dd5bc7536c5accd66b45ce0263134a14 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 03:04:11 +0530 Subject: [PATCH 14/82] Revert "Fix #microseconds conversion and #fast_string_to_time" This reverts commit 717a2941e15b32d07cc456bb0d81742ecfc5b4a3. Bunch of failures when running postgresql tests. --- .../abstract/schema_definitions.rb | 13 +++--- .../test/cases/schema_definitions_test.rb | 43 ------------------- 2 files changed, 6 insertions(+), 50 deletions(-) delete mode 100644 activerecord/test/cases/schema_definitions_test.rb diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 5e8a01644d0ea..520f3c8c0c01b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -13,7 +13,6 @@ class Column module Format ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ - NEW_ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(?:\.(\d+))?\z/ end attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale @@ -168,11 +167,10 @@ def value_to_decimal(value) end protected - # Rational(123456, 1_000_000) -> 123456 - # The sec_fraction component returned by Date._parse is a Rational fraction of a second or nil - # NB: This method is optimized for performance by immediately converting away from Rational. + # '0.123456' -> 123456 + # '1.123456' -> 123456 def microseconds(time) - ((time[:sec_fraction].to_f % 1) * 1_000_000).round + ((time[:sec_fraction].to_f % 1) * 1_000_000).to_i end def new_date(year, mon, mday) @@ -196,8 +194,9 @@ def fast_string_to_date(string) # Doesn't handle time zones. def fast_string_to_time(string) - if md = Format::NEW_ISO_DATETIME.match(string) - new_time *md.to_a[1..7].map(&:to_i) + if string =~ Format::ISO_DATETIME + microsec = ($7.to_f * 1_000_000).to_i + new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec end end diff --git a/activerecord/test/cases/schema_definitions_test.rb b/activerecord/test/cases/schema_definitions_test.rb deleted file mode 100644 index d90a95cf58b1f..0000000000000 --- a/activerecord/test/cases/schema_definitions_test.rb +++ /dev/null @@ -1,43 +0,0 @@ -require "cases/helper" - -class SchemaDefinitionsTest < ActiveRecord::TestCase - - REGRESSION_SAMPLES = %w{000249 125014 003912 256051 524287} - - test 'fast_string_to_time converts properly' do - converted = ActiveRecord::ConnectionAdapters::Column.send('fast_string_to_time', "2010-01-12 12:34:56.000249") - assert_equal Time.mktime(2010, 01, 12, 12, 34, 56, 249), converted - end - - test 'fallback_string_to_time converts properly' do - converted = ActiveRecord::ConnectionAdapters::Column.send('fallback_string_to_time', "2010-01-12 12:34:56.000249") - assert_equal Time.mktime(2010, 01, 12, 12, 34, 56, 249), converted - end - - test 'fallback_string_to_time converts properly with no microseconds' do - converted = ActiveRecord::ConnectionAdapters::Column.send('fallback_string_to_time', "2010-01-12 12:34:56") - assert_equal Time.mktime(2010, 01, 12, 12, 34, 56, 0), converted - end - - test "fast_string_to_time can handle problematic microseconds" do - REGRESSION_SAMPLES.each do |u| - converted = ActiveRecord::ConnectionAdapters::Column.send('fast_string_to_time', "2010-01-12 12:34:56.#{u}") - assert_equal u.to_i, converted.usec - end - end - - test "microseconds can handle problematic microseconds" do - REGRESSION_SAMPLES.each do |u| - i = u.to_i - converted = ActiveRecord::ConnectionAdapters::Column.send('microseconds', {:sec_fraction => Rational(i, 1_000_000)}) - assert_equal i, converted - - converted = ActiveRecord::ConnectionAdapters::Column.send('microseconds', {:sec_fraction => Rational(i, 1_000_000)}) - assert_equal i, converted - end - end - - test 'fast constant is equally restrictive' do - assert_match ActiveRecord::ConnectionAdapters::Column::Format::NEW_ISO_DATETIME, "2010-01-12 12:34:56.555493" - end -end From 6806483b913aba611af48c1630f229a76a98ecc7 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 03:10:07 +0530 Subject: [PATCH 15/82] Use arel_table[] instead of unscoped[] to get arel attribute --- activerecord/lib/active_record/base.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b0c9ed254805e..3c4a9fe99d510 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2193,7 +2193,7 @@ def delete # be made (since they can't be persisted). def destroy unless new_record? - self.class.unscoped.where(self.class.unscoped[self.class.primary_key].eq(id)).delete_all + self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all end @destroyed = true @@ -2480,7 +2480,7 @@ def create_or_update def update(attribute_names = @attributes.keys) attributes_with_values = arel_attributes_values(false, false, attribute_names) return 0 if attributes_with_values.empty? - self.class.unscoped.where(self.class.unscoped[self.class.primary_key].eq(id)).update(attributes_with_values) + self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values) end # Creates a record with values matching those of the instance attributes @@ -2590,7 +2590,7 @@ def arel_attributes_values(include_primary_key = true, include_readonly_attribut if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array)) value = value.to_yaml end - attrs[self.class.unscoped[name]] = value + attrs[self.class.arel_table[name]] = value end end end From 6e3bee6cf1f0d2684152292db0a8b757249824fd Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 03:14:17 +0530 Subject: [PATCH 16/82] Cache Model.arel_table --- activerecord/lib/active_record/base.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 3c4a9fe99d510..82e91a80ad3c0 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1388,7 +1388,7 @@ def column_methods_hash #:nodoc: def reset_column_information undefine_attribute_methods @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil - @arel_engine = @unscoped = nil + @arel_engine = @unscoped = @arel_table = nil end def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc: @@ -1506,8 +1506,8 @@ def unscoped finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped end - def arel_table(table_name_alias = nil) - Arel::Table.new(table_name, :as => table_name_alias, :engine => arel_engine) + def arel_table + @arel_table ||= Arel::Table.new(table_name, :engine => arel_engine) end def arel_engine From dba196cb7f8d34b93f6872e4a43737bb52019065 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 03:26:20 +0530 Subject: [PATCH 17/82] Merge docrails --- actionmailer/lib/action_mailer/base.rb | 2 +- .../lib/action_controller/metal/responder.rb | 34 +- actionpack/lib/action_dispatch/routing.rb | 7 +- .../testing/assertions/routing.rb | 16 +- .../action_view/helpers/form_tag_helper.rb | 3 + .../action_view/helpers/sanitize_helper.rb | 2 +- .../lib/action_view/helpers/text_helper.rb | 6 +- activemodel/README | 68 +- activemodel/lib/active_model/callbacks.rb | 104 +- activemodel/lib/active_model/conversion.rb | 13 +- activemodel/lib/active_model/dirty.rb | 40 +- .../lib/active_model/validations/length.rb | 8 +- .../lib/active_record/associations.rb | 8 +- activerecord/lib/active_record/base.rb | 4 +- activerecord/lib/active_record/fixtures.rb | 3 +- activerecord/lib/active_record/migration.rb | 3 +- .../lib/active_record/nested_attributes.rb | 10 +- activeresource/lib/active_resource/base.rb | 7 + activesupport/lib/active_support/callbacks.rb | 2 +- .../active_support/core_ext/array/access.rb | 2 +- .../lib/active_support/core_ext/array/wrap.rb | 9 + .../core_ext/hash/conversions.rb | 48 + .../active_support/core_ext/hash/except.rb | 8 + .../core_ext/hash/indifferent_access.rb | 5 + .../lib/active_support/core_ext/hash/keys.rb | 6 +- .../core_ext/object/duplicable.rb | 16 + .../lib/active_support/core_ext/rexml.rb | 5 +- .../active_support/core_ext/string/access.rb | 4 +- .../lib/active_support/multibyte/chars.rb | 2 +- railties/README | 62 +- railties/guides/rails_guides.rb | 15 +- railties/guides/rails_guides/generator.rb | 14 +- .../guides/source/2_2_release_notes.textile | 18 +- .../guides/source/2_3_release_notes.textile | 6 +- .../source/action_mailer_basics.textile | 4 +- .../source/action_view_overview.textile | 1434 ++++++++++++- .../source/active_record_basics.textile | 135 +- .../active_support_core_extensions.textile | 1835 +++++++++++++++++ .../source/active_support_overview.textile | 818 -------- ...activerecord_validations_callbacks.textile | 41 + railties/guides/source/ajax_on_rails.textile | 322 ++- .../guides/source/association_basics.textile | 2 +- .../guides/source/caching_with_rails.textile | 4 + railties/guides/source/configuring.textile | 174 +- .../source/contributing_to_rails.textile | 38 +- ...redits.erb.textile => credits.textile.erb} | 0 .../debugging_rails_applications.textile | 2 +- railties/guides/source/form_helpers.textile | 4 +- railties/guides/source/generators.textile | 378 ++++ .../guides/source/getting_started.textile | 10 +- railties/guides/source/i18n.textile | 2 +- .../{index.erb.textile => index.textile.erb} | 0 railties/guides/source/migrations.textile | 2 +- railties/guides/source/plugins.textile | 2 +- railties/guides/source/rails_on_rack.textile | 2 +- railties/guides/source/routing.textile | 6 +- railties/guides/source/security.textile | 54 +- railties/guides/source/testing.textile | 81 +- 58 files changed, 4712 insertions(+), 1198 deletions(-) create mode 100644 railties/guides/source/active_support_core_extensions.textile delete mode 100644 railties/guides/source/active_support_overview.textile rename railties/guides/source/{credits.erb.textile => credits.textile.erb} (100%) create mode 100644 railties/guides/source/generators.textile rename railties/guides/source/{index.erb.textile => index.textile.erb} (100%) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index b9a01356bafad..5be1beaedbc50 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -36,7 +36,7 @@ module ActionMailer #:nodoc: # * cc - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the Cc: header. # * bcc - Takes one or more email addresses. These addresses will receive a blind carbon copy of your email. Sets the Bcc: header. # * reply_to - Takes one or more email addresses. These addresses will be listed as the default recipients when replying to your email. Sets the Reply-To: header. - # * sent_on - The date on which the message was sent. If not set, the header wil be set by the delivery agent. + # * sent_on - The date on which the message was sent. If not set, the header will be set by the delivery agent. # * content_type - Specify the content type of the message. Defaults to text/plain. # * headers - Specify additional headers to be set for the message, e.g. headers 'X-Mail-Count' => 107370. # diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index cb0e600871215..6178a590298ec 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -1,7 +1,7 @@ module ActionController #:nodoc: - # Responder is responsible to expose a resource for different mime requests, + # Responder is responsible for exposing a resource to different mime requests, # usually depending on the HTTP verb. The responder is triggered when - # respond_with is called. The simplest case to study is a GET request: + # respond_with is called. The simplest case to study is a GET request: # # class PeopleController < ApplicationController # respond_to :html, :xml, :json @@ -12,17 +12,17 @@ module ActionController #:nodoc: # end # end # - # When a request comes, for example with format :xml, three steps happen: + # When a request comes in, for example for an XML response, three steps happen: # - # 1) responder searches for a template at people/index.xml; + # 1) the responder searches for a template at people/index.xml; # - # 2) if the template is not available, it will invoke :to_xml in the given resource; + # 2) if the template is not available, it will invoke #to_xml on the given resource; # - # 3) if the responder does not respond_to :to_xml, call :to_format on it. + # 3) if the responder does not respond_to :to_xml, call #to_format on it. # # === Builtin HTTP verb semantics # - # Rails default responder holds semantics for each HTTP verb. Depending on the + # The default Rails responder holds semantics for each HTTP verb. Depending on the # content type, verb and the resource status, it will behave differently. # # Using Rails default responder, a POST request for creating an object could @@ -55,7 +55,7 @@ module ActionController #:nodoc: # # === Nested resources # - # You can given nested resource as you do in form_for and polymorphic_url. + # You can supply nested resources as you do in form_for and polymorphic_url. # Consider the project has many tasks example. The create action for # TasksController would be like: # @@ -67,15 +67,15 @@ module ActionController #:nodoc: # end # # Giving an array of resources, you ensure that the responder will redirect to - # project_task_url instead of task_url. + # project_task_url instead of task_url. # - # Namespaced and singleton resources requires a symbol to be given, as in + # Namespaced and singleton resources require a symbol to be given, as in # polymorphic urls. If a project has one manager which has many tasks, it # should be invoked as: # # respond_with(@project, :manager, @task) # - # Check polymorphic_url documentation for more examples. + # Check polymorphic_url documentation for more examples. # class Responder attr_reader :controller, :request, :format, :resource, :resources, :options @@ -126,7 +126,7 @@ def to_html navigation_behavior(e) end - # All others formats follow the procedure below. First we try to render a + # All other formats follow the procedure below. First we try to render a # template, if the template is not available, we verify if the resource # responds to :to_format and display it. # @@ -183,11 +183,11 @@ def default_render @default_response.call end - # display is just a shortcut to render a resource with the current format. + # Display is just a shortcut to render a resource with the current format. # # display @user, :status => :ok # - # For xml request is equivalent to: + # For XML requests it's equivalent to: # # render :xml => @user, :status => :ok # @@ -204,14 +204,14 @@ def display(resource, given_options={}) controller.render given_options.merge!(options).merge!(format => resource) end - # Check if the resource has errors or not. + # Check whether the resource has errors. # def has_errors? resource.respond_to?(:errors) && !resource.errors.empty? end - # By default, render the :edit action for html requests with failure, unless - # the verb is post. + # By default, render the :edit action for HTML requests with failure, unless + # the verb is POST. # def default_action @action ||= ACTIONS_FOR_VERBS[request.method] diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index e99f9791978b1..b598d6f7e27cc 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -193,9 +193,10 @@ module ActionDispatch # # With conditions you can define restrictions on routes. Currently the only valid condition is :method. # - # * :method - Allows you to specify which method can access the route. Possible values are :post, - # :get, :put, :delete and :any. The default value is :any, - # :any means that any method can access the route. + # * :method - Allows you to specify which HTTP method(s) can access the route. Possible values are + # :post, :get, :put, :delete and :any. Use an array to specify more + # than one method, e.g. [ :get, :post ]. The default value is :any, :any means that any + # method can access the route. # # Example: # diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index fc477afb177d2..0c33539b4a6b7 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -12,29 +12,29 @@ module RoutingAssertions # and a :method containing the required HTTP verb. # # # assert that POSTing to /items will call the create action on ItemsController - # assert_recognizes {:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post} + # assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post}) # # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used # to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the # extras argument, appending the query string on the path directly will not work. For example: # # # assert that a path of '/items/list/1?view=print' returns the correct options - # assert_recognizes {:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" } + # assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" }) # # The +message+ parameter allows you to pass in an error message that is displayed upon failure. # # ==== Examples # # Check the default route (i.e., the index action) - # assert_recognizes {:controller => 'items', :action => 'index'}, 'items' + # assert_recognizes({:controller => 'items', :action => 'index'}, 'items') # # # Test a specific action - # assert_recognizes {:controller => 'items', :action => 'list'}, 'items/list' + # assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list') # # # Test an action with a parameter - # assert_recognizes {:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1' + # assert_recognizes({:controller => 'items', :action => 'destroy', :id => '1'}, 'items/destroy/1') # # # Test a custom route - # assert_recognizes {:controller => 'items', :action => 'show', :id => '1'}, 'view/item1' + # assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1') # # # Check a Simply RESTful generated route # assert_recognizes list_items_url, 'items/list' @@ -103,7 +103,7 @@ def assert_generates(expected_path, options, defaults={}, extras = {}, message=n # assert_routing '/home', :controller => 'home', :action => 'index' # # # Test a route generated with a specific controller, action, and parameter (id) - # assert_routing '/entries/show/23', :controller => 'entries', :action => 'show', id => 23 + # assert_routing '/entries/show/23', :controller => 'entries', :action => 'show', :id => 23 # # # Assert a basic route (controller + default action), with an error message if it fails # assert_routing '/store', { :controller => 'store', :action => 'index' }, {}, {}, 'Route for store index not generated properly' @@ -112,7 +112,7 @@ def assert_generates(expected_path, options, defaults={}, extras = {}, message=n # assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"} # # # Tests a route with a HTTP method - # assert_routing { :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" } + # assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" }) def assert_routing(path, options, defaults={}, extras={}, message=nil) assert_recognizes(options, path, extras, message) diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 7688e786b1796..048bedc7bad9a 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -55,6 +55,9 @@ def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block) # * Any other key creates standard HTML attributes for the tag. # # ==== Examples + # select_tag "people", options_from_collection_for_select(@people, "name", "id") + # # + # # select_tag "people", "" # # => # diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index f03ffe5ef42db..657d26f0a22a3 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -22,7 +22,7 @@ module SanitizeHelper # # Custom Use (only the mentioned tags and attributes are allowed, nothing else) # - # <%= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id class style) + # <%= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id class style) %> # # Add table tags to the default allowed tags # diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index be15e227b9a5b..814d86812d620 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -226,8 +226,7 @@ def word_wrap(text, *args) # Returns the text with all the Textile[http://www.textism.com/tools/textile] codes turned into HTML tags. # # You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile]. - # This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/] - # is available. + # This method is only available if RedCloth[http://redcloth.org/] is available. # # ==== Examples # textilize("*This is Textile!* Rejoice!") @@ -263,8 +262,7 @@ def textilize(text, *options) # but without the bounding

tag that RedCloth adds. # # You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile]. - # This method is requires RedCloth[http://whytheluckystiff.net/ruby/redcloth/] - # to be available. + # This method is only available if RedCloth[http://redcloth.org/] is available. # # ==== Examples # textilize_without_paragraph("*This is Textile!* Rejoice!") diff --git a/activemodel/README b/activemodel/README index c20f732a12a3d..7c9c754a8ee95 100644 --- a/activemodel/README +++ b/activemodel/README @@ -1,21 +1,57 @@ -Active Model -============== += Active Model - defined interfaces for Rails -Totally experimental library that aims to extract common model mixins from -ActiveRecord for use in ActiveResource (and other similar libraries). -This is in a very rough state (no autotest or spec rake tasks set up yet), -so please excuse the mess. +Prior to Rails 3.0, if a plugin or gem developer wanted to be able to have +an object interact with Action Pack helpers, it was required to either +copy chunks of code from Rails, or monkey patch entire helpers to make them +handle objects that did not look like Active Record. This generated code +duplication and fragile applications that broke on upgrades. -Here's what I plan to extract: - * ActiveModel::Observing - * ActiveModel::Callbacks - * ActiveModel::Validations +Active Model is a solution for this problem. - # for ActiveResource params and ActiveRecord options - * ActiveModel::Scoping +Active Model provides a known set of interfaces that your objects can implement +to then present a common interface to the Action Pack helpers. You can include +functionality from the following modules: - # to_json, to_xml, etc - * ActiveModel::Serialization +* Adding callbacks to your class + + class MyClass + extend ActiveModel::Callbacks + define_model_callbacks :create + + def create + _run_create_callbacks do + # Your create action methods here + end + end + end + + ...gives you before_create, around_create and after_create class methods that + wrap your create method. + + {Learn more}[link:classes/ActiveModel/CallBacks.html] + +* For classes that already look like an Active Record object + + class MyClass + include ActiveModel::Conversion + end + + ...returns the class itself when sent :to_model + +* Tracking changes in your object + + Provides all the value tracking features implemented by ActiveRecord... + + person = Person.new + person.name # => nil + person.changed? # => false + person.name = 'bob' + person.changed? # => true + person.changed # => ['name'] + person.changes # => { 'name' => [nil, 'bob'] } + person.name = 'robert' + person.save + person.previous_changes # => {'name' => ['bob, 'robert']} + + {Learn more}[link:classes/ActiveModel/Dirty.html] -I'm trying to keep ActiveRecord compatibility where possible, but I'm -annotating the spots where I'm diverging a bit. \ No newline at end of file diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index f66a1ddcaa847..a7e0cf90c1baf 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -1,6 +1,52 @@ require 'active_support/callbacks' module ActiveModel + # == Active Model Callbacks + # + # Provides an interface for any class to have Active Record like callbacks. + # + # Like the Active Record methods, the call back chain is aborted as soon as + # one of the methods in the chain returns false. + # + # First, extend ActiveModel::Callbacks from the class you are creating: + # + # class MyModel + # extend ActiveModel::Callbacks + # end + # + # Then define a list of methods that you want call backs attached to: + # + # define_model_callbacks :create, :update + # + # This will provide all three standard callbacks (before, around and after) around + # both the :create and :update methods. To implement, you need to wrap the methods + # you want call backs on in a block so that the call backs get a chance to fire: + # + # def create + # _run_create_callbacks do + # # Your create action methods here + # end + # end + # + # The _run__callbacks methods are dynamically created when you extend + # the ActiveModel::Callbacks module. + # + # Then in your class, you can use the +before_create+, +after_create+ and +around_create+ + # methods, just as you would in an Active Record module. + # + # before_create :action_before_create + # + # def action_before_create + # # Your code here + # end + # + # You can choose not to have all three callbacks by passing an hash to the + # define_model_callbacks method. + # + # define_model_callbacks :create, :only => :after, :before + # + # Would only create the after_create and before_create callback methods in your + # class. module Callbacks def self.extended(base) base.class_eval do @@ -8,43 +54,39 @@ def self.extended(base) end end - # Define callbacks similar to ActiveRecord ones. It means: - # - # * The callback chain is aborted whenever the block given to - # _run_callbacks returns false. - # - # * If a class is given to the fallback, it will search for - # before_create, around_create and after_create methods. - # - # == Usage - # - # First you need to define which callbacks your model will have: + # define_model_callbacks accepts all options define_callbacks does, in case you + # want to overwrite a default. Besides that, it also accepts an :only option, + # where you can choose if you want all types (before, around or after) or just some. # + # define_model_callbacks :initializer, :only => :after + # + # Note, the :only => hash will apply to all callbacks defined on + # that method call. To get around this you can call the define_model_callbacks + # method as many times as you need. + # + # define_model_callbacks :create, :only => :after + # define_model_callbacks :update, :only => :before + # define_model_callbacks :destroy, :only => :around + # + # Would create +after_create+, +before_update+ and +around_destroy+ methods only. + # + # You can pass in a class to before_, after_ and around_, in which + # case the call back will call that class's _ method passing the object + # that the callback is being called on. + # # class MyModel + # extend ActiveModel::Callbacks # define_model_callbacks :create + # + # before_create AnotherClass # end - # - # This will define three class methods: before_create, around_create, - # and after_create. They accept a symbol, a string, an object or a block. - # - # After you create a callback, you need to tell when they are executed. - # For example, you could do: - # - # def create - # _run_create_callbacks do - # super + # + # class AnotherClass + # def self.before_create( obj ) + # # obj is the MyModel instance that the callback is being called on # end # end - # - # == Options - # - # define_model_callbacks accepts all options define_callbacks does, in - # case you want to overwrite a default. Besides that, it also accepts - # an :only option, where you can choose if you want all types (before, - # around or after) or just some: - # - # define_model_callbacks :initializer, :only => :after - # + # def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { :terminator => "result == false", :scope => [:kind, :name] }.merge(options) diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index d5c65920f65da..c14a07c7dc575 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -1,5 +1,16 @@ module ActiveModel - # Include ActiveModel::Conversion if your object "acts like an ActiveModel model". + # If your object is already designed to implement all of the Active Model featurs + # include this module in your Class. + # + # class MyClass + # include ActiveModel::Conversion + # end + # + # Returns self to the :to_model method. + # + # If your model does not act like an Active Model object, then you should define + # :to_model yourself returning a proxy object that wraps your object + # with Active Model compliant methods. module Conversion def to_model self diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 735c61df7426d..5f02929a9d52b 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -1,5 +1,43 @@ module ActiveModel - # Track unsaved attribute changes. + # ActiveModel::Dirty provides a way to track changes in your + # object in the same way as ActiveRecord does. + # + # The requirements to implement ActiveModel::Dirty are: + # + # * include ActiveModel::Dirty in your object + # * Call define_attribute_methods passing each method you want to track + # * Call attr_name_will_change! before each change to the tracked attribute + # + # If you wish to also track previous changes on save or update, you need to add + # + # @previously_changed = changes + # + # inside of your save or update method. + # + # A minimal implementation could be: + # + # class Person + # + # include ActiveModel::Dirty + # + # define_attribute_methods [:name] + # + # def name + # @name + # end + # + # def name=(val) + # name_will_change! + # @name = val + # end + # + # def save + # @previously_changed = changes + # end + # + # end + # + # == Examples: # # A newly instantiated object is unchanged: # person = Person.find_by_name('Uncle Bob') diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 871f589af92ea..9ceb75487fd96 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -57,13 +57,13 @@ module ClassMethods # # class Person < ActiveRecord::Base # validates_length_of :first_name, :maximum=>30 - # validates_length_of :last_name, :maximum=>30, :message=>"less than {{count}} if you don't mind" + # validates_length_of :last_name, :maximum=>30, :message=>"less than 30 if you don't mind" # validates_length_of :fax, :in => 7..32, :allow_nil => true # validates_length_of :phone, :in => 7..32, :allow_blank => true # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name" - # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least {{count}} character" - # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with {{count}} characters... don't play me." - # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least {{count}} words."), :tokenizer => lambda {|str| str.scan(/\w+/) } + # validates_length_of :zip_code, :minimum => 5, :too_short => "please enter at least 5 characters" + # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with 4 characters... don't play me." + # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words."), :tokenizer => lambda {|str| str.scan(/\w+/) } # end # # Configuration options: diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3034e9d2375ea..468a6cd9f833c 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -774,13 +774,12 @@ module ClassMethods # [collection.build(attributes = {}, ...)] # Returns one or more new objects of the collection type that have been instantiated # with +attributes+ and linked to this object through a foreign key, but have not yet - # been saved. Note: This only works if an associated object already exists, not if - # it's +nil+! + # been saved. # [collection.create(attributes = {})] # Returns a new object of the collection type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that has already - # been saved (if it passed the validation). Note: This only works if an associated - # object already exists, not if it's +nil+! + # been saved (if it passed the validation). *Note*: This only works if the base model + # already exists in the DB, not if it is a new (unsaved) record! # # (*Note*: +collection+ is replaced with the symbol passed as the first argument, so # has_many :clients would add among others clients.empty?.) @@ -1040,7 +1039,6 @@ def has_one(association_id, options = {}) # A Post class declares belongs_to :author, which will add: # * Post#author (similar to Author.find(author_id)) # * Post#author=(author) (similar to post.author_id = author.id) - # * Post#author? (similar to post.author == some_author) # * Post#build_author (similar to post.author = Author.new) # * Post#create_author (similar to post.author = Author.new; post.author.save; post.author) # The declaration can also include an options hash to specialize the behavior of the association. diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 82e91a80ad3c0..4ee988718637d 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1724,10 +1724,10 @@ def attribute_condition(quoted_column_name, argument) # class Article < ActiveRecord::Base # def self.find_with_scope # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do - # with_scope(:find => { :limit => 10 }) + # with_scope(:find => { :limit => 10 }) do # find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10 # end - # with_scope(:find => { :conditions => "author_id = 3" }) + # with_scope(:find => { :conditions => "author_id = 3" }) do # find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1 # end # end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index adc675966aa2e..79f70f07cdba6 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -335,7 +335,6 @@ class FixtureClassNotFound < StandardError #:nodoc: # george: # id: 1 # name: George the Monkey -# pirate_id: 1 # # ### in fruits.yml # @@ -370,8 +369,8 @@ class FixtureClassNotFound < StandardError #:nodoc: # ### in monkeys.yml # # george: +# id: 1 # name: George the Monkey -# pirate: reginald # fruits: apple, orange, grape # # ### in fruits.yml diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index aeeed24881403..c059b2d18b812 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1,7 +1,8 @@ require 'active_support/core_ext/object/metaclass' module ActiveRecord - class IrreversibleMigration < ActiveRecordError#:nodoc: + # Exception that can be raised to stop migrations from going backwards. + class IrreversibleMigration < ActiveRecordError end class DuplicateMigrationVersionError < ActiveRecordError#:nodoc: diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index f79a12a95cd0d..d8fae495e7b3f 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -49,14 +49,14 @@ class TooManyRecords < ActiveRecordError # create the member and avatar in one go: # # params = { :member => { :name => 'Jack', :avatar_attributes => { :icon => 'smiling' } } } - # member = Member.create(params) + # member = Member.create(params[:member]) # member.avatar.id # => 2 # member.avatar.icon # => 'smiling' # # It also allows you to update the avatar through the member: # - # params = { :member' => { :avatar_attributes => { :id => '2', :icon => 'sad' } } } - # member.update_attributes params['member'] + # params = { :member => { :avatar_attributes => { :id => '2', :icon => 'sad' } } } + # member.update_attributes params[:member] # member.avatar.icon # => 'sad' # # By default you will only be able to set and update attributes on the @@ -75,7 +75,7 @@ class TooManyRecords < ActiveRecordError # member.avatar_attributes = { :id => '2', :_destroy => '1' } # member.avatar.marked_for_destruction? # => true # member.save - # member.avatar #=> nil + # member.reload.avatar #=> nil # # Note that the model will _not_ be destroyed until the parent is saved. # @@ -179,7 +179,7 @@ class TooManyRecords < ActiveRecordError # member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true # member.posts.length #=> 2 # member.save - # member.posts.length # => 1 + # member.reload.posts.length # => 1 # # === Saving # diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index a6243e7011dee..b841108bd1109 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -42,6 +42,13 @@ module ActiveResource # self.element_name = "person" # end # + # If your Active Resource object is required to use an HTTP proxy you can set the +proxy+ value which holds a URI. + # + # class PersonResource < ActiveResource::Base + # self.site = "http://api.people.com:3000/" + # self.proxy = "http://user:password@proxy.people.com:8080" + # end + # # # == Lifecycle methods # diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 8719dc0e3f7f8..6727eda811e7c 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -531,7 +531,7 @@ def reset_callbacks(symbol) # to change this behavior. # # * :scope - Show which methods should be executed when a class - # is giben as callback: + # is given as callback: # # define_callbacks :filters, :scope => [ :kind ] # diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index d7606bb9b00cb..c69a015f12471 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -4,7 +4,7 @@ class Array # %w( a b c d ).from(0) # => %w( a b c d ) # %w( a b c d ).from(2) # => %w( c d ) # %w( a b c d ).from(10) # => nil - # %w().from(0) # => nil + # %w().from(0) # => %w() def from(position) self[position..-1] end diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index f48d5ce500f0e..e211bdeeca89a 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -1,6 +1,15 @@ class Array # Wraps the object in an Array unless it's an Array. Converts the # object to an Array using #to_ary if it implements that. + # + # It differs with Array() in that it does not call +to_a+ on + # the argument: + # + # Array(:foo => :bar) # => [[:foo, :bar]] + # Array.wrap(:foo => :bar) # => [{:foo => :bar}] + # + # Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8 + # Array.wrap("foo\nbar") # => ["foo\nbar"] def self.wrap(object) if object.nil? [] diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index cfd840fb93e83..48b185d05e024 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -69,6 +69,54 @@ def content_type ) end + # Returns a string containing an XML representation of its receiver: + # + # {"foo" => 1, "bar" => 2}.to_xml + # # => + # # + # # + # # 1 + # # 2 + # # + # + # To do so, the method loops over the pairs and builds nodes that depend on + # the _values_. Given a pair +key+, +value+: + # + # * If +value+ is a hash there's a recursive call with +key+ as :root. + # + # * If +value+ is an array there's a recursive call with +key+ as :root, + # and +key+ singularized as :children. + # + # * If +value+ is a callable object it must expect one or two arguments. Depending + # on the arity, the callable is invoked with the +options+ hash as first argument + # with +key+ as :root, and +key+ singularized as second argument. Its + # return value becomes a new node. + # + # * If +value+ responds to +to_xml+ the method is invoked with +key+ as :root. + # + # * Otherwise, a node with +key+ as tag is created with a string representation of + # +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added. + # Unless the option :skip_types exists and is true, an attribute "type" is + # added as well according to the following mapping: + # + # XML_TYPE_NAMES = { + # "Symbol" => "symbol", + # "Fixnum" => "integer", + # "Bignum" => "integer", + # "BigDecimal" => "decimal", + # "Float" => "float", + # "TrueClass" => "boolean", + # "FalseClass" => "boolean", + # "Date" => "date", + # "DateTime" => "datetime", + # "Time" => "datetime" + # } + # + # By default the root node is "hash", but that's configurable via the :root option. + # + # The default XML builder is a fresh instance of Builder::XmlMarkup. You can + # configure your own builder with the :builder option. The method also accepts + # options like :dasherize and friends, they are forwarded to the builder. def to_xml(options = {}) require 'builder' unless defined?(Builder) diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb index 6d04cb5621097..207801d3a7f22 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -3,6 +3,14 @@ class Hash # limiting a set of parameters to everything but a few known toggles: # # @person.update_attributes(params[:person].except(:admin)) + # + # If the receiver responds to +convert_key+, the method is called on each of the + # arguments. This allows +except+ to play nice with hashes with indifferent access + # for instance: + # + # {:a => 1}.with_indifferent_access.except(:a) # => {} + # {:a => 1}.with_indifferent_access.except("a") # => {} + # def except(*keys) dup.except!(*keys) end diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index b30e1602b6241..0420e206af027 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -1,6 +1,11 @@ require 'active_support/hash_with_indifferent_access' class Hash + + # Returns an +ActiveSupport::HashWithIndifferentAccess+ out of its receiver: + # + # {:a => 1}.with_indifferent_access["a"] # => 1 + # def with_indifferent_access hash = ActiveSupport::HashWithIndifferentAccess.new(self) hash.default = self.default diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index ffaa69570f3ee..ecd63293b4f8e 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -15,7 +15,8 @@ def stringify_keys! self end - # Return a new hash with all keys converted to symbols. + # Return a new hash with all keys converted to symbols, as long as + # they respond to +to_sym+. def symbolize_keys inject({}) do |options, (key, value)| options[(key.to_sym rescue key) || key] = value @@ -23,7 +24,8 @@ def symbolize_keys end end - # Destructively convert all keys to symbols. + # Destructively convert all keys to symbols, as long as they respond + # to +to_sym+. def symbolize_keys! self.replace(self.symbolize_keys) end diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index a2d4d50076a6e..b05325790cd74 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -1,3 +1,19 @@ +# Most objects are cloneable, but not all. For example you can't dup +nil+: +# +# nil.dup # => TypeError: can't dup NilClass +# +# Classes may signal their instances are not duplicable removing +dup+/+clone+ +# or raising exceptions from them. So, to dup an arbitrary object you normally +# use an optimistic approach and are ready to catch an exception, say: +# +# arbitrary_object.dup rescue object +# +# Rails dups objects in a few critical spots where they are not that arbitrary. +# That rescue is very expensive (like 40 times slower than a predicate), and it +# is often triggered. +# +# That's why we hardcode the following cases and check duplicable? instead of +# using that rescue idiom. class Object # Can you safely .dup this object? # False for nil, false, true, symbols, numbers, class and module objects; true otherwise. diff --git a/activesupport/lib/active_support/core_ext/rexml.rb b/activesupport/lib/active_support/core_ext/rexml.rb index 5288b639a6b20..0419ebc84b144 100644 --- a/activesupport/lib/active_support/core_ext/rexml.rb +++ b/activesupport/lib/active_support/core_ext/rexml.rb @@ -2,7 +2,10 @@ # Fixes the rexml vulnerability disclosed at: # http://www.ruby-lang.org/en/news/2008/08/23/dos-vulnerability-in-rexml/ -# This fix is identical to rexml-expansion-fix version 1.0.1 +# This fix is identical to rexml-expansion-fix version 1.0.1. +# +# We still need to distribute this fix because albeit the REXML +# in recent 1.8.7s is patched, it wasn't in early patchlevels. require 'rexml/rexml' # Earlier versions of rexml defined REXML::Version, newer ones REXML::VERSION diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index 182b3e745d323..64bc8f6ceaa11 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -7,7 +7,7 @@ class String # Examples: # "hello".at(0) # => "h" # "hello".at(4) # => "o" - # "hello".at(10) # => nil + # "hello".at(10) # => ERROR if < 1.9, nil in 1.9 def at(position) mb_chars[position, 1].to_s end @@ -17,7 +17,7 @@ def at(position) # Examples: # "hello".from(0) # => "hello" # "hello".from(2) # => "llo" - # "hello".from(10) # => nil + # "hello".from(10) # => "" if < 1.9, nil in 1.9 def from(position) mb_chars[position..-1].to_s end diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index c7225fec06bd8..3eb0bf31f8a58 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -385,7 +385,7 @@ def ord # Convert characters in the string to uppercase. # # Example: - # 'Laurent, òu sont les tests?'.mb_chars.upcase.to_s #=> "LAURENT, ÒU SONT LES TESTS?" + # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s #=> "LAURENT, OÙ SONT LES TESTS ?" def upcase apply_mapping :uppercase_mapping end diff --git a/railties/README b/railties/README index 221e8486d175e..64dea89585dfc 100644 --- a/railties/README +++ b/railties/README @@ -1,7 +1,7 @@ == Welcome to Rails -Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Control pattern. +Rails is a web-application framework that includes everything needed to create +database-backed web applications according to the Model-View-Control pattern. This pattern splits the view (also called the presentation) into "dumb" templates that are primarily responsible for inserting pre-built data in between HTML tags. @@ -46,9 +46,9 @@ getting up and running with mongrel is as easy as: gem install mongrel. More info at: http://mongrel.rubyforge.org Other ruby web servers exist which can run your rails application, however script/server does -not search for them or start them. These include Thin, Ebb, and Apache with mod_rails. +not search for them or start them. These include {Thin}[http://code.macournoyer.com/thin/], {Ebb}[http://ebb.rubyforge.org/], and Apache with {mod_rails}[http://www.modrails.com/]. -For production use, often a web/proxy server such as Apache, LiteSpeed, Lighttpd or IIS is +For production use, often a web/proxy server such as {Apache}[http://apache.org], {Nginx}[http://nginx.net/], {LiteSpeed}[http://litespeedtech.com/], {Lighttpd}[http://www.lighttpd.net/] or {IIS}[http://www.iis.net/] is deployed as the front-end server, with the chosen ruby web server running in the back-end and receiving the proxied requests via one of several protocols (HTTP, CGI, FCGI). @@ -131,7 +131,7 @@ and also on programming in general. Debugger support is available through the debugger command when you start your Mongrel or Webrick server with --debugger. This means that you can break out of execution at any point -in the code, investigate and change the model, AND then resume execution! +in the code, investigate and change the model, AND then resume execution! You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug' Example: @@ -163,13 +163,20 @@ Finally, when you're ready to resume execution, you enter "cont" == Console -You can interact with the domain model by starting the console through script/console. -Here you'll have all parts of the application configured, just like it is when the +The console is a ruby shell, which allows you to interact with your application's domain +model. Here you'll have all parts of the application configured, just like it is when the application is running. You can inspect domain models, change values, and save to the database. Starting the script without arguments will launch it in the development environment. -Passing an argument will specify a different environment, like script/console production. -To reload your controllers and models after launching the console run reload! +To start the console, just run script/console from the application directory. + +Options: + +* Passing the -s, --sandbox argument will rollback any modifications made to the database. +* Passing an environment name as an argument will load the corresponding environment. + Example: script/console production. + +More information about irb can be found at link:http://www.rubycentral.com/pickaxe/irb.html == dbconsole @@ -181,6 +188,43 @@ Currently works for mysql, postgresql and sqlite. == Description of Contents +The default directory structure of a generated Ruby on Rails applicartion: + + |-- app + | |-- controllers + | |-- helpers + | |-- models + | `-- views + | `-- layouts + |-- config + | |-- environments + | |-- initializers + | `-- locales + |-- db + |-- doc + |-- lib + | `-- tasks + |-- log + |-- public + | |-- images + | |-- javascripts + | `-- stylesheets + |-- script + | `-- performance + |-- test + | |-- fixtures + | |-- functional + | |-- integration + | |-- performance + | `-- unit + |-- tmp + | |-- cache + | |-- pids + | |-- sessions + | `-- sockets + `-- vendor + `-- plugins + app Holds all the code that's specific to this particular application. diff --git a/railties/guides/rails_guides.rb b/railties/guides/rails_guides.rb index e0532812e47b5..0d9458bf0f0be 100644 --- a/railties/guides/rails_guides.rb +++ b/railties/guides/rails_guides.rb @@ -1,5 +1,8 @@ pwd = File.dirname(__FILE__) -$: << pwd +$:.unshift pwd + +# Loading Action Pack requires rack and erubis. +require 'rubygems' begin as_lib = File.join(pwd, "../../activesupport/lib") @@ -11,7 +14,6 @@ require "action_controller" require "action_view" rescue LoadError - require 'rubygems' gem "actionpack", '>= 2.3' require "action_controller" @@ -19,7 +21,6 @@ end begin - require 'rubygems' gem 'RedCloth', '>= 4.1.1' rescue Gem::LoadError $stderr.puts %(Generating Guides requires RedCloth 4.1.1+) @@ -29,11 +30,11 @@ require 'redcloth' module RailsGuides - autoload :Generator, "rails_guides/generator" - autoload :Indexer, "rails_guides/indexer" - autoload :Helpers, "rails_guides/helpers" + autoload :Generator, "rails_guides/generator" + autoload :Indexer, "rails_guides/indexer" + autoload :Helpers, "rails_guides/helpers" autoload :TextileExtensions, "rails_guides/textile_extensions" - autoload :Levenshtein, "rails_guides/levenshtein" + autoload :Levenshtein, "rails_guides/levenshtein" end RedCloth.send(:include, RailsGuides::TextileExtensions) diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb index e3df3be8824ec..bd25111405fbd 100644 --- a/railties/guides/rails_guides/generator.rb +++ b/railties/guides/rails_guides/generator.rb @@ -1,5 +1,11 @@ require 'set' +class String + def html_safe! + self + end unless "post 9415935902f120a9bac0bfce7129725a0db38ed3".respond_to?(:html_safe!) +end + module RailsGuides class Generator attr_reader :output, :view_path, :view, :guides_dir @@ -18,7 +24,7 @@ def initialize(output = nil) end def generate - guides = Dir.entries(view_path).find_all {|g| g =~ /textile$/ } + guides = Dir.entries(view_path).find_all {|g| g =~ /\.textile(?:\.erb)?$/ } if ENV["ONLY"] only = ENV["ONLY"].split(",").map{|x| x.strip }.map {|o| "#{o}.textile" } @@ -36,7 +42,7 @@ def generate end def generate_guide(guide) - guide =~ /(.*?)(\.erb)?\.textile/ + guide =~ /(.*?)\.textile(?:\.erb)?$/ name = $1 puts "Generating #{name}" @@ -46,7 +52,7 @@ def generate_guide(guide) @view = ActionView::Base.new(view_path) @view.extend(Helpers) - if guide =~ /\.erb\.textile/ + if guide =~ /\.textile\.erb$/ # Generate the erb pages with textile formatting - e.g. index/authors result = view.render(:layout => 'layout', :file => guide) f.write textile(result) @@ -55,7 +61,7 @@ def generate_guide(guide) body = set_header_section(body, @view) body = set_index(body, @view) - result = view.render(:layout => 'layout', :text => textile(body)) + result = view.render(:layout => 'layout', :text => textile(body).html_safe!) f.write result warn_about_broken_links(result) if ENV.key?("WARN_BROKEN_LINKS") end diff --git a/railties/guides/source/2_2_release_notes.textile b/railties/guides/source/2_2_release_notes.textile index 15a7bdbd44598..78f28d1d76c1e 100644 --- a/railties/guides/source/2_2_release_notes.textile +++ b/railties/guides/source/2_2_release_notes.textile @@ -18,7 +18,7 @@ Rails 2.2 supplies an easy system for internationalization (or i18n, for those o * More information : ** "Official Rails i18 website":http://rails-i18n.org ** "Finally. Ruby on Rails gets internationalized":http://www.artweb-design.de/2008/7/18/finally-ruby-on-rails-gets-internationalized -** "Localizing Rails : Demo application":http://i18n-demo.phusion.nl +** "Localizing Rails : Demo application":http://github.com/clemens/i18n_demo_app h4. Compatibility with Ruby 1.9 and JRuby @@ -28,20 +28,20 @@ h3. Documentation The internal documentation of Rails, in the form of code comments, has been improved in numerous places. In addition, the "Ruby on Rails Guides":http://guides.rubyonrails.org/ project is the definitive source for information on major Rails components. In its first official release, the Guides page includes: -* "Getting Started with Rails":http://guides.rubyonrails.org/getting_started_with_rails.html +* "Getting Started with Rails":http://guides.rubyonrails.org/getting_started.html * "Rails Database Migrations":http://guides.rubyonrails.org/migrations.html * "Active Record Associations":http://guides.rubyonrails.org/association_basics.html -* "Active Record Finders":http://guides.rubyonrails.org/finders.html +* "Active Record Query Interface":http://guides.rubyonrails.org/active_record_querying.html * "Layouts and Rendering in Rails":http://guides.rubyonrails.org/layouts_and_rendering.html * "Action View Form Helpers":http://guides.rubyonrails.org/form_helpers.html -* "Rails Routing from the Outside In":http://guides.rubyonrails.org/routing_outside_in.html -* "Basics of Action Controller":http://guides.rubyonrails.org/actioncontroller_basics.html +* "Rails Routing from the Outside In":http://guides.rubyonrails.org/routing.html +* "Action Controller Overview":http://guides.rubyonrails.org/action_controller_overview.html * "Rails Caching":http://guides.rubyonrails.org/caching_with_rails.html -* "Testing Rails Applications":http://guides.rubyonrails.org/testing_rails_applications.html +* "A Guide to Testing Rails Applications":http://guides.rubyonrails.org/testing.html * "Securing Rails Applications":http://guides.rubyonrails.org/security.html * "Debugging Rails Applications":http://guides.rubyonrails.org/debugging_rails_applications.html -* "Benchmarking and Profiling Rails Applications":http://guides.rubyonrails.org/benchmarking_and_profiling.html -* "The Basics of Creating Rails Plugins":http://guides.rubyonrails.org/creating_plugins.html +* "Performance Testing Rails Applications":http://guides.rubyonrails.org/performance_testing.html +* "The Basics of Creating Rails Plugins":http://guides.rubyonrails.org/plugins.html All told, the Guides provide tens of thousands of words of guidance for beginning and intermediate Rails developers. @@ -229,7 +229,7 @@ This will enable recognition of (among others) these routes: * Lead Contributor: "S. Brent Faulkner":http://www.unwwwired.net/ * More information: -** "Rails Routing from the Outside In":http://guides.rails.info/routing/routing_outside_in.html#_nested_resources +** "Rails Routing from the Outside In":http://guides.rubyonrails.org/routing.html#_nested_resources ** "What's New in Edge Rails: Shallow Routes":http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-shallow-routes h4. Method Arrays for Member or Collection Routes diff --git a/railties/guides/source/2_3_release_notes.textile b/railties/guides/source/2_3_release_notes.textile index bb2998fbdfef6..b864c5dc79512 100644 --- a/railties/guides/source/2_3_release_notes.textile +++ b/railties/guides/source/2_3_release_notes.textile @@ -81,7 +81,7 @@ accepts_nested_attributes_for :author, :reject_if => proc { |attributes| attributes['name'].blank? } -* Lead Contributor: "Eloy Duran":http://www.superalloy.nl/blog/ +* Lead Contributor: "Eloy Duran":http://superalloy.nl/ * More Information: "Nested Model Forms":http://weblog.rubyonrails.org/2009/1/26/nested-model-forms h4. Nested Transactions @@ -365,10 +365,10 @@ You can write this view in Rails 2.3: <% end %> -* Lead Contributor: "Eloy Duran":http://www.superalloy.nl/blog/ +* Lead Contributor: "Eloy Duran":http://superalloy.nl/ * More Information: ** "Nested Model Forms":http://weblog.rubyonrails.org/2009/1/26/nested-model-forms -** "complex-form-examples":http://github.com/alloy/complex-form-examples/tree/nested_attributes +** "complex-form-examples":http://github.com/alloy/complex-form-examples ** "What's New in Edge Rails: Nested Object Forms":http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes h4. Smart Rendering of Partials diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile index 2e7f0e7fedf28..931ebe8a34023 100644 --- a/railties/guides/source/action_mailer_basics.textile +++ b/railties/guides/source/action_mailer_basics.textile @@ -402,8 +402,6 @@ Testing mailers normally involves two things: One is that the mail was queued, a class UserMailerTest < ActionMailer::TestCase - tests UserMailer - def test_welcome_email user = users(:some_user_in_your_fixtures) @@ -412,7 +410,7 @@ class UserMailerTest < ActionMailer::TestCase assert !ActionMailer::Base.deliveries.empty? # Test the body of the sent email contains what we expect it to - assert_equal [@user.email], email.to + assert_equal [user.email], email.to assert_equal "Welcome to My Awesome Site", email.subject assert_match /Welcome to example.com, #{user.first_name}/, email.body end diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index ebc267ab713d2..4aa43a8f3c259 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -2,13 +2,21 @@ h2. Action View Overview In this guide you will learn: -* What Action View is, and how to use it +* What Action View is, and how to use it with Rails +* How to use Action View outside of Rails +* How best to use templates, partials, and layouts +* What helpers are provided by Action View, and how to make your own +* How to use localized views endprologue. h3. What is Action View? -TODO... +Action View and Action Controller are the two major components of Action Pack. In Rails, web requests are handled by Action Pack, which splits the work into a controller part (performing the logic) and a view part (rendering a template). Typically, Action Controller will be concerned with communicating with the database and performing CRUD actions where necessary. Action View is then responsible for compiling the response. + +Action View templates are written using embedded Ruby in tags mingled with HTML. To avoid cluttering the templates with boilerplate code, a number of helper classes provide common behavior for forms, dates, and strings. It's also easy to add new helpers to your application as it evolves. + +Note: Some features of Action View are tied to Active Record, but that doesn't mean that Action View depends on Active Record. Action View is an independent package that can be used with any sort of backend. h3. Using Action View with Rails @@ -16,35 +24,1438 @@ TODO... h3. Using Action View outside of Rails -TODO... +Action View works well with Action Record, but it can also be used with other Ruby tools. We can demonstrate this by creating a small "Rack":http://rack.rubyforge.org/ application that includes Action View functionality. This may be useful, for example, if you'd like access to Action View's helpers in a Rack application. + +Let's start by ensuring that you have the Action Pack and Rack gems installed: + + +gem install actionpack +gem install rack + + +Now we'll create a simple "Hello World" application that uses the +titleize+ method provided by Action View. + +*hello_world.rb:* + + +require 'rubygems' +require 'action_view' +require 'rack' + +def hello_world(env) + [200, {"Content-Type" => "text/html"}, "hello world".titleize] +end + +Rack::Handler::Mongrel.run method(:hello_world), :Port => 4567 + + +We can see this all come together by starting up the application and then visiting +http://localhost:4567/+ + + +ruby hello_world.rb + + +TODO needs a screenshot? I have one - not sure where to put it. + +Notice how 'hello world' has been converted into 'Hello World' by the +titleize+ helper method. + +Action View can also be used with "Sinatra":http://www.sinatrarb.com/ in the same way. + +Let's start by ensuring that you have the Action Pack and Sinatra gems installed: + + +gem install actionpack +gem install sinatra + + +Now we'll create the same "Hello World" application in Sinatra. + +*hello_world.rb:* + + +require 'rubygems' +require 'action_view' +require 'sinatra' + +get '/' do + erb 'hello world'.titleize +end + + +Then, we can run the application: + + +ruby hello_world.rb + + +Once the application is running, you can see Sinatra and Action View working together by visiting +http://localhost:4567/+ + +TODO needs a screenshot? I have one - not sure where to put it. h3. Templates, Partials and Layouts TODO... +TODO see http://guides.rubyonrails.org/layouts_and_rendering.html + h3. Using Templates, Partials and Layouts in "The Rails Way" TODO... h3. Partial Layouts -TODO... +Partials can have their own layouts applied to them. These layouts are different than the ones that are specified globally for the entire action, but they work in a similar fashion. + +Let's say we're displaying a post on a page where it should be wrapped in a +div+ for display purposes. First, we'll create a new +Post+: + + +Post.create(:body => 'Partial Layouts are cool!') + + +In the +show+ template, we'll render the +post+ partial wrapped in the +box+ layout: + +*posts/show.html.erb* + + +<%= render :partial => 'post', :layout => 'box', :locals => {:post => @post} %> + + +The +box+ layout simply wraps the +post+ partial in a +div+: + +*posts/_box.html.erb* + + +

+ <%= yield %> +
+ + +The +post+ partial wraps the post's +body+ in a +div+ with the +id+ of the post using the +div_for+ helper: + +*posts/_post.html.erb* + + +<% div_for(post) do %> +

<%= post.body %>

+<% end %> +
+ +This example would output the following: + + +
+
+

Partial Layouts are cool!

+
+
+ + +Note that the partial layout has access to the local +post+ variable that was passed into the +render+ call. However, unlike application-wide layouts, partial layouts still have the underscore prefix. + +You can also render a block of code within a partial layout instead of calling +yield+. For example, if we didn't have the +post+ partial, we could do this instead: + +*posts/show.html.erb* + + +<% render(:layout => 'box', :locals => {:post => @post}) do %> + <% div_for(post) do %> +

<%= post.body %>

+ <% end %> +<% end %> +
+ +If we're using the same +box+ partial from above, his would produce the same output as the previous example. h3. View Paths TODO... -h3. Overview of all the helpers provided by AV +h3. Overview of all the helpers provided by Action View -TODO... +The following is only a brief overview summary of the helpers available in Action View. It's recommended that you review the API Documentation, which covers all of the helpers in more detail, but this should serve as a good starting point. -h3. Localized Views +h4. ActiveRecordHelper -Action View has the ability render different templates depending on the current locale. +The Active Record Helper makes it easier to create forms for records kept in instance variables. You may also want to review the "Rails Form helpers guide":form_helpers.html. -For example, suppose you have a Posts controller with a show action. By default, calling this action will render +app/views/posts/show.html.erb+. But if you set +I18n.locale = :de+, then +app/views/posts/show.de.html.erb+ will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available. +h5. error_message_on + +Returns a string containing the error message attached to the method on the object if one exists. + + +error_message_on "post", "title" + + +h5. error_messages_for + +Returns a string with a DIV containing all of the error messages for the objects located as instance variables by the names given. + + +error_messages_for "post" + + +h5. form + +Returns a form with inputs for all attributes of the specified Active Record object. For example, let's say we have a +@post+ with attributes named +title+ of type +String+ and +body+ of type +Text+. Calling +form+ would produce a form to creating a new post with inputs for those attributes. + + +form("post") + + + +
+

+
+ +

+

+
+ +

+ +
+ + +Typically, +form_for+ is used instead of +form+ because it doesn't automatically include all of the model's attributes. + +h5. input + +Returns a default input tag for the type of object returned by the method. + +For example, if +@post+ has an attribute +title+ mapped to a +String+ column that holds "Hello World": + + +input("post", "title") # => + + + +h4. AssetTagHelper + +This module provides methods for generating HTML that links views to assets such as images, javascripts, stylesheets, and feeds. + +By default, Rails links to these assets on the current host in the public folder, but you can direct Rails to link to assets from a dedicated assets server by setting +ActionController::Base.asset_host+ in your +config/environment.rb+. For example, let's say your asset host is +assets.example.com+: + + +ActionController::Base.asset_host = "assets.example.com" +image_tag("rails.png") # => Rails + + +h5. register_javascript_expansion + +Register one or more javascript files to be included when symbol is passed to javascript_include_tag. This method is typically intended to be called from plugin initialization to register javascript files that the plugin installed in public/javascripts. + + +ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"] + +javascript_include_tag :monkey # => + + + + + +h5. register_javascript_include_default + +Register one or more additional JavaScript files to be included when +javascript_include_tag :defaults+ is called. This method is typically intended to be called from plugin initialization to register additional +.js+ files that the plugin installed in +public/javascripts+. + +h5. register_stylesheet_expansion + +Register one or more stylesheet files to be included when symbol is passed to +stylesheet_link_tag+. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in +public/stylesheets+. + + +ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"] + +stylesheet_link_tag :monkey # => + + + + + +h5. auto_discovery_link_tag + +Returns a link tag that browsers and news readers can use to auto-detect an RSS or ATOM feed. + + +auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "RSS Feed"}) # => + + + +h5. image_path + +Computes the path to an image asset in the public images directory. Full paths from the document root will be passed through. Used internally by +image_tag+ to build the image path. + + +image_path("edit.png") # => /images/edit.png + + +h5. image_tag + +Returns an html image tag for the source. The source can be a full path or a file that exists in your public images directory. + + +image_tag("icon.png") # => Icon + + +h5. javascript_include_tag + +Returns an html script tag for each of the sources provided. You can pass in the filename (+.js+ extension is optional) of javascript files that exist in your +public/javascripts+ directory for inclusion into the current page or you can pass the full path relative to your document root. + + +javascript_include_tag "common" # => + + + +To include the Prototype and Scriptaculous javascript libraries in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +public/javascripts+ directory, it will be included as well. + + +javascript_include_tag :defaults + + +You can also include all javascripts in the javascripts directory using +:all+ as the source. + + +javascript_include_tag :all + + +You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be compressed by gzip (leading to faster transfers). Caching will only happen if +ActionController::Base.perform_caching+ is set to true (which is the case by default for the Rails production environment, but not for the development environment). + + +javascript_include_tag :all, :cache => true # => + + + +h5. javascript_path + +Computes the path to a javascript asset in the +public/javascripts+ directory. If the source filename has no extension, +.js+ will be appended. Full paths from the document root will be passed through. Used internally by +javascript_include_tag+ to build the script path. + + +javascript_path "common" # => /javascripts/common.js + + +h5. stylesheet_link_tag + +Returns a stylesheet link tag for the sources specified as arguments. If you don't specify an extension, +.css+ will be appended automatically. + + +stylesheet_link_tag "application" # => + + + +You can also include all styles in the stylesheet directory using :all as the source: + + +stylesheet_link_tag :all + + +You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching is set to true (which is the case by default for the Rails production environment, but not for the development environment). + + +stylesheet_link_tag :all, :cache => true + + + +h5. stylesheet_path + +Computes the path to a stylesheet asset in the public stylesheets directory. If the source filename has no extension, .css will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path. + + +stylesheet_path "application" # => /stylesheets/application.css + + +h4. AtomFeedHelper + +h5. atom_feed + +This helper makes building an ATOM feed easy. Here's a full usage example: + +*config/routes.rb* + + +map.resources :posts + + +*app/controllers/posts_controller.rb* + + +def index + @posts = Post.find(:all) + + respond_to do |format| + format.html + format.atom + end +end + + +*app/views/posts/index.atom.builder* + + +atom_feed do |feed| + feed.title("Posts Index") + feed.updated((@posts.first.created_at)) + + for post in @posts + feed.entry(post) do |entry| + entry.title(post.title) + entry.content(post.body, :type => 'html') + + entry.author do |author| + author.name(post.author_name) + end + end + end +end + + +h4. BenchmarkHelper + +h5. benchmark + +Allows you to measure the execution time of a block in a template and records the result to the log. Wrap this block around expensive operations or possible bottlenecks to get a time reading for the operation. + + +<% benchmark "Process data files" do %> + <%= expensive_files_operation %> +<% end %> + + +This would add something like "Process data files (0.34523)" to the log, which you can then use to compare timings when optimizing your code. + +h4. CacheHelper + +h5. cache + +A method for caching fragments of a view rather than an entire action or page. This technique is useful caching pieces like menus, lists of news topics, static HTML fragments, and so on. This method takes a block that contains the content you wish to cache. See +ActionController::Caching::Fragments+ for more information. + + +<% cache do %> + <%= render :partial => "shared/footer" %> +<% end %> + + +h4. CaptureHelper + +h5. capture + +The +capture+ method allows you to extract part of a template into a variable. You can then use this variable anywhere in your templates or layout. + + +<% @greeting = capture do %> +

Welcome! The date and time is <%= Time.now %>

+<% end %> + + +The captured variable can then be used anywhere else. + + + + + Welcome! + + + <%= @greeting %> + + + + +h5. content_for + +Calling +content_for+ stores a block of markup in an identifier for later use. You can make subsequent calls to the stored content in other templates or the layout by passing the identifier as an argument to +yield+. + +For example, let's say we have a standard application layout, but also a special page that requires certain Javascript that the rest of the site doesn't need. We can use +content_for+ to include this Javascript on our special page without fattening up the rest of the site. + +*app/views/layouts/application.html.erb* + + + + + Welcome! + <%= yield :special_script %> + + +

Welcome! The date and time is <%= Time.now %>

+ + +
+ +*app/views/posts/special.html.erb* + + +

This is a special page.

+ +<% content_for :special_script do %> + +<% end %> +
+ +h4. DateHelper + +h5. date_select + +Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute. + + +date_select("post", "published_on") + + +h5. datetime_select + +Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based attribute. + + +datetime_select("post", "published_on") + + +h5. distance_of_time_in_words + +Reports the approximate distance in time between two Time or Date objects or integers as seconds. Set +include_seconds+ to true if you want more detailed approximations. + + +distance_of_time_in_words(Time.now, Time.now + 15.seconds) # => less than a minute +distance_of_time_in_words(Time.now, Time.now + 15.seconds, true) # => less than 20 seconds + + +h5. select_date + +Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+ provided. + + +# Generates a date select that defaults to the date provided (six days after today) +select_date(Time.today + 6.days) + +# Generates a date select that defaults to today (no specified date) +select_date() + + +h5. select_datetime + +Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+ provided. + + +# Generates a datetime select that defaults to the datetime provided (four days after today) +select_datetime(Time.now + 4.days) + +# Generates a datetime select that defaults to today (no specified datetime) +select_datetime() + + +h5. select_day + +Returns a select tag with options for each of the days 1 through 31 with the current day selected. + + +# Generates a select field for days that defaults to the day for the date provided +select_day(Time.today + 2.days) + +# Generates a select field for days that defaults to the number given +select_day(5) + + +h5. select_hour + +Returns a select tag with options for each of the hours 0 through 23 with the current hour selected. + + +# Generates a select field for minutes that defaults to the minutes for the time provided +select_minute(Time.now + 6.hours) + -TODO add full code example... +h5. select_minute + +Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected. + + +# Generates a select field for minutes that defaults to the minutes for the time provided. +select_minute(Time.now + 6.hours) + + +h5. select_month + +Returns a select tag with options for each of the months January through December with the current month selected. + + +# Generates a select field for months that defaults to the current month +select_month(Date.today) + + +h5. select_second + +Returns a select tag with options for each of the seconds 0 through 59 with the current second selected. + + +# Generates a select field for seconds that defaults to the seconds for the time provided +select_second(Time.now + 16.minutes) + + +h5. select_time + +Returns a set of html select-tags (one for hour and minute). + + +# Generates a time select that defaults to the time provided +select_time(Time.now) + + +h5. select_year + +Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius can be changed using the +:start_year+ and +:end_year+ keys in the +options+. + + +# Generates a select field for five years on either side of +Date.today+ that defaults to the current year +select_year(Date.today) + +# Generates a select field from 1900 to 2009 that defaults to the current year +select_year(Date.today, :start_year => 1900, :end_year => 2009) + + +h5. time_ago_in_words + +Like +distance_of_time_in_words+, but where +to_time+ is fixed to +Time.now+. + + +time_ago_in_words(3.minutes.from_now) # => 3 minutes + + +h5. time_select + +Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified time-based attribute. The selects are prepared for multi-parameter assignment to an Active Record object. + + +# Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted attribute +time_select("order", "submitted") + + +h4. DebugHelper + +Returns a +pre+ tag that has object dumped by YAML. This creates a very readable way to inspect an object. + + +my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]} +debug(my_hash) + + + +
---
+first: 1
+second: two
+third:
+- 1
+- 2
+- 3
+
+ + +h4. FormHelper + +Form helpers are designed to make working with models much easier compared to using just standard HTML elements by providing a set of methods for creating forms based on your models. This helper generates the HTML for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form is submitted (i.e., when the user hits the submit button or form.submit is called via JavaScript), the form inputs will be bundled into the params object and passed back to the controller. + +There are two types of form helpers: those that specifically work with model attributes and those that don't. This helper deals with those that work with model attributes; to see an example of form helpers that don‘t work with model attributes, check the ActionView::Helpers::FormTagHelper documentation. + +The core method of this helper, form_for, gives you the ability to create a form for a model instance; for example, let's say that you have a model Person and want to create a new instance of it: + + +# Note: a @person variable will have been created in the controller (e.g. @person = Person.new) +<% form_for :person, @person, :url => { :action => "create" } do |f| %> + <%= f.text_field :first_name %> + <%= f.text_field :last_name %> + <%= submit_tag 'Create' %> +<% end %> + + +The HTML generated for this would be: + + +
+ + + +
+ + +The params object created when this form is submitted would look like: + + +{"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}} + + +The params hash has a nested person value, which can therefore be accessed with params[:person] in the controller. + +h5. check_box + +Returns a checkbox tag tailored for accessing a specified attribute. + + +# Let's say that @post.validated? is 1: +check_box("post", "validated") +# => +# + + +h5. fields_for + +Creates a scope around a specific model object like form_for, but doesn‘t create the form tags themselves. This makes fields_for suitable for specifying additional model objects in the same form: + + +<% form_for @person, :url => { :action => "update" } do |person_form| %> + First name: <%= person_form.text_field :first_name %> + Last name : <%= person_form.text_field :last_name %> + + <% fields_for @person.permission do |permission_fields| %> + Admin? : <%= permission_fields.check_box :admin %> + <% end %> +<% end %> + + +h5. file_field + +Returns an file upload input tag tailored for accessing a specified attribute. + + +file_field(:user, :avatar) +# => + + +h5. form_for + +Creates a form and a scope around a specific model object that is used as a base for questioning about values for the fields. + + +<% form_for @post do |f| %> + <%= f.label :title, 'Title' %>: + <%= f.text_field :title %>
+ <%= f.label :body, 'Body' %>: + <%= f.text_area :body %>
+<% end %> +
+ +h5. hidden_field + +Returns a hidden input tag tailored for accessing a specified attribute. + + +hidden_field(:user, :token) +# => + + +h5. label + +Returns a label tag tailored for labelling an input field for a specified attribute. + + +label(:post, :title) +# => + + +h5. password_field + +Returns an input tag of the "password" type tailored for accessing a specified attribute. + + +password_field(:login, :pass) +# => + + +h5. radio_button + +Returns a radio button tag for accessing a specified attribute. + + +# Let's say that @post.category returns "rails": +radio_button("post", "category", "rails") +radio_button("post", "category", "java") +# => +# + + +h5. text_area + +Returns a textarea opening and closing tag set tailored for accessing a specified attribute. + + +text_area(:comment, :text, :size => "20x30") +# => + + +h5. text_field + +Returns an input tag of the "text" type tailored for accessing a specified attribute. + + +text_field(:post, :title) +# => + + +h4. FormOptionsHelper + +Provides a number of methods for turning different kinds of containers into a set of option tags. + +h5. collection_select + +Returns +select+ and +option+ tags for the collection of existing return values of +method+ for +object+'s class. + +Example object structure for use with this method: + + +class Post < ActiveRecord::Base + belongs_to :author +end + +class Author < ActiveRecord::Base + has_many :posts + def name_with_initial + "#{first_name.first}. #{last_name}" + end +end + + +Sample usage (selecting the associated Author for an instance of Post, +@post+): + + +collection_select(:post, :author_id, Author.find(:all), :id, :name_with_initial, {:prompt => true}) + + +If @post.author_id is already 1, this would return: + + + + + +h5. country_options_for_select + +Returns a string of option tags for pretty much any country in the world. + +h5. country_select + +Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags. + +h5. option_groups_from_collection_for_select + +Returns a string of +option+ tags, like +options_from_collection_for_select+, but groups them by +optgroup+ tags based on the object relationships of the arguments. + +Example object structure for use with this method: + + +class Continent < ActiveRecord::Base + has_many :countries + # attribs: id, name +end + +class Country < ActiveRecord::Base + belongs_to :continent + # attribs: id, name, continent_id +end + + +Sample usage: + + +option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3) + + +TODO check above textile output looks right + +Possible output: + + + + + + ... + + + + + + ... + + + +Note: Only the +optgroup+ and +option+ tags are returned, so you still have to wrap the output in an appropriate +select+ tag. + +h5. options_for_select + +Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. + + +options_for_select([ "VISA", "MasterCard" ]) +# => + + +Note: Only the +option+ tags are returned, you have to wrap this call in a regular HTML +select+ tag. + +h5. options_from_collection_for_select + +Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the the result of a call to the +value_method+ as the option value and the +text_method+ as the option text. + + +# options_from_collection_for_select(collection, value_method, text_method, selected = nil) + + +For example, imagine a loop iterating over each person in @project.people to generate an input tag: + + +options_from_collection_for_select(@project.people, "id", "name") +# => + + +Note: Only the +option+ tags are returned, you have to wrap this call in a regular HTML +select+ tag. + +h5. select + +Create a select tag and a series of contained option tags for the provided object and method. + +Example with @post.person_id => 1: + + +select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, { :include_blank => true }) + + +could become: + + + + + +h5. time_zone_options_for_select + +Returns a string of option tags for pretty much any time zone in the world. + +h5. time_zone_select + +Return select and option tags for the given object and method, using +time_zone_options_for_select+ to generate the list of option tags. + + +time_zone_select( "user", "time_zone") + + +h4. FormTagHelper + +Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like FormHelper does. Instead, you provide the names and values manually. + +h5. check_box_tag + +Creates a check box form input tag. + + +check_box_tag 'accept' +# => + + +h5. field_set_tag + +Creates a field set for grouping HTML form elements. + + +<% field_set_tag do %> +

<%= text_field_tag 'name' %>

+<% end %> +# =>

+
+ +h5. file_field_tag + +Creates a file upload field. + +If you are using file uploads then you will also need to set the multipart option for the form tag: + + +<%= form_tag { :action => "post" }, { :multipart => true } %> + <%= file_field_tag "file" %> + <%= submit_tag %> +<%= end_form_tag %> + + +Example output: + + +file_field_tag 'attachment' +# => + + +h5. form_tag + +Starts a form tag that points the action to an url configured with +url_for_options+ just like +ActionController::Base#url_for+. + + +<% form_tag '/posts' do -%> +
<%= submit_tag 'Save' %>
+<% end -%> +# =>
+
+ +h5. hidden_field_tag + +Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or data that should be hidden from the user. + + +hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@' +# => + + +h5. image_submit_tag + +Displays an image which when clicked will submit the form. + + +image_submit_tag("login.png") +# => + + +h5. label_tag + +Creates a label field. + + +label_tag 'name' +# => + + +h5. password_field_tag + +Creates a password field, a masked text field that will hide the users input behind a mask character. + + +password_field_tag 'pass' +# => + + +h5. radio_button_tag + +Creates a radio button; use groups of radio buttons named the same to allow users to select from a group of options. + + +radio_button_tag 'gender', 'male' +# => + + +h5. select_tag + +Creates a dropdown selection box. + + +select_tag "people", "" +# => + + +h5. submit_tag + +Creates a submit button with the text provided as the caption. + + +submit_tag "Publish this post" +# => + + +h5. text_area_tag + +Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions. + + +text_area_tag 'post' +# => + + +h5. text_field_tag + +Creates a standard text field; use these text fields to input smaller chunks of text like a username or a search query. + + +text_field_tag 'name' +# => + + +h4. JavaScriptHelper + +Provides functionality for working with JavaScript in your views. + +Rails includes the Prototype JavaScript framework and the Scriptaculous JavaScript controls and visual effects library. If you wish to use these libraries and their helpers, make sure +<%= javascript_include_tag :defaults, :cache => true %>+ is in the HEAD section of your page. This function will include the necessary JavaScript files Rails generated in the public/javascripts directory. + +h5. button_to_function + +Returns a button that'll trigger a JavaScript function using the onclick handler. Examples: + + +button_to_function "Greeting", "alert('Hello world!')" +button_to_function "Delete", "if (confirm('Really?')) do_delete()" +button_to_function "Details" do |page| + page[:details].visual_effect :toggle_slide +end + + +h5. define_javascript_functions + +Includes the Action Pack JavaScript libraries inside a single +script+ tag. + +h5. escape_javascript + +Escape carrier returns and single and double quotes for JavaScript segments. + +h5. javascript_tag + +Returns a JavaScript tag wrapping the provided code. + + +javascript_tag "alert('All is good')" + + + + + + +h5. link_to_function + +Returns a link that will trigger a JavaScript function using the onclick handler and return false after the fact. + + +link_to_function "Greeting", "alert('Hello world!')" +# => Greeting + + +h4. NumberHelper + +Provides methods for converting numbers into formatted strings. Methods are provided for phone numbers, currency, percentage, precision, positional notation, and file size. + +h5. number_to_currency + +Formats a number into a currency string (e.g., $13.65). + + +number_to_currency(1234567890.50) # => $1,234,567,890.50 + + +h5. number_to_human_size + +Formats the bytes in size into a more understandable representation; useful for reporting file sizes to users. + + +number_to_human_size(1234) # => 1.2 KB +number_to_human_size(1234567) # => 1.2 MB + + +h5. number_to_percentage + +Formats a number as a percentage string. + + +number_to_percentage(100, :precision => 0) # => 100% + + +h5. number_to_phone + +Formats a number into a US phone number. + + +number_to_phone(1235551234) # => 123-555-1234 + + +h5. number_with_delimiter + +Formats a number with grouped thousands using a delimiter. + + +number_with_delimiter(12345678) # => 12,345,678 + + +h5. number_with_precision + +Formats a number with the specified level of +precision+, which defaults to 3. + + +number_with_precision(111.2345) # => 111.235 +number_with_precision(111.2345, 2) # => 111.23 + + +h4. PrototypeHelper + +Prototype is a JavaScript library that provides DOM manipulation, Ajax functionality, and more traditional object-oriented facilities for JavaScript. This module provides a set of helpers to make it more convenient to call functions from Prototype using Rails, including functionality to call remote Rails methods (that is, making a background request to a Rails action) using Ajax. + +To be able to use these helpers, you must first include the Prototype JavaScript framework in the HEAD of the pages with Prototype functions. + + +javascript_include_tag 'prototype' + + +h5. evaluate_remote_response + +Returns +eval(request.responseText)+ which is the JavaScript function that form_remote_tag can call in +:complete+ to evaluate a multiple update return document using +update_element_function+ calls. + +h5. form_remote_tag + +Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular reloading POST arrangement. Even though it‘s using JavaScript to serialize the form elements, the form submission will work just like a regular submission as viewed by the receiving side. + +For example, this: + + +form_remote_tag :html => { :action => url_for(:controller => "some", :action => "place") } + + +would generate the following: + + +
+ + +h5. link_to_remote + +Returns a link to a remote action that's called in the background using XMLHttpRequest. You can generate a link that uses AJAX in the general case, while degrading gracefully to plain link behavior in the absence of JavaScript. For example: + + +link_to_remote "Delete this post", + { :update => "posts", :url => { :action => "destroy", :id => post.id } }, + :href => url_for(:action => "destroy", :id => post.id) + + +h5. observe_field + +Observes the field specified and calls a callback when its contents have changed. + + +observe_field("my_field", :function => "alert('Field changed')") + + +h5. observe_form + +Observes the form specified and calls a callback when its contents have changed. The options for observe_form are the same as the options for observe_field. + + +observe_field("my_form", :function => "alert('Form changed')") + + +h5. periodically_call_remote + +Periodically calls the specified url as often as specified. Usually used to update a specified div with the results of the remote call. The following example will call update every 20 seconds and update the news_block div: + + +periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block') +# => PeriodicalExecuter(function() {new Ajax.Updater('news_block', 'update', {asynchronous:true, evalScripts:true})}, 20) + + +h5. remote_form_for + +Creates a form that will submit using XMLHttpRequest in the background instead of the regular reloading POST arrangement and a scope around a specific resource that is used as a base for questioning about values for the fields. + + +<% remote_form_for(@post) do |f| %> + ... +<% end %> + + +h5. remote_function + +Returns the JavaScript needed for a remote function. Takes the same arguments as +link_to_remote+. + + + +# => + + +h5. update_page + +Yields a JavaScriptGenerator and returns the generated JavaScript code. Use this to update multiple elements on a page in an Ajax response. + + +update_page do |page| + page.hide 'spinner' +end + + +h5. update_page_tag + +Works like update_page but wraps the generated JavaScript in a +script+ tag. Use this to include generated JavaScript in an ERb template. + +h4. PrototypeHelper::JavaScriptGenerator::GeneratorMethods + +JavaScriptGenerator generates blocks of JavaScript code that allow you to change the content and presentation of multiple DOM elements. Use this in your Ajax response bodies, either in a +script+ tag or as plain JavaScript sent with a Content-type of "text/javascript". + +h5. << + +Writes raw JavaScript to the page. + + +page << "alert('JavaScript with Prototype.');" + + +h5. [] + +Returns a element reference by finding it through it's id in the DOM. + + +page['blank_slate'].show # => $('blank_slate').show(); + + +h5. alert + +Displays an alert dialog with the given message. + + +page.alert('This message is from Rails!') + + +h5. assign + +Assigns the JavaScript variable the given value. + + +page.assign 'tabulated_total', @total_from_cart + + +h5. call + +Calls the JavaScript function, optionally with the given arguments. + + +page.call 'Element.replace', 'my_element', "My content to replace with." + + +h5. delay + +Executes the content of the block after a delay of the number of seconds provided. + + +page.delay(20) do + page.visual_effect :fade, 'notice' +end + + +h5. draggable + +Creates a script.aculo.us draggable element. See ActionView::Helpers::ScriptaculousHelper for more information. + +h5. drop_receiving + +Creates a script.aculo.us drop receiving element. See ActionView::Helpers::ScriptaculousHelper for more information. + +h5. hide + +Hides the visible DOM elements with the given ids. + + +page.hide 'person_29', 'person_9', 'person_0' + + +h5. insert_html + +Inserts HTML at the specified position relative to the DOM element identified by the given id. + + +page.insert_html :bottom, 'my_list', '
  • Last item
  • ' +
    + +h5. literal + +Returns an object whose to_json evaluates to the code provided. Use this to pass a literal JavaScript expression as an argument to another JavaScriptGenerator method. + +h5. redirect_to + +Redirects the browser to the given location using JavaScript, in the same form as +url_for+. + + +page.redirect_to(:controller => 'accounts', :action => 'new') + + +h5. remove + +Removes the DOM elements with the given ids from the page. + + +page.remove 'person_23', 'person_9', 'person_2' + + +h5. replace + +Replaces the "outer HTML" (i.e., the entire element, not just its contents) of the DOM element with the given id. + + +page.replace 'person-45', :partial => 'person', :object => @person + + +h5. replace_html + +Replaces the inner HTML of the DOM element with the given id. + + +page.replace_html 'person-45', :partial => 'person', :object => @person + + +h5. select + +Returns a collection reference by finding it through a CSS pattern in the DOM. + + +page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide(); + + +h5. show + +Shows hidden DOM elements with the given ids. + + +page.show 'person_6', 'person_13', 'person_223' + + +h5. sortable + +Creates a script.aculo.us sortable element. Useful to recreate sortable elements after items get added or deleted. See ActionView::Helpers::ScriptaculousHelper for more information. + +h5. toggle + +Toggles the visibility of the DOM elements with the given ids. Example: + + +page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements +page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements + + +h5. visual_effect + +Starts a script.aculo.us visual effect. See ActionView::Helpers::ScriptaculousHelper for more information. + + +TODO start from RecordIdentificationHelper + + +h3. Localized Views + +Action View has the ability render different templates depending on the current locale. + +For example, suppose you have a Posts controller with a show action. By default, calling this action will render +app/views/posts/show.html.erb+. But if you set +I18n.locale = :de+, then +app/views/posts/show.de.html.erb+ will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available. You can use the same technique to localize the rescue files in your public directory. For example, setting +I18n.locale = :de+ and creating +public/500.de.html+ and +public/404.de.html+ would allow you to have localized rescue pages. @@ -58,7 +1469,7 @@ def set_expert_locale end
    -Then you could create special views like +app/views/posts/show.expert.html.erb+, which would only be displayed to expert users. +Then you could create special views like +app/views/posts/show.expert.html.erb+ that would only be displayed to expert users. You can read more about the Rails Internationalization (I18n) API "here":i18n.html. @@ -66,4 +1477,5 @@ h3. Changelog "Lighthouse Ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/71 +* September 3, 2009: Continuing work by Trevor Turk, leveraging the "Action Pack docs":http://ap.rubyonrails.org/ and "What's new in Edge Rails":http://ryandaigle.com/articles/2007/8/3/what-s-new-in-edge-rails-partials-get-layouts * April 5, 2009: Starting work by Trevor Turk, leveraging Mike Gunderloy's docs diff --git a/railties/guides/source/active_record_basics.textile b/railties/guides/source/active_record_basics.textile index bf6e3c81816d6..226f1b134b080 100644 --- a/railties/guides/source/active_record_basics.textile +++ b/railties/guides/source/active_record_basics.textile @@ -1,50 +1,37 @@ h2. Active Record Basics -This guide will give you a strong grasp of the Active Record pattern and how it can be used with or without Rails. Hopefully, some of the philosophical and theoretical intentions discussed here will also make you a stronger and better developer. +This guide is an introduction to Active Record. After reading this guide we hope that you'll learn: -After reading this guide we hope that you'll be able to: - -* Understand the way Active Record fits into the MVC model. -* Create basic Active Record models and map them with your database tables. -* Use your models to execute CRUD (Create, Read, Update and Delete) database operations. -* Follow the naming conventions used by Rails to make developing database applications easier and obvious. -* Take advantage of the way Active Record maps it's attributes with the database tables' columns to implement your application's logic. -* Use Active Record with legacy databases that do not follow the Rails naming conventions. +* What Object Relational Mapping and Active Record are and how they are used in Rails +* How Active Record fits into the Model-View-Controller paradigm +* How to use Active Record models to manipulate data stored in a relational database +* Active Record schema naming conventions +* The concepts of database migrations, validations and callbacks endprologue. -h3. What's Active Record? - -Rails' ActiveRecord is an implementation of Martin Fowler's "Active Record Design Pattern":http://martinfowler.com/eaaCatalog/activeRecord.html. This pattern is based on the idea of creating relations between the database and the application in the following way: +h3. What is Active Record? -* Each database table is mapped to a class. -* Each table column is mapped to an attribute of this class. -* Each instance of this class is mapped to a single row in the database table. +Active Record is the M in "MVC":getting_started.html#the-mvc-architecture - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system. -The definition of the Active Record pattern in Martin Fowler's words: +h4. The Active Record Pattern -??An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data.?? +Active Record was described by Martin Fowler in his book _Patterns of Enterprise Application Architecture_. In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring data access logic is part of the object will educate users of that object on how to write to and read from the database. -h3. Object Relational Mapping +h4. 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. +Object-Relational Mapping, commonly referred to as its abbreviation ORM, is a technique that connects the rich objects of an application to tables in a relational database management system. Using ORM, the properties and relationships of the objects in an application can be easily stored and retrieved from a database without writing SQL statements directly and with less overall database access code. -h3. ActiveRecord as an ORM Framework +h4. Active Record as an ORM Framework -ActiveRecord gives us several mechanisms, being the most important ones the ability to: +Active Record gives us several mechanisms, the most important being the ability to: -* Represent models. -* Represent associations between these models. -* Represent inheritance hierarchies through related models. -* Validate models before they get recorded to the database. +* Represent models and their data +* Represent associations between these models +* Represent inheritance hierarchies through related models +* Validate models before they get persisted to the database * Perform database operations in an object-oriented fashion. -It's easy to see that the Rails Active Record implementation goes way beyond the basic description of the Active Record Pattern. - -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. - h3. Convention over Configuration in ActiveRecord When writing applications using other programming languages or frameworks, it may be necessary to write a lot of configuration code. This is particularly true for ORM frameworks in general. However, if you follow the conventions adopted by Rails, you'll need to write very little configuration (in some case no configuration at all) when creating ActiveRecord models. The idea is that if you configure your applications in the very same way most of the times then this should be the default way. In this cases, explicit configuration would be needed only in those cases where you can't follow the conventions for any reason. @@ -125,11 +112,93 @@ class Product < ActiveRecord::Base end +h3. Reading and Writing Data + +CRUD is an acronym for the four verbs we use to operate on data: Create, Read, Update, Delete. Active Record automatically creates methods to allow an application to read and manipulate data stored within its tables. + +h4. Create + +Active Record objects can be created from a hash, a block or have its attributes manually set after creation. The _new_ method will return a new object while _create_ will return the object and save it to the database. + +For example, given a model +User+ with attributes of +name+ and +occupation+, the _create_ method call will create and save a new record into the database: + + + user = User.create(:name => "David", :occupation => "Code Artist") + + +Using the _new_ method, an object can be created without being saved: + + + user = User.new + user.name = "David" + user.occupation = "Code Artist" + + +A call to _user.save_ will commit the record to the database. + +Finally, passing a block to either create or new will return a new User object: + + + user = User.new do |u| + u.name = "David" + u.occupation = "Code Artist" + end + + +h4. Read + +ActiveRecord provides a rich API for accessing data within a database. Below are a few examples of different data access methods provided by ActiveRecord. + + + # return all records + users = User.all + + + + # return first record + user = User.first + + + + # return the first user named David + david = User.find_by_name('David') + + + + # find all users named David who are Code Artists and sort by created_at in reverse chronological order + users = User.all(:conditions => { :name => 'David', :occupation => 'Code Artist'}, :order => 'created_at DESC') + + +You can learn more about querying an Active Record model in the "Active Record Query Interface":"active_record_querying.html" guide. + +h4. Update + +Once an Active Record object has been retrieved, its attributes can be modified and it can be saved to the database. + + + user = User.find_by_name('David') + user.name = 'Dave' + user.save + + +h4. Delete + +Likewise, once retrieved an Active Record object can be destroyed which removes it from the database. + + + user = User.find_by_name('David') + user.destroy + + + h3. Validations -ActiveRecord gives the ability to validate the state of your models before they get recorded into the database. There are several methods that you can use to hook into the life-cycle of your models and validate that an attribute value is not empty or follow a specific format and so on. You can learn more about validations in the "Active Record Validations and Callbacks guide":activerecord_validations_callbacks.html#validations-overview. +Active Record allows you to validate the state of a model before it gets written into the database. There are several methods that you can use to check your models and validate that an attribute value is not empty, is unique and not already in the database, follows a specific format and many more. You can learn more about validations in the "Active Record Validations and Callbacks guide":activerecord_validations_callbacks.html#validations-overview. h3. Callbacks -ActiveRecord callbacks allow you to attach code to certain events in the life-cycle of your models. This way you can add behavior to your models by transparently executing code when those events occur, like when you create a new record, update it, destroy it and so on. You can learn more about callbacks in the "Active Record Validations and Callbacks guide":activerecord_validations_callbacks.html#callbacks-overview. +Active Record callbacks allow you to attach code to certain events in the life-cycle of your models. This enables you to add behavior to your models by transparently executing code when those events occur, like when you create a new record, update it, destroy it and so on. You can learn more about callbacks in the "Active Record Validations and Callbacks guide":activerecord_validations_callbacks.html#callbacks-overview. + +h3. Migrations +Rails provides a domain-specific language for managing a database schema called migrations. Migrations are stored in files which are executed against any database that Active Record support using rake. Rails keeps track of which files have been committed to the database and provides rollback features. You can learn more about migrations in the "Active Record Migrations guide":migrations.html \ No newline at end of file diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile new file mode 100644 index 0000000000000..3073c3a7a505e --- /dev/null +++ b/railties/guides/source/active_support_core_extensions.textile @@ -0,0 +1,1835 @@ +h2. Active Support Core Extensions + +Active Support is the Rails component responsible for providing Ruby language extensions, utilities, and other transversal stuff. It offers a richer bottom-line at the language level, targeted both at the development of Rails applications, and at the development of Rails itself. + +By referring to this guide you will learn the extensions to the Ruby core classes and modules provided by Rails. + +endprologue. + +h3. Extensions to All Objects + +h4. +blank?+ and +present?+ + +The following values are considered to be blank in a Rails application: + +* +nil+ and +false+, + +* strings composed only of whitespace, i.e. matching +/\A\s*\z/+, + +* empty arrays and hashes, and + +* any other object that responds to +empty?+ and it is empty. + +WARNING: Note that numbers are not mentioned, in particular 0 and 0.0 are *not* blank. + +For example, this method from +ActionDispatch::Response+ uses +blank?+ to easily be robust to +nil+ and whitespace strings in one shot: + + +def charset + charset = String(headers["Content-Type"] || headers["type"]).split(";")[1] + charset.blank? ? nil : charset.strip.split("=")[1] +end + + +That's a typical use case for +blank?+. + +Here, the method Rails runs to instantiate observers upon initialization has nothing to do if there are none: + + +def instantiate_observers + return if @observers.blank? + # ... +end + + +The method +present?+ is equivalent to +!blank?+: + + +assert @response.body.present? # same as !@response.body.blank? + + +NOTE: Defined in +active_support/core_ext/object/blank.rb+. + +h4. +presence+ + +The +presence+ method returns its receiver if +present?+, and +nil+ otherwise. It is useful for idioms like this: + + +host = config[:host].presence || 'localhost' + + +NOTE: Defined in +active_support/core_ext/object/blank.rb+. + +h4. +duplicable?+ + +A few fundamental objects in Ruby are singletons. For example, in the whole live of a program the integer 1 refers always to the same instance: + + +1.object_id # => 3 +Math.cos(0).to_i.object_id # => 3 + + +Hence, there's no way these objects can be duplicated through +dup+ or +clone+: + + +true.dup # => TypeError: can't dup TrueClass + + +Some numbers which are not singletons are not duplicable either: + + +0.0.clone # => allocator undefined for Float +(2**1024).clone # => allocator undefined for Bignum + + +Active Support provides +duplicable?+ to programmatically query an object about this property: + + +"".duplicable? # => true +false.duplicable? # => false + + +By definition all objects are +duplicable?+ except +nil+, +false+, +true+, symbols, numbers, and class objects. + +WARNING. Using +duplicable?+ is discouraged because it depends on a hard-coded list. Classes have means to disallow duplication like removing +dup+ and +clone+ or raising exceptions from them, only +rescue+ can tell. + +NOTE: Defined in +active_support/core_ext/object/duplicable.rb+. + +h4. +returning+ + +The method +returning+ yields its argument to a block and returns it. You tipically use it with a mutable object that gets modified in the block: + + +def html_options_for_form(url_for_options, options, *parameters_for_url) + returning options.stringify_keys do |html_options| + html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart") + html_options["action"] = url_for(url_for_options, *parameters_for_url) + end +end + + +NOTE: Defined in +active_support/core_ext/object/returning.rb+. + +h4. +try+ + +Sometimes you want to call a method provided the receiver object is not +nil+, which is something you usually check first. + +For instance, note how this method of +ActiveRecord::ConnectionAdapters::AbstractAdapter+ checks if there's a +@logger+: + + +def log_info(sql, name, ms) + if @logger && @logger.debug? + name = '%s (%.1fms)' % [name || 'SQL', ms] + @logger.debug(format_log_entry(name, sql.squeeze(' '))) + end +end + + +You can shorten that using +Object#try+. This method is a synonim for +Object#send+ except that it returns +nil+ if sent to +nil+. The previous example could then be rewritten as: + + +def log_info(sql, name, ms) + if @logger.try(:debug?) + name = '%s (%.1fms)' % [name || 'SQL', ms] + @logger.debug(format_log_entry(name, sql.squeeze(' '))) + end +end + + +NOTE: Defined in +active_support/core_ext/object/try.rb+. + +h4. +metaclass+ + +The method +metaclass+ returns the singleton class on any object: + + +String.metaclass # => # +String.new.metaclass # => #> + + +NOTE: Defined in +active_support/core_ext/object/metaclass.rb+. + +h4. +class_eval(*args, &block)+ + +You can evaluate code in the context of any object's singleton class using +class_eval+: + + +class Proc + def bind(object) + block, time = self, Time.now + object.class_eval do + method_name = "__bind_#{time.to_i}_#{time.usec}" + define_method(method_name, &block) + method = instance_method(method_name) + remove_method(method_name) + method + end.bind(object) + end +end + + +NOTE: Defined in +active_support/core_ext/object/metaclass.rb+. + +h4. +acts_like?(duck)+ + +The method +acts_like+ provides a way to check whether some class acts like some other class based on a simple convention: a class that provides the same interface as +String+ defines + + +def acts_like_string? +end + + +which is only a marker, its body or return value are irrelevant. Then, client code can query for duck-type-safeness this way: + + +some_klass.acts_like?(:string) + + +Rails has classes that act like +Date+ or +Time+ and follow this contract. + +NOTE: Defined in +active_support/core_ext/object/acts_like.rb+. + +h4. +to_param+ + +All objects in Rails respond to the method +to_param+, which is meant to return something that represents them as values in a query string, or as a URL fragments. + +By default +to_param+ just calls +to_s+: + + +7.to_param # => "7" + + +The return value of +to_param+ should *not* be escaped: + + +"Tom & Jerry".to_param # => "Tom & Jerry" + + +Several classes in Rails overwrite this method. + +For example +nil+, +true+, and +false+ return themselves. +Array#to_param+ calls +to_param+ on the elements and joins the result with "/": + + +[0, true, String].to_param # => "0/true/String" + + +Notably, the Rails routing system calls +to_param+ on models to get a value for the +:id+ placeholder. +ActiveRecord::Base#to_param+ returns the +id+ of a model, but you can redefine that method in your models. For example, given + + +class User + def to_param + "#{id}-#{name.parameterize}" + end +end + + +we get: + + +user_path(@user) # => "/users/357-john-smith" + + +WARNING. Controllers need to be aware of any redifinition of +to_param+ because when a request like that comes in "357-john-smith" is the value of +params[:id]+. + +NOTE: Defined in +active_support/core_ext/object/to_param.rb+. + +h4. +to_query+ + +Except for hashes, given an unescaped +key+ this method constructs the part of a query string that would map such key to what +to_param+ returns. For example, given + + +class User + def to_param + "#{id}-#{name.parameterize}" + end +end + + +we get: + + +current_user.to_query('user') # => user=357-john-smith + + +This method escapes whatever is needed, both for the key and the value: + + +account.to_query('company[name]') +# => "company%5Bname%5D=Johnson+%26+Johnson" + + +so its output is ready to be used in a query string. + +Arrays return the result of applying +to_query+ to each element with _key_[] as key, and join the result with "&": + + +[3.4, -45.6].to_query('sample') +# => "sample%5B%5D=3.4&sample%5B%5D=-45.6" + + +Hashes also respond to +to_query+ but with a different signature. If no argument is passed a call generates a sorted series of key/value assigments calling +to_query(key)+ on its values. Then it joins the result with "&": + + +{:c => 3, :b => 2, :a => 1}.to_query # => "a=1&b=2&c=3" + + +The method +Hash#to_query+ accepts an optional namespace for the keys: + + +{:id => 89, :name => "John Smith"}.to_query('user') +# => "user%5Bid%5D=89&user%5Bname%5D=John+Smith" + + +NOTE: Defined in +active_support/core_ext/object/to_query.rb+. + +h4. +with_options+ + +The method +with_options+ provides a way to factor out common options in a series of method calls. + +Given a default options hash, +with_options+ yields a proxy object to a block. Within the block, methods called on the proxy are forwarded to the receiver with their options merged. For example, you get rid of the duplication in: + + +class Account < ActiveRecord::Base + has_many :customers, :dependent => :destroy + has_many :products, :dependent => :destroy + has_many :invoices, :dependent => :destroy + has_many :expenses, :dependent => :destroy +end + + +this way: + + +class Account < ActiveRecord::Base + with_options :dependent => :destroy do |assoc| + assoc.has_many :customers + assoc.has_many :products + assoc.has_many :invoices + assoc.has_many :expenses + end +end + + +That idiom may convey _grouping_ to the reader as well. For example, say you want to send a newsletter whose language depends on the user. Somewhere in the mailer you could group locale-dependent bits like this: + + +I18n.with_options :locale => user.locale, :scope => "newsletter" do |i18n| + subject i18n.t :subject + body i18n.t :body, :user_name => user.name +end + + +TIP: Since +with_options+ forwards calls to its receiver they can be nested. Each nesting level will merge inherited defaults in addition to their own. + +NOTE: Defined in +active_support/core_ext/object/with_options.rb+. + +h4. Instance Variables + +Active Support provides several methods to ease access to instance variables. + +h5. +instance_variable_names+ + +Ruby 1.8 and 1.9 have a method called +instance_variables+ that returns the names of the defined instance variables. But they behave differently, in 1.8 it returns strings whereas in 1.9 it returns symbols. Active Support defines +instance_variable_names+ as a portable way to obtain them as strings: + + +class C + def initialize(x, y) + @x, @y = x, y + end +end + +C.new(0, 1).instance_variable_names # => ["@y", "@x"] + + +WARNING: The order in which the names are returned is unespecified, and it indeed depends on the version of the interpreter. + +NOTE: Defined in +active_support/core_ext/object/instance_variables.rb+. + +h5. +instance_values+ + +The method +instance_values+ returns a hash that maps instance variable names without "@" to their +corresponding values. Keys are strings both in Ruby 1.8 and 1.9: + + +class C + def initialize(x, y) + @x, @y = x, y + end +end + +C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} + + +NOTE: Defined in +active_support/core_ext/object/instance_variables.rb+. + +h5. +copy_instance_variables_from(object, exclude = [])+ + +Copies the instance variables of +object+ into +self+. + +Instance variable names in the +exclude+ array are ignored. If +object+ +responds to +protected_instance_variables+ the ones returned are +also ignored. For example, Rails controllers implement that method. + +In both arrays strings and symbols are understood, and they have to include +the at sign. + + +class C + def initialize(x, y, z) + @x, @y, @z = x, y, z + end + + def protected_instance_variables + %w(@z) + end +end + +a = C.new(0, 1, 2) +b = C.new(3, 4, 5) + +a.copy_instance_variables_from(b, [:@y]) +# a is now: @x = 3, @y = 1, @z = 2 + + +In the example +object+ and +self+ are of the same type, but they don't need to. + +NOTE: Defined in +active_support/core_ext/object/instance_variables.rb+. + +h4. Silencing Warnings, Streams, and Exceptions + +The methods +silence_warnings+ and +enable_warnings+ change the value of +$VERBOSE+ accordingly for the duration of their block, and reset it afterwards: + + +silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger } + + +You can silence any stream while a block runs with +silence_stream+: + + +silence_stream(STDOUT) do + # STDOUT is silent here +end + + +Silencing exceptions is also possible with +suppress+. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is +kind_of?+ any of the arguments, +suppress+ captures it and returns silently. Otherwise the exception is reraised: + + +# If the user is locked the increment is lost, no big deal. +suppress(ActiveRecord::StaleObjectError) do + current_user.increment! :visits +end + + +NOTE: Defined in +active_support/core_ext/kernel/reporting.rb+. + +h3. Extensions to +Module+ + +h4. Aliasing + +h5. +alias_method_chain+ + +Using plain Ruby you can wrap methods with other methods, that's called _alias chaining_. + +For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap +ActionController::TestCase#process+ this way in +test/test_helper.rb+: + + +ActionController::TestCase.class_eval do + # save a reference to the original process method + alias_method :original_process, :process + + # now redefine process and delegate to original_process + def process(action, params=nil, session=nil, flash=nil, http_method='GET') + params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] + original_process(action, params, session, flash, http_method) + end +end + + +That's the method +get+, +post+, etc., delegate the work to. + +That technique has a risk, it could be the case that +:original_process+ was taken. To try to avoid collisions people choose some label that characterizes what the chaining is about: + + +ActionController::TestCase.class_eval do + def process_with_stringified_params(...) + params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] + process_without_stringified_params(action, params, session, flash, http_method) + end + alias_method :process_without_stringified_params, :process + alias_method :process, :process_with_stringified_params +end + + +The method +alias_method_chain+ provides a shortcut for that pattern: + + +ActionController::TestCase.class_eval do + def process_with_stringified_params(...) + params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] + process_without_stringified_params(action, params, session, flash, http_method) + end + alias_method_chain :process, :stringified_params +end + + +Rails uses +alias_method_chain+ all over the code base. For example validations are added to +ActiveRecord::Base#save+ by wrapping the method that way in a separate module specialised in validations. + +NOTE: Defined in +active_support/core_ext/module/aliasing.rb+. + +h5. +alias_attribute+ + +Model attributes have a reader, a writer, and a predicate. You can aliase a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (my mnemonic is they go in the same order as if you did an assignment): + + +class User < ActiveRecord::Base + # let me refer to the email column as "login", + # much meaningful for authentication code + alias_attribute :login, :email +end + + +NOTE: Defined in +active_support/core_ext/module/aliasing.rb+. + +h4. Delegation + +The class method +delegate+ + +h3. Extensions to +Class+ + +h4. Class Attribute Accessors + +The macros +cattr_reader+, +cattr_writer+, and +cattr_accessor+ are analogous to their +attr_*+ counterparts but for classes. They initialize a class variable to +nil+ unless it already exists, and generate the corresponding class methods to access it: + + +class MysqlAdapter < AbstractAdapter + # Generates class methods to access @@emulate_booleans. + cattr_accessor :emulate_booleans + self.emulate_booleans = true +end + + +Instance methods are created as well for convenience. For example given + + +module ActionController + class Base + cattr_accessor :logger + end +end + + +we can access +logger+ in actions. The generation of the writer instance method can be prevented setting +:instance_writer+ to +false+ (not any false value, but exactly +false+): + + +module ActiveRecord + class Base + # No pluralize_table_names= instance writer is generated. + cattr_accessor :pluralize_table_names, :instance_writer => false + end +end + + +NOTE: Defined in +active_support/core_ext/class/attribute_accessors.rb+. + +h4. Class Inheritable Attributes + +Class variables are shared down the inheritance tree. Class instance variables are not shared, but they are not inherited either. The macros +class_inheritable_reader+, +class_inheritable_writer+, and +class_inheritable_accessor+ provide accesors for class-level data which is inherited but not shared with children: + + +module ActionController + class Base + # FIXME: REVISE/SIMPLIFY THIS COMMENT. + # The value of allow_forgery_protection is inherited, + # but its value in a particular class does not affect + # the value in the rest of the controllers hierarchy. + class_inheritable_accessor :allow_forgery_protection + end +end + + +They accomplish this with class instance variables and cloning on subclassing, there are no class variables involved. Cloning is performed with +dup+ as long as the value is duplicable. + +There are some variants specialised in arrays and hashes: + + +class_inheritable_array +class_inheritable_hash + + +Those writers take any inherited array or hash into account and extend them rather than overwrite them. + +As with vanilla class attribute accessors these macros create convenience instance methods for reading and writing. The generation of the writer instance method can be prevented setting +:instance_writer+ to +false+ (not any false value, but exactly +false+): + + +module ActiveRecord + class Base + class_inheritable_accessor :default_scoping, :instance_writer => false + end +end + + +Since values are copied when a subclass is defined, if the base class changes the attribute after that, the subclass does not see the new value. That's the point. + +NOTE: Defined in +active_support/core_ext/class/inheritable_attributes.rb+. + +There's a related macro called +superclass_delegating_accessor+, however, that does not copy the value when the base class is subclassed. Instead, it delegates reading to the superclass as long as the attribute is not set via its own writer. For example, +ActionMailer::Base+ defines +delivery_method+ this way: + + +module ActionMailer + class Base + superclass_delegating_accessor :delivery_method + self.delivery_method = :smtp + end +end + + +If for whatever reason an application loads the definition of a mailer class and after that sets +ActionMailer::Base.delivery_method+, the mailer class will still see the new value. In addition, the mailer class is able to change the +delivery_method+ without affecting the value in the parent using its own inherited class attribute writer. + +NOTE: Defined in +active_support/core_ext/class/delegating_attributes.rb+. + +h4. Subclasses + +The +subclasses+ method returns the names of all subclasses of a given class as an array of strings. That comprises not only direct subclasses, but all descendants down the hierarchy: + + +class C; end +C.subclasses # => [] + +Integer.subclasses # => ["Bignum", "Fixnum"] + +module M + class A; end + class B1 < A; end + class B2 < A; end +end + +module N + class C < M::B1; end +end + +M::A.subclasses # => ["N::C", "M::B2", "M::B1"] + + +The order in which these class names are returned is unspecified. + +See also +Object#subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME. + +NOTE: Defined in +active_support/core_ext/class/removal.rb+. + +h4. Class Removal + +Roughly speaking, the +remove_class+ method removes the class objects passed as arguments: + + +Class.remove_class(Hash, Dir) # => [Hash, Dir] +Hash # => NameError: uninitialized constant Hash +Dir # => NameError: uninitialized constant Dir + + +More specifically, +remove_class+ attempts to remove constants with the same name as the passed class objects from their parent modules. So technically this method does not guarantee the class objects themselves are not still valid and alive somewhere after the method call: + + +module M + class A; end + class B < A; end +end + +A2 = M::A + +M::A.object_id # => 13053950 +Class.remove_class(M::A) + +M::B.superclass.object_id # => 13053950 (same object as before) +A2.name # => "M::A" (name is hard-coded in object) + + +WARNING: Removing fundamental classes like +String+ can result in really funky behaviour. + +The method +remove_subclasses+ provides a shortcut for removing all descendants of a given class, where "removing" has the meaning explained above: + + +class A; end +class B1 < A; end +class B2 < A; end +class C < A; end + +A.subclasses # => ["C", "B2", "B1"] +A.remove_subclasses +A.subclasses # => [] +C # => NameError: uninitialized constant C + + +See also +Object#remove_subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME. + +NOTE: Defined in +active_support/core_ext/class/removal.rb+. + +h3. Extensions to +String+ + +h4. +squish+ + +The method +String#squish+ strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each: + + +" \n foo\n\r \t bar \n".squish # => "foo bar" + + +There's also the destructive version +String#squish!+. + +NOTE: Defined in +active_support/core_ext/string/filters.rb+. + +h4. Key-based Interpolation + +In Ruby 1.9 the % string operator supports key-based interpolation, both formatted and unformatted: + + +"Total is %.02f" % {:total => 43.1} # => Total is 43.10 +"I say %{foo}" % {:foo => "wadus"} # => "I say wadus" +"I say %{woo}" % {:foo => "wadus"} # => KeyError + + +Active Support adds that functionality to % in previous versions of Ruby. + +NOTE: Defined in +active_support/core_ext/string/interpolation.rb+. + +h4. +starts_with?+ and +ends_width?+ + +Active Support defines 3rd person aliases of +String#start_with?+ and +String#end_with?+: + + +"foo".starts_with?("f") # => true +"foo".ends_with?("o") # => true + + +NOTE: Defined in +active_support/core_ext/string/starts_ends_with.rb+. + +h4. Access + +h5. +at(position)+ + +Returns the character of the string at position +position+: + + +"hello".at(0) # => "h" +"hello".at(4) # => "o" +"hello".at(-1) # => "o" +"hello".at(10) # => ERROR if < 1.9, nil in 1.9 + + +NOTE: Defined in +active_support/core_ext/string/access.rb+. + +h5. +from(position)+ + +Returns the substring of the string starting at position +position+: + + +"hello".from(0) # => "hello" +"hello".from(2) # => "llo" +"hello".from(-2) # => "lo" +"hello".from(10) # => "" if < 1.9, nil in 1.9 + + +NOTE: Defined in +active_support/core_ext/string/access.rb+. + +h5. +to(position)+ + +Returns the substring of the string up to position +position+: + + +"hello".to(0) # => "h" +"hello".to(2) # => "hel" +"hello".to(-2) # => "hell" +"hello".to(10) # => "hello" + + +NOTE: Defined in +active_support/core_ext/string/access.rb+. + +h5. +first(limit = 1)+ + +The call +str.first(n)+ is equivalent to +str.to(n-1)+ if +n+ > 0, and returns an empty string for +n+ == 0. + +NOTE: Defined in +active_support/core_ext/string/access.rb+. + +h5. +last(limit = 1)+ + +The call +str.last(n)+ is equivalent to +str.from(-n)+ if +n+ > 0, and returns an empty string for +n+ == 0. + +NOTE: Defined in +active_support/core_ext/string/access.rb+. + +h3. Extensions to +Numeric+ + +h4. Bytes + +All numbers respond to these methods: + + +bytes +kilobytes +megabytes +gigabytes +terabytes +petabytes +exabytes + + +They return the corresponding amount of bytes, using a conversion factor of 1024: + + +2.kilobytes # => 2048 +3.megabytes # => 3145728 +3.5.gigabytes # => 3758096384 +-4.exabytes # => -4611686018427387904 + + +Singular forms are aliased so you are able to say: + + +1.megabyte # => 1048576 + + +NOTE: Defined in +active_support/core_ext/numeric/bytes.rb+. + +h3. Extensions to +Integer+ + +h4. +multiple_of?+ + +The method +multiple_of?+ tests whether an integer is multiple of the argument: + + +2.multiple_of?(1) # => true +1.multiple_of?(2) # => false + + +NOTE: Defined in +active_support/core_ext/integer/multiple.rb+. + +h4. +ordinalize+ + +The method +ordinalize+ returns the ordinal string corresponding to the receiver integer: + + +1.ordinalize # => "1st" +2.ordinalize # => "2nd" +53.ordinalize # => "53rd" +2009.ordinalize # => "2009th" + + +NOTE: Defined in +active_support/core_ext/integer/inflections.rb+. + +h3. Extensions to +Float+ + +... + +h3. Extensions to +BigDecimal+ + +... + +h3. Extensions to +Enumerable+ + +h4. +group_by+ + +Ruby 1.8.7 and up define +group_by+, and Active Support does it for previous versions. + +This iterator takes a block and builds an ordered hash with its return values as keys. Each key is mapped to the array of elements for which the block returned that value: + + +entries_by_surname_initial = address_book.group_by do |entry| + entry.surname.at(0).upcase +end + + +WARNING. Active Support redefines +group_by+ in Ruby 1.8.7 so that it still returns an ordered hash. + +NOTE: Defined in +active_support/core_ext/enumerable.rb+. + +h4. +sum+ + +The method +sum+ adds the elements of an enumerable: + + +[1, 2, 3].sum # => 6 +(1..100).sum # => 5050 + + +Addition only assumes the elements respond to +: + + +[[1, 2], [2, 3], [3, 4]].sum # => [1, 2, 2, 3, 3, 4] +%w(foo bar baz).sum # => "foobarbaz" +{:a => 1, :b => 2, :c => 3}.sum # => [:b, 2, :c, 3, :a, 1] + + +The sum of an empty collection is zero by default, but this is customizable: + + +[].sum # => 0 +[].sum(1) # => 1 + + +If a block is given +sum+ becomes an iterator that yields the elements of the collection and sums the returned values: + + +(1..5).sum {|n| n * 2 } # => 30 +[2, 4, 6, 8, 10].sum # => 30 + + +The sum of an empty receiver can be customized in this form as well: + + +[].sum(1) {|n| n**3} # => 1 + + +The method +ActiveRecord::Observer#observed_subclasses+ for example is implemented this way: + + +def observed_subclasses + observed_classes.sum([]) { |klass| klass.send(:subclasses) } +end + + +NOTE: Defined in +active_support/core_ext/enumerable.rb+. + +h4. +each_with_object+ + +The +inject+ method offers iteration with an accumulator: + + +[2, 3, 4].inject(1) {|acc, i| product*i } # => 24 + + +The block is expected to return the value for the accumulator in the next iteration, and this makes building mutable objects a bit cumbersome: + + +[1, 2].inject({}) {|h, i| h[i] = i**2; h} # => {1 => 1, 2 => 4} + + +See that spurious "+; h+"? + +Active Support backports +each_with_object+ from Ruby 1.9, which addresses that use case. It iterates over the collection, passes the accumulator, and returns the accumulator when done. You normally modify the accumulator in place. The example above would be written this way: + + +[1, 2].each_with_object({}) {|i, h| h[i] = i**2} # => {1 => 1, 2 => 4} + + +WARNING. Note that the item of the collection and the accumulator come in different order in +inject+ and +each_with_object+. + +NOTE: Defined in +active_support/core_ext/enumerable.rb+. + +h4. +index_by+ + +The method +index_by+ generates a hash with the elements of an enumerable indexed by some key. + +It iterates through the collection and passes each element to a block. The element will be keyed by the value returned by the block: + + +invoices.index_by(&:number) +# => {'2009-032' => , '2009-008' => , ...} + + +WARNING. Keys should normally be unique. If the block returns the same value for different elements no collection is built for that key. The last item will win. + +NOTE: Defined in +active_support/core_ext/enumerable.rb+. + +h4. +many?+ + +The method +many?+ is shorthand for +collection.size > 1+: + + +<% if pages.many? %> + <%= pagination_links %> +<% end %> + + +If an optional block is given +many?+ only takes into account those elements that return true: + + +@see_more = videos.many? {|video| video.category == params[:category]} + + +NOTE: Defined in +active_support/core_ext/enumerable.rb+. + +h4. +exclude?+ + +The predicate +exclude?+ tests whether a given object does *not* belong to the collection. It is the negation of the builtin +include?+: + + +to_visit << node if visited.exclude?(node) + + +NOTE: Defined in +active_support/core_ext/enumerable.rb+. + +h3. Extensions to +Array+ + +h4. Accessing + +Active Support augments the API of arrays to ease certain ways of accessing them. For example, +to+ returns the subarray of elements up to the one at the passed index: + + +%w(a b c d).to(2) # => %w(a b c) +[].to(7) # => [] + + +Similarly, +from+ returns the tail from the element at the passed index on: + + +%w(a b c d).from(2) # => %w(c d) +%w(a b c d).from(10) # => nil +[].from(0) # => [] + + +The methods +second+, +third+, +fourth+, and +fifth+ return the corresponding element (+first+ is builtin). Thanks to social wisdom and positive constructiveness all around, +forty_two+ is also available. + +You can pick a random element with +rand+: + + +shape_type = [Circle, Square, Triangle].rand + + +NOTE: Defined in +active_support/core_ext/array/access.rb+. + +h4. Options Extraction + +When the last argument in a method call is a hash, except perhaps for a +&block+ argument, Ruby allows you to omit the brackets: + + +User.exists?(:email => params[:email]) + + +That syntactic sugar is used a lot in Rails to avoid positional arguments where there would be too many, offering instead interfaces that emulate named parameters. In particular it is very idiomatic to use a trailing hash for options. + +If a method expects a variable number of arguments and uses * in its declaration, however, such an options hash ends up being an item of the array of arguments, where kind of loses its role. + +In those cases, you may give an options hash a distinguished treatment with +extract_options!+. That method checks the type of the last item of an array. If it is a hash it pops it and returns it, otherwise returns an empty hash. + +Let's see for example the definition of the +caches_action+ controller macro: + + +def caches_action(*actions) + return unless cache_configured? + options = actions.extract_options! + ... +end + + +This method receives an arbitrary number of action names, and an optional hash of options as last argument. With the call to +extract_options!+ you obtain the options hash and remove it from +actions+ in a simple and explicit way. + +NOTE: Defined in +active_support/core_ext/array/extract_options.rb+. + +h4. Conversions + +h5. +to_sentence+ + +The method +to_sentence+ turns an array into a string containing a sentence that enumerates its items: + + +%w().to_sentence # => "" +%w(Earth).to_sentence # => "Earth" +%w(Earth Wind).to_sentence # => "Earth and Wind" +%w(Earth Wind Fire).to_sentence # => "Earth, Wind, and Fire" + + +This method accepts three options: + +* :two_words_connector: What is used for arrays of length 2. Default is " and ". +* :words_connector: What is used to join the elements of arrays with 3 or more elements, except for the last two. Default is ", ". +* :last_word_connector: What is used to join the last items of an array with 3 or more elements. Default is ", and ". + +The defaults for these options can be localised, their keys are: + +|_. Option |_. I18n key | +| :two_words_connector | support.array.two_words_connector | +| :words_connector | support.array.words_connector | +| :last_word_connector | support.array.last_word_connector | + +Options :connector and :skip_last_comma are deprecated. + +NOTE: Defined in +active_support/core_ext/array/conversions.rb+. + +h5. +to_formatted_s+ + +The method +to_formatted_s+ acts like +to_s+ by default. + +If the array contains items that respond to +id+, however, it may be passed the symbol :db as argument. That's typically used with collections of ARs, though technically any object in Ruby 1.8 responds to +id+ indeed. Returned strings are: + + +[].to_formatted_s(:db) # => "null" +[user].to_formatted_s(:db) # => "8456" +invoice.lines.to_formatted_s(:db) # => "23,567,556,12" + + +Integers in the example above are supposed to come from the respective calls to +id+. + +NOTE: Defined in +active_support/core_ext/array/conversions.rb+. + +h5. +to_xml+ + +The method +to_xml+ returns a string containing an XML representation of its receiver: + + +Contributor.all(:limit => 2, :order => 'rank ASC').to_xml +# => +# +# +# +# 4356 +# Jeremy Kemper +# 1 +# jeremy-kemper +# +# +# 4404 +# David Heinemeier Hansson +# 2 +# david-heinemeier-hansson +# +# + + +To do so it sends +to_xml+ to every item in turn, and collects the results under a root node. All items must respond to +to_xml+, an exception is raised otherwise. + +By default, the name of the root element is the underscorized and dasherized plural of the name of the class of the first item, provided the rest of elements belong to that type (checked with is_a?) and they are not hashes. In the example above that's "contributors". + +If there's any element that does not belong to the type of the first one the root node becomes "records": + + +[Contributor.first, Commit.first].to_xml +# => +# +# +# +# 4583 +# Aaron Batalion +# 53 +# aaron-batalion +# +# +# Joshua Peek +# 2009-09-02T16:44:36Z +# origin/master +# 2009-09-02T16:44:36Z +# Joshua Peek +# +# 190316 +# false +# Kill AMo observing wrap_with_notifications since ARes was only using it +# 723a47bfb3708f968821bc969a9a3fc873a3ed58 +# +# + + +If the receiver is an array of hashes the root element is by default also "records": + + +[{:a => 1, :b => 2}, {:c => 3}].to_xml +# => +# +# +# +# 2 +# 1 +# +# +# 3 +# +# + + +WARNING. If the collection is empty the root element is by default "nil-classes". That's a gotcha, for example the root element of the list of contributors above would not be "contributors" if the collection was empty, but "nil-classes". You may use the :root option to ensure a consistent root element. + +The name of children nodes is by default the name of the root node singularized. In the examples above we've seen "contributor" and "record". The option :children allows you to set these node names. + +The default XML builder is a fresh instance of Builder::XmlMarkup. You can configure your own builder via the :builder option. The method also accepts options like :dasherize and friends, they are forwarded to the builder: + + +Contributor.all(:limit => 2, :order => 'rank ASC').to_xml(:skip_types => true) +# => +# +# +# +# 4356 +# Jeremy Kemper +# 1 +# jeremy-kemper +# +# +# 4404 +# David Heinemeier Hansson +# 2 +# david-heinemeier-hansson +# +# + + +NOTE: Defined in +active_support/core_ext/array/conversions.rb+. + +h4. Wrapping + +The class method +Array.wrap+ behaves like the function +Array()+ except that it does not try to call +to_a+ on its argument. That changes the behaviour for enumerables: + + +Array.wrap(:foo => :bar) # => [{:foo => :bar}] +Array(:foo => :bar) # => [[:foo, :bar]] + +Array.wrap("foo\nbar") # => ["foo\nbar"] +Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8 + + +NOTE: Defined in +active_support/core_ext/array/wrap.rb+. + +h4. Grouping + +h5. +in_groups_of(number, fill_with = nil)+ + +The method +in_groups_of+ splits an array into consecutive groups of a certain size. It returns an array with the groups: + + +[1, 2, 3].in_groups_of(2) # => [[1, 2], [3, nil]] + + +or yields them in turn if a block is passed: + + +<% sample.in_groups_of(3) do |a, b, c| %> + + <%=h a %> + <%=h b %> + <%=h c %> + +<% end %> + + +The first example shows +in_groups_of+ fills the last group with as many +nil+ elements as needed to have the requested size. You can change this padding value using the second optional argument: + + +[1, 2, 3].in_groups_of(2, 0) # => [[1, 2], [3, 0]] + + +And you can tell the method not to fill the last group passing +false+: + + +[1, 2, 3].in_groups_of(2, false) # => [[1, 2], [3]] + + +As a consequence +false+ can't be a used as a padding value. + +NOTE: Defined in +active_support/core_ext/array/grouping.rb+. + +h5. +in_groups(number, fill_with = nil)+ + +The method +in_groups+ splits an array into a certain number of groups. The method returns and array with the groups: + + +%w(1 2 3 4 5 6 7).in_groups(3) +# => [["1", "2", "3"], ["4", "5", nil], ["6", "7", nil]] + + +or yields them in turn if a block is passed: + + +%w(1 2 3 4 5 6 7).in_groups(3) {|group| p group} +["1", "2", "3"] +["4", "5", nil] +["6", "7", nil] + + +The examples above show that +in_groups+ fills some groups with a trailing +nil+ element as needed. A group can get at most one of these extra elements, the rightmost one if any. And the groups that have them are always the last ones. + +You can change this padding value using the second optional argument: + + +%w(1 2 3 4 5 6 7).in_groups(3, "0") +# => [["1", "2", "3"], ["4", "5", "0"], ["6", "7", "0"]] + + +And you can tell the method not to fill the smaller groups passing +false+: + + +%w(1 2 3 4 5 6 7).in_groups(3, false) +# => [["1", "2", "3"], ["4", "5"], ["6", "7"]] + + +As a consequence +false+ can't be a used as a padding value. + +NOTE: Defined in +active_support/core_ext/array/grouping.rb+. + +h5. +split(value = nil)+ + +The method +split+ divides an array by a separator and returns the resulting chunks. + +If a block is passed the separators are those elements of the array for which the block returns true: + + +(-5..5).to_a.split { |i| i.multiple_of?(4) } +# => [[-5], [-3, -2, -1], [1, 2, 3], [5]] + + +Otherwise, the value received as argument, which defaults to +nil+, is the separator: + + +[0, 1, -5, 1, 1, "foo", "bar"].split(1) +# => [[0], [-5], [], ["foo", "bar"]] + + +TIP: Observe in the previous example that consecutive separators result in empty arrays. + +NOTE: Defined in +active_support/core_ext/array/grouping.rb+. + +h3. Extensions to +Hash+ + +h4. Conversions + +h5. +to_xml+ + +The method +to_xml+ returns a string containing an XML representation of its receiver: + + +{"foo" => 1, "bar" => 2}.to_xml +# => +# +# +# 1 +# 2 +# + + +To do so, the method loops over the pairs and builds nodes that depend on the _values_. Given a pair +key+, +value+: + +* If +value+ is a hash there's a recursive call with +key+ as :root. + +* If +value+ is an array there's a recursive call with +key+ as :root, and +key+ singularized as :children. + +* If +value+ is a callable object it must expect one or two arguments. Depending on the arity, the callable is invoked with the +options+ hash as first argument with +key+ as :root, and +key+ singularized as second argument. Its return value becomes a new node. + +* If +value+ responds to +to_xml+ the method is invoked with +key+ as :root. + +* Otherwise, a node with +key+ as tag is created with a string representation of +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added. Unless the option :skip_types exists and is true, an attribute "type" is added as well according to the following mapping: + +XML_TYPE_NAMES = { + "Symbol" => "symbol", + "Fixnum" => "integer", + "Bignum" => "integer", + "BigDecimal" => "decimal", + "Float" => "float", + "TrueClass" => "boolean", + "FalseClass" => "boolean", + "Date" => "date", + "DateTime" => "datetime", + "Time" => "datetime" +} + + +By default the root node is "hash", but that's configurable via the :root option. + +The default XML builder is a fresh instance of Builder::XmlMarkup. You can configure your own builder with the :builder option. The method also accepts options like :dasherize and friends, they are forwarded to the builder. + +NOTE: Defined in +active_support/core_ext/hash/conversions.rb+. + +h4. Merging + +Ruby has a builtin method +Hash#merge+ that merges two hashes: + + +{:a => 1, :b => 1}.merge(:a => 0, :c => 2) +# => {:a => 0, :b => 1, :c => 2} + + +Active Support defines a few more ways of merging hashes that may be convenient. + +h5. +reverse_merge+ and +reverse_merge!+ + +In case of collision the key in the hash of the argument wins in +merge+. You can support option hashes with default values in a compact way with this idiom: + + +options = {:length => 30, :omission => "..."}.merge(options) + + +Active Support defines +reverse_merge+ in case you prefer this alternative notation: + + +options = options.reverse_merge(:length => 30, :omission => "...") + + +And a bang version +reverse_merge!+ that performs the merge in place: + + +options.reverse_merge!(:length => 30, :omission => "...") + + +WARNING. Take into account that +reverse_merge!+ may change the hash in the caller, which may or may not be a good idea. + +NOTE: Defined in +active_support/core_ext/hash/reverse_merge.rb+. + +h5. +reverse_update+ + +The method +reverse_update+ is an alias for +reverse_merge!+, explained above. + +WARNING. Note that +reverse_update+ has no bang. + +NOTE: Defined in +active_support/core_ext/hash/reverse_merge.rb+. + +h5. +deep_merge+ and +deep_merge!+ + +As you can see in the previous example if a key is found in both hashes the value in the one in the argument wins. + +Active Support defines +Hash#deep_merge+. In a deep merge, if a key is found in both hashes and their values are hashes in turn, then their _merge_ becomes the value in the resulting hash: + + +{:a => {:b => 1}}.deep_merge(:a => {:c => 2}) +# => {:a => {:b => 1, :c => 2}} + + +The method +deep_merge!+ performs a deep merge in place. + +NOTE: Defined in +active_support/core_ext/hash/deep_merge.rb+. + +h4. Diffing + +The method +diff+ returns a hash that represents a diff of the receiver and the argument with the following logic: + +* Pairs +key+, +value+ that exist in both hashes do not belong to the diff hash. + +* If both hashes have +key+, but with different values, the pair in the receiver wins. + +* The rest is just merged. + + +{:a => 1}.diff(:a => 1) +# => {}, first rule + +{:a => 1}.diff(:a => 2) +# => {:a => 1}, second rule + +{:a => 1}.diff(:b => 2) +# => {:a => 1, :b => 2}, third rule + +{:a => 1, :b => 2, :c => 3}.diff(:b => 1, :c => 3, :d => 4) +# => {:a => 1, :b => 2, :d => 4}, all rules + +{}.diff({}) # => {} +{:a => 1}.diff({}) # => {:a => 1} +{}.diff(:a => 1) # => {:a => 1} + + +An important property of this diff hash is that you can retrieve the original hash by applying +diff+ twice: + + +hash.diff(hash2).diff(hash2) == hash + + +Diffing hashes may be useful for error messages related to expected option hashes for example. + +NOTE: Defined in +active_support/core_ext/hash/diff.rb+. + +h4. Working with Keys + +h5. +except+ and +except!+ + +The method +except+ returns a hash with the keys in the argument list removed, if present: + + +{:a => 1, :b => 2}.except(:a) # => {:b => 2} + + +If the receiver responds to +convert_key+, the method is called on each of the arguments. This allows +except+ to play nice with hashes with indifferent access for instance: + + +{:a => 1}.with_indifferent_access.except(:a) # => {} +{:a => 1}.with_indifferent_access.except("a") # => {} + + +The method +except+ may come in handy for example when you want to protect some parameter that can't be globally protected with +attr_protected+: + + +params[:account] = params[:account].except(:plan_id) unless admin? +@account.update_attributes(params[:account]) + + +There's also the bang variant +except!+ that removes keys in the very receiver. + +NOTE: Defined in +active_support/core_ext/hash/except.rb+. + +h5. +stringify_keys+ and +stringify_keys!+ + +The method +stringify_keys+ returns a hash that has a stringified version of the keys in the receiver. It does so by sending +to_s+ to them: + + +{nil => nil, 1 => 1, :a => :a}.stringify_keys +# => {"" => nil, "a" => :a, "1" => 1} + + +The result in case of collision is undefined: + + +{"a" => 1, :a => 2}.stringify_keys +# => {"a" => 2}, in my test, can't rely on this result though + + +This method may be useful for example to easily accept both symbols and strings as options. For instance +ActionView::Helpers::FormHelper+ defines: + + +def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0") + options = options.stringify_keys + options["type"] = "checkbox" + ... +end + + +The second line can safely access the "type" key, and let the user to pass either +:type+ or "type". + +There's also the bang variant +stringify_keys!+ that stringifies keys in the very receiver. + +NOTE: Defined in +active_support/core_ext/hash/keys.rb+. + +h5. +symbolize_keys+ and +symbolize_keys!+ + +The method +symbolize_keys+ returns a hash that has a symbolized version of the keys in the receiver, where possible. It does so by sending +to_sym+ to them: + + +{nil => nil, 1 => 1, "a" => "a"}.symbolize_keys +# => {1 => 1, nil => nil, :a => "a"} + + +WARNING. Note in the previous example only one key was symbolized. + +The result in case of collision is undefined: + + +{"a" => 1, :a => 2}.symbolize_keys +# => {:a => 2}, in my test, can't rely on this result though + + +This method may be useful for example to easily accept both symbols and strings as options. For instance +ActionController::UrlRewriter+ defines + + +def rewrite_path(options) + options = options.symbolize_keys + options.update(options[:params].symbolize_keys) if options[:params] + ... +end + + +The second line can safely access the +:params+ key, and let the user to pass either +:params+ or "params". + +There's also the bang variant +symbolize_keys!+ that symbolizes keys in the very receiver. + +NOTE: Defined in +active_support/core_ext/hash/keys.rb+. + +h5. +to_options+ and +to_options!+ + +The methods +to_options+ and +to_options!+ are respectively aliases of +symbolize_keys+ and +symbolize_keys!+. + +NOTE: Defined in +active_support/core_ext/hash/keys.rb+. + +h5. +assert_valid_keys+ + +The method +assert_valid_keys+ receives an arbitrary number of arguments, and checks whether the receiver has any key outside that white list. If it does +ArgumentError+ is raised. + + +{:a => 1}.assert_valid_keys(:a) # passes +{:a => 1}.assert_valid_keys("a") # ArgumentError + + +Active Record does not accept unknown options when building associations for example. It implements that control via +assert_valid_keys+: + + +mattr_accessor :valid_keys_for_has_many_association +@@valid_keys_for_has_many_association = [ + :class_name, :table_name, :foreign_key, :primary_key, + :dependent, + :select, :conditions, :include, :order, :group, :having, :limit, :offset, + :as, :through, :source, :source_type, + :uniq, + :finder_sql, :counter_sql, + :before_add, :after_add, :before_remove, :after_remove, + :extend, :readonly, + :validate, :inverse_of +] + +def create_has_many_reflection(association_id, options, &extension) + options.assert_valid_keys(valid_keys_for_has_many_association) + ... +end + + +NOTE: Defined in +active_support/core_ext/hash/keys.rb+. + +h4. Slicing + +Ruby has builtin support for taking slices out of strings and arrays. Active Support extends slicing to hashes: + + +{:a => 1, :b => 2, :c => 3}.slice(:a, :c) +# => {:c => 3, :a => 1} + +{:a => 1, :b => 2, :c => 3}.slice(:b, :X) +# => {:b => 2} # non-existing keys are ignored + + +If the receiver responds to +convert_key+ keys are normalized: + + +{:a => 1, :b => 2}.with_indifferent_access.slice("a") +# => {:a => 1} + + +NOTE. Slicing may come in handy for sanitizing option hashes with a white list of keys. + +There's also +slice!+ which in addition to perform a slice in place returns what's removed: + + +hash = {:a => 1, :b => 2} +rest = hash.slice!(:a) # => {:b => 2} +hash # => {:a => 1} + + +NOTE: Defined in +active_support/core_ext/hash/slice.rb+. + +h4. Indifferent Access + +The method +with_indifferent_access+ returns an +ActiveSupport::HashWithIndifferentAccess+ out of its receiver: + + +{:a => 1}.with_indifferent_access["a"] # => 1 + + +NOTE: Defined in +active_support/core_ext/hash/indifferent_access.rb+. + +h3. Extensions to +Regexp+ + +h4. +multiline?+ + +The method +multiline?+ says whether a regexp has the +/m+ flag set, that is, whether the dot matches newlines. + + +%r{.}.multiline? # => false +%r{.}m.multiline? # => true + +Regexp.new('.').multiline? # => false +Regexp.new('.', Regexp::MULTILINE).multiline? # => true + + +Rails uses this method in a single place, also in the routing code. Multiline regexps are disallowed for route requirements and this flag eases enforcing that constraint. + + +def assign_route_options(segments, defaults, requirements) + ... + if requirement.multiline? + raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" + end + ... +end + + +NOTE: Defined in +active_support/core_ext/regexp.rb+. + +h3. Extensions to +Range+ + +h4. +to_s+ + +Active Support extends the method +Range#to_s+ so that it understands an optional format argument. As of this writing the only supported non-default format is +:db+: + + +(Date.today..Date.tomorrow).to_s +# => "2009-10-25..2009-10-26" + +(Date.today..Date.tomorrow).to_s(:db) +# => "BETWEEN '2009-10-25' AND '2009-10-26'" + + +As the example depicts, the +:db+ format generates a +BETWEEN+ SQL clause. That is used by Active Record in its support for range values in conditions. + +NOTE: Defined in +active_support/core_ext/range/conversions.rb+. + +h4. +step+ + +Active Support extends the method +Range#step+ so that it can be invoked without a block: + + +(1..10).step(2) # => [1, 3, 5, 7, 9] + + +As the example shows, in that case the method returns and array with the corresponding elements. + +NOTE: Defined in +active_support/core_ext/range/blockless_step.rb+. + +h4. +include?+ + +The method +Range#include?+ says whether some value falls between the ends of a given instance: + + +(2..3).include?(Math::E) # => true + + +Active Support extends this method so that the argument may be another range in turn. In that case we test whether the ends of the argument range belong to the receiver themselves: + + +(1..10).include?(3..7) # => true +(1..10).include?(0..7) # => false +(1..10).include?(3..11) # => false +(1...9).include?(3..9) # => false + + +WARNING: The orginal +Range#include?+ is still the one aliased to +Range#===+. + +NOTE: Defined in +active_support/core_ext/range/include_range.rb+. + +h4. +overlaps?+ + +The method +Range#overlaps?+ says whether any two given ranges have non-void intersection: + + +(1..10).overlaps?(7..11) # => true +(1..10).overlaps?(0..7) # => true +(1..10).overlaps?(11..27) # => false + + +NOTE: Defined in +active_support/core_ext/range/overlaps.rb+. + +h3. Extensions to +Proc+ + +h4. +bind+ + +As you surely know Ruby has an +UnboundMethod+ class whose instances are methods that belong to the limbo of methods without a self. The method +Module#instance_method+ returns an unbound method for example: + + +Hash.instance_method(:delete) # => # + + +An unbound method is not callable as is, you need to bind it first to an object with +bind+: + + +clear = Hash.instance_method(:clear) +clear.bind({:a => 1}).call # => {} + + +Active Support defines +Proc#bind+ with an analogous purpose: + + +Proc.new { size }.bind([]).call # => 0 + + +As you see that's callable and bound to the argument, the return value is indeed a +Method+. + +NOTE: To do so +Proc#bind+ actually creates a method under the hood. If you ever see a method with a weird name like +__bind_1256598120_237302+ in a stack trace you know now where it comes from. + +Action Pack uses this trick in +rescue_from+ for example, which accepts the name of a method and also a proc as callbacks for a given rescued exception. It has to call them in either case, so a bound method is returned by +handler_for_rescue+, thus simplifying the code in the caller: + + +def handler_for_rescue(exception) + _, rescuer = Array(rescue_handlers).reverse.detect do |klass_name, handler| + ... + end + + case rescuer + when Symbol + method(rescuer) + when Proc + rescuer.bind(self) + end +end + + +NOTE: Defined in +active_support/core_ext/proc.rb+. + +h3. Extensions to +Date+ + +... + +h3. Extensions to +DateTime+ + +... + +h3. Extensions to +Time+ + +... + +h3. Extensions to +Process+ + +... + +h3. Extensions to +Pathname+ + +... + +h3. Extensions to +File+ + +h4. +atomic_write+ + +With the class method +File.atomic_write+ you can write to a file in a way that will prevent any reader from seeing half-written content. + +The name of the file is passed as an argument, and the method yields a file handle opened for writing. Once the block is done +atomic_write+ closes the file handle and completes its job. + +For example, Action Pack uses this method to write asset cache files like +all.css+: + + +File.atomic_write(joined_asset_path) do |cache| + cache.write(join_asset_file_contents(asset_paths)) +end + + +To accomplish this +atomic_write+ creates a temporary file. That's the file the code in the block actually writes to. On completion, the temporary file is renamed. If the target file exists +atomic_write+ overwrites it and keeps owners and permissions. + +WARNING. Note you can't append with +atomic_write+. + +The auxiliary file is written in a standard directory for temporary files, but you can pass a directory of your choice as second argument. + +NOTE: Defined in +active_support/core_ext/file/atomic.rb+. + +h3. Extensions to +NameError+ + +Active Support adds +missing_name?+ to +NameError+, which tests whether the exception was raised because of the name passed as argument. + +The name may be given as a symbol or string. A symbol is tested against the bare constant name, a string is against the fully-qualified constant name. + +TIP: A symbol can represent a fully-qualified constant name as in +:"ActiveRecord::Base"+, so the behaviour for symbols is defined for convenience, not because it has to be that way technically. + +For example, when an action of +PostsController+ is called Rails tries optimistically to use +PostsHelper+. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that +posts_helper.rb+ raises a +NameError+ due to an actual unknown constant. That should be reraised. The method +missing_name?+ provides a way to distinguish both cases: + + +def default_helper_module! + module_name = name.sub(/Controller$/, '') + module_path = module_name.underscore + helper module_path +rescue MissingSourceFile => e + raise e unless e.is_missing? "#{module_path}_helper" +rescue NameError => e + raise e unless e.missing_name? "#{module_name}Helper" +end + + +NOTE: Defined in +active_support/core_ext/name_error.rb+. + +h3. Extensions to +LoadError+ + +Rails hijacks +LoadError.new+ to return a +MissingSourceFile+ exception: + + +$ ruby -e 'require "nonexistent"' +...: no such file to load -- nonexistent (LoadError) +... +$ script/runner 'require "nonexistent"' +...: no such file to load -- nonexistent (MissingSourceFile) +... + + +The class +MissingSourceFile+ is a subclass of +LoadError+, so any code that rescues +LoadError+ as usual still works as expected. Point is these exception objects respond to +is_missing?+, which given a path name tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension). + +For example, when an action of +PostsController+ is called Rails tries to load +posts_helper.rb+, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist, but it in turn requires another library that is missing. In that case Rails must reraise the exception. The method +is_missing?+ provides a way to distinguish both cases: + + +def default_helper_module! + module_name = name.sub(/Controller$/, '') + module_path = module_name.underscore + helper module_path +rescue MissingSourceFile => e + raise e unless e.is_missing? "#{module_path}_helper" +rescue NameError => e + raise e unless e.missing_name? "#{module_name}Helper" +end + + +NOTE: Defined in +active_support/core_ext/load_error.rb+. + +h3. Changelog + +"Lighthouse ticket":https://rails.lighthouseapp.com/projects/16213/tickets/67 + +* April 18, 2009: Initial version by "Xavier Noria":credits.html#fxn diff --git a/railties/guides/source/active_support_overview.textile b/railties/guides/source/active_support_overview.textile deleted file mode 100644 index aea77c8d4e81f..0000000000000 --- a/railties/guides/source/active_support_overview.textile +++ /dev/null @@ -1,818 +0,0 @@ -h2. Active Support Overview - -Active Support is the Rails component responsible for providing Ruby language extensions, utilities, and other transversal stuff. It offers a richer bottom-line at the language level, targeted both at the development of Rails applications, and at the development of Rails itself. - -By referring to this guide you will learn: - -* The extensions to the Ruby core modules and classes provided by Rails. -* The rest of fundamental libraries available in Rails. - -endprologue. - -h3. Extensions to All Objects - -h4. +blank?+ and +present?+ - -The following values are considered to be blank in a Rails application: - -* +nil+ and +false+, - -* strings composed only of whitespace, i.e. matching +/\A\s*\z/+, - -* empty arrays and hashes, and - -* any other object that responds to +empty?+ and it is empty. - -WARNING: Note that numbers are not mentioned, in particular 0 and 0.0 are *not* blank. - -For example, this method from +ActionDispatch::Response+ uses +blank?+ to easily be robust to +nil+ and whitespace strings in one shot: - - -def charset - charset = String(headers["Content-Type"] || headers["type"]).split(";")[1] - charset.blank? ? nil : charset.strip.split("=")[1] -end - - -That's a typical use case for +blank?+. - -Here, the method Rails runs to instantiate observers upon initialization has nothing to do if there are none: - - -def instantiate_observers - return if @observers.blank? - # ... -end - - -The method +present?+ is equivalent to +!blank?+: - - -assert @response.body.present? # same as !@response.body.blank? - - -h4. +duplicable?+ - -A few fundamental objects in Ruby are singletons. For example, in the whole live of a program the integer 1 refers always to the same instance: - - -1.object_id # => 3 -Math.cos(0).to_i.object_id # => 3 - - -Hence, there's no way these objects can be duplicated through +dup+ or +clone+: - - -true.dup # => TypeError: can't dup TrueClass - - -Some numbers which are not singletons are not duplicable either: - - -0.0.clone # => allocator undefined for Float -(2**1024).clone # => allocator undefined for Bignum - - -Active Support provides +duplicable?+ to programmatically query an object about this property: - - -"".duplicable? # => true -false.duplicable? # => false - - -By definition all objects are +duplicable?+ except +nil+, +false+, +true+, symbols, numbers, and class objects. - -WARNING. Using +duplicable?+ is discouraged because it depends on a hard-coded list. Classes have means to disallow duplication like removing +dup+ and +clone+ or raising exceptions from them, only +rescue+ can tell. - -h4. +returning+ - -The method +returning+ yields its argument to a block and returns it. You tipically use it with a mutable object that gets modified in the block: - - -def html_options_for_form(url_for_options, options, *parameters_for_url) - returning options.stringify_keys do |html_options| - html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart") - html_options["action"] = url_for(url_for_options, *parameters_for_url) - end -end - - -See also "+Object#tap+":#tap. - -h4. +tap+ - -+Object#tap+ exists in Ruby 1.8.7 and 1.9, and it is defined by Active Support for previous versions. This method yields its receiver to a block and returns it. - -For example, the following class method from +ActionDispatch::TestResponse+ creates, initializes, and returns a new test response using +tap+: - - -def self.from_response(response) - new.tap do |resp| - resp.status = response.status - resp.headers = response.headers - resp.body = response.body - end -end - - -See also "+Object#returning+":#returning. - -h4. +try+ - -Sometimes you want to call a method provided the receiver object is not +nil+, which is something you usually check first. - -For instance, note how this method of +ActiveRecord::ConnectionAdapters::AbstractAdapter+ checks if there's a +@logger+: - - -def log_info(sql, name, ms) - if @logger && @logger.debug? - name = '%s (%.1fms)' % [name || 'SQL', ms] - @logger.debug(format_log_entry(name, sql.squeeze(' '))) - end -end - - -You can shorten that using +Object#try+. This method is a synonim for +Object#send+ except that it returns +nil+ if sent to +nil+. The previous example could then be rewritten as: - - -def log_info(sql, name, ms) - if @logger.try(:debug?) - name = '%s (%.1fms)' % [name || 'SQL', ms] - @logger.debug(format_log_entry(name, sql.squeeze(' '))) - end -end - - -h4. +metaclass+ - -The method +metaclass+ returns the singleton class on any object: - - -String.metaclass # => # -String.new.metaclass # => #> - - -h4. +class_eval(*args, &block)+ - -You can evaluate code in the context of any object's singleton class using +class_eval+: - - -class Proc - def bind(object) - block, time = self, Time.now - object.class_eval do - method_name = "__bind_#{time.to_i}_#{time.usec}" - define_method(method_name, &block) - method = instance_method(method_name) - remove_method(method_name) - method - end.bind(object) - end -end - - -h4. +acts_like?(duck)+ - -The method +acts_like+ provides a way to check whether some class acts like some other class based on a simple convention: a class that provides the same interface as +String+ defines - - -def acts_like_string? -end - - -which is only a marker, its body or return value are irrelevant. Then, client code can query for duck-type-safeness this way: - - -some_klass.acts_like?(:string) - - -Rails has classes that act like +Date+ or +Time+ and follow this contract. - -h4. +to_param+ - -All objects in Rails respond to the method +to_param+, which is meant to return something that represents them as values in a query string, or as a URL fragments. - -By default +to_param+ just calls +to_s+: - - -7.to_param # => "7" - - -The return value of +to_param+ should *not* be escaped: - - -"Tom & Jerry".to_param # => "Tom & Jerry" - - -Several classes in Rails overwrite this method. - -For example +nil+, +true+, and +false+ return themselves. +Array#to_param+ calls +to_param+ on the elements and joins the result with "/": - - -[0, true, String].to_param # => "0/true/String" - - -Notably, the Rails routing system calls +to_param+ on models to get a value for the +:id+ placeholder. +ActiveRecord::Base#to_param+ returns the +id+ of a model, but you can redefine that method in your models. For example, given - - -class User - def to_param - "#{id}-#{name.parameterize}" - end -end - - -we get: - - -user_path(@user) # => "/users/357-john-smith" - - -WARNING. Controllers need to be aware of any redifinition of +to_param+ because when a request like that comes in "357-john-smith" is the value of +params[:id]+. - -h4. +to_query+ - -Except for hashes, given an unescaped +key+ this method constructs the part of a query string that would map such key to what +to_param+ returns. For example, given - - -class User - def to_param - "#{id}-#{name.parameterize}" - end -end - - -we get: - - -current_user.to_query('user') # => user=357-john-smith - - -This method escapes whatever is needed, both for the key and the value: - - -account.to_query('company[name]') -# => "company%5Bname%5D=Johnson+%26+Johnson" - - -so its output is ready to be used in a query string. - -Arrays return the result of applying +to_query+ to each element with _key_[] as key, and join the result with "/": - - -[3.4, -45.6].to_query('sample') -# => "sample%5B%5D=3.4&sample%5B%5D=-45.6" - - -Hashes also respond to +to_query+ but with a different signature. If no argument is passed a call generates a sorted series of key/value assigments calling +to_query(key)+ on its values. Then it joins the result with "&": - - -{:c => 3, :b => 2, :a => 1}.to_query # => "a=1&b=2&c=3" - - -The method +Hash#to_query+ accepts an optional namespace for the keys: - - -{:id => 89, :name => "John Smith"}.to_query('user') -# => "user%5Bid%5D=89&user%5Bname%5D=John+Smith" - - -h4. +with_options+ - -The method +with_options+ provides a way to factor out common options in a series of method calls. - -Given a default options hash, +with_options+ yields a proxy object to a block. Within the block, methods called on the proxy are forwarded to the receiver with their options merged. For example, you get rid of the duplication in: - - -class Account < ActiveRecord::Base - has_many :customers, :dependent => :destroy - has_many :products, :dependent => :destroy - has_many :invoices, :dependent => :destroy - has_many :expenses, :dependent => :destroy -end - - -this way: - - -class Account < ActiveRecord::Base - with_options :dependent => :destroy do |assoc| - assoc.has_many :customers - assoc.has_many :products - assoc.has_many :invoices - assoc.has_many :expenses - end -end - - -That idiom may convey _grouping_ to the reader as well. For example, say you want to send a newsletter whose language depends on the user. Somewhere in the mailer you could group locale-dependent bits like this: - - -I18n.with_options :locale => user.locale, :scope => "newsletter" do |i18n| - subject i18n.t :subject - body i18n.t :body, :user_name => user.name -end - - -TIP: Since +with_options+ forwards calls to its receiver they can be nested. Each nesting level will merge inherited defaults in addition to their own. - -h4. Instance Variables - -Active Support provides several methods to ease access to instance variables. - -h5. +instance_variable_defined?+ - -The method +instance_variable_defined?+ exists in Ruby 1.8.6 and later, and it is defined for previous versions anyway: - - -class C - def initialize - @a = 1 - end - - def m - @b = 2 - end -end - -c = C.new - -c.instance_variable_defined?("@a") # => true -c.instance_variable_defined?(:@a) # => true -c.instance_variable_defined?("a") # => NameError: `a' is not allowed as an instance variable name - -c.instance_variable_defined?("@b") # => false -c.m -c.instance_variable_defined?("@b") # => true - - -h5. +instance_variable_names+ - -Ruby 1.8 and 1.9 have a method called +instance_variables+ that returns the names of the defined instance variables. But they behave differently, in 1.8 it returns strings whereas in 1.9 it returns symbols. Active Support defines +instance_variable_names+ as a portable way to obtain them as strings: - - -class C - def initialize(x, y) - @x, @y = x, y - end -end - -C.new(0, 1).instance_variable_names # => ["@y", "@x"] - - -WARNING: The order in which the names are returned is unespecified, and it indeed depends on the version of the interpreter. - -h5. +instance_values+ - -The method +instance_values+ returns a hash that maps instance variable names without "@" to their -corresponding values. Keys are strings both in Ruby 1.8 and 1.9: - - -class C - def initialize(x, y) - @x, @y = x, y - end -end - -C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} - - -h5. +copy_instance_variables_from(object, exclude = [])+ - -Copies the instance variables of +object+ into +self+. - -Instance variable names in the +exclude+ array are ignored. If +object+ -responds to +protected_instance_variables+ the ones returned are -also ignored. For example, Rails controllers implement that method. - -In both arrays strings and symbols are understood, and they have to include -the at sign. - - -class C - def initialize(x, y, z) - @x, @y, @z = x, y, z - end - - def protected_instance_variables - %w(@z) - end -end - -a = C.new(0, 1, 2) -b = C.new(3, 4, 5) - -a.copy_instance_variables_from(b, [:@y]) -# a is now: @x = 3, @y = 1, @z = 2 - - -In the example +object+ and +self+ are of the same type, but they don't need to. - -h4. Silencing Warnings, Streams, and Exceptions - -The methods +silence_warnings+ and +enable_warnings+ change the value of +$VERBOSE+ accordingly for the duration of their block, and reset it afterwards: - - -silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger } - - -You can silence any stream while a block runs with +silence_stream+: - - -silence_stream(STDOUT) do - # STDOUT is silent here -end - - -Silencing exceptions is also possible with +suppress+. This method receives an arbitrary number of exception classes. If an exception is raised during the execution of the block and is +kind_of?+ any of the arguments, +suppress+ captures it and returns silently. Otherwise the exception is reraised: - - -# If the user is locked the increment is lost, no big deal. -suppress(ActiveRecord::StaleObjectError) do - current_user.increment! :visits -end - - -h3. Extensions to +Module+ - -... - -h3. Extensions to +Class+ - -h4. Class Attribute Accessors - -The macros +cattr_reader+, +cattr_writer+, and +cattr_accessor+ are analogous to their +attr_*+ counterparts but for classes. They initialize a class variable to +nil+ unless it already exists, and generate the corresponding class methods to access it: - - -class MysqlAdapter < AbstractAdapter - # Generates class methods to access @@emulate_booleans. - cattr_accessor :emulate_booleans - self.emulate_booleans = true -end - - -Instance methods are created as well for convenience. For example given - - -module ActionController - class Base - cattr_accessor :logger - end -end - - -we can access +logger+ in actions. The generation of the writer instance method can be prevented setting +:instance_writer+ to +false+ (not any false value, but exactly +false+): - - -module ActiveRecord - class Base - # No pluralize_table_names= instance writer is generated. - cattr_accessor :pluralize_table_names, :instance_writer => false - end -end - - -h4. Class Inheritable Attributes - -Class variables are shared down the inheritance tree. Class instance variables are not shared, but they are not inherited either. The macros +class_inheritable_reader+, +class_inheritable_writer+, and +class_inheritable_accessor+ provide accesors for class-level data which is inherited but not shared with children: - - -module ActionController - class Base - # FIXME: REVISE/SIMPLIFY THIS COMMENT. - # The value of allow_forgery_protection is inherited, - # but its value in a particular class does not affect - # the value in the rest of the controllers hierarchy. - class_inheritable_accessor :allow_forgery_protection - end -end - - -They accomplish this with class instance variables and cloning on subclassing, there are no class variables involved. Cloning is performed with +dup+ as long as the value is duplicable. - -There are some variants specialised in arrays and hashes: - - -class_inheritable_array -class_inheritable_hash - - -Those writers take any inherited array or hash into account and extend them rather than overwrite them. - -As with vanilla class attribute accessors these macros create convenience instance methods for reading and writing. The generation of the writer instance method can be prevented setting +:instance_writer+ to +false+ (not any false value, but exactly +false+): - - -module ActiveRecord - class Base - class_inheritable_accessor :default_scoping, :instance_writer => false - end -end - - -Since values are copied when a subclass is defined, if the base class changes the attribute after that, the subclass does not see the new value. That's the point. - -There's a related macro called +superclass_delegating_accessor+, however, that does not copy the value when the base class is subclassed. Instead, it delegates reading to the superclass as long as the attribute is not set via its own writer. For example, +ActionMailer::Base+ defines +delivery_method+ this way: - - -module ActionMailer - class Base - superclass_delegating_accessor :delivery_method - self.delivery_method = :smtp - end -end - - -If for whatever reason an application loads the definition of a mailer class and after that sets +ActionMailer::Base.delivery_method+, the mailer class will still see the new value. In addition, the mailer class is able to change the +delivery_method+ without affecting the value in the parent using its own inherited class attribute writer. - -h4. Subclasses - -The +subclasses+ method returns the names of all subclasses of a given class as an array of strings. That comprises not only direct subclasses, but all descendants down the hierarchy: - - -class C; end -C.subclasses # => [] - -Integer.subclasses # => ["Bignum", "Fixnum"] - -module M - class A; end - class B1 < A; end - class B2 < A; end -end - -module N - class C < M::B1; end -end - -M::A.subclasses # => ["N::C", "M::B2", "M::B1"] - - -The order in which these class names are returned is unspecified. - -See also +Object#subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME. - -h4. Class Removal - -Roughly speaking, the +remove_class+ method removes the class objects passed as arguments: - - -Class.remove_class(Hash, Dir) # => [Hash, Dir] -Hash # => NameError: uninitialized constant Hash -Dir # => NameError: uninitialized constant Dir - - -More specifically, +remove_class+ attempts to remove constants with the same name as the passed class objects from their parent modules. So technically this method does not guarantee the class objects themselves are not still valid and alive somewhere after the method call: - - -module M - class A; end - class B < A; end -end - -A2 = M::A - -M::A.object_id # => 13053950 -Class.remove_class(M::A) - -M::B.superclass.object_id # => 13053950 (same object as before) -A2.name # => "M::A" (name is hard-coded in object) - - -WARNING: Removing fundamental classes like +String+ can result in really funky behaviour. - -The method +remove_subclasses+ provides a shortcut for removing all descendants of a given class, where "removing" has the meaning explained above: - - -class A; end -class B1 < A; end -class B2 < A; end -class C < A; end - -A.subclasses # => ["C", "B2", "B1"] -A.remove_subclasses -A.subclasses # => [] -C # => NameError: uninitialized constant C - - -See also +Object#remove_subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME. - -h3. Extensions to +NilClass+ - -... - -h3. Extensions to +TrueClass+ - -... - -h3. Extensions to +FalseClass+ - -... - -h3. Extensions to +Symbol+ - -... - -h3. Extensions to +String+ - -... - -h3. Extensions to +Numeric+ - -... - -h3. Extensions to +Integer+ - -... - -h3. Extensions to +Float+ - -... - -h3. Extensions to +BigDecimal+ - -... - -h3. Extensions to +Enumerable+ - -... - -h3. Extensions to +Array+ - -h4. Accessing - -Active Support augments the API of arrays to ease certain ways of accessing them. For example, +to+ returns the subarray of elements up to the one at the passed index: - - -%w(a b c d).to(2) # => %w(a b c) -[].to(7) # => [] - - -Similarly, +from+ returns the tail from the element at the passed index on: - - -%w(a b c d).from(2) # => %w(c d) -%w(a b c d).from(10) # => nil -[].from(0) # => nil - - -The methods +second+, +third+, +fourth+, and +fifth+ return the corresponding element (+first+ is builtin). Thanks to social wisdom and positive constructiveness all around, +forty_two+ is also available. - -You can pick a random element with +rand+: - - -shape_type = [Circle, Square, Triangle].rand - - -h4. Grouping - -h5. +in_groups_of(number, fill_with = nil)+ - -The method +in_groups_of+ splits an array into consecutive groups of a certain size. It returns an array with the groups: - - -[1, 2, 3].in_groups_of(2) # => [[1, 2], [3, nil]] - - -or yields them in turn if a block is passed: - - -<% sample.in_groups_of(3) do |a, b, c| %> - - <%=h a %> - <%=h b %> - <%=h c %> - -<% end %> - - -The first example shows +in_groups_of+ fills the last group with as many +nil+ elements as needed to have the requested size. You can change this padding value using the second optional argument: - - -[1, 2, 3].in_groups_of(2, 0) # => [[1, 2], [3, 0]] - - -And you can tell the method not to fill the last group passing +false+: - - -[1, 2, 3].in_groups_of(2, false) # => [[1, 2], [3]] - - -As a consequence +false+ can't be a used as a padding value. - -h5. +in_groups(number, fill_with = nil)+ - -The method +in_groups+ splits an array into a certain number of groups. The method returns and array with the groups: - - -%w(1 2 3 4 5 6 7).in_groups(3) -# => [["1", "2", "3"], ["4", "5", nil], ["6", "7", nil]] - - -or yields them in turn if a block is passed: - - -%w(1 2 3 4 5 6 7).in_groups(3) {|group| p group} -["1", "2", "3"] -["4", "5", nil] -["6", "7", nil] - - -The examples above show that +in_groups+ fills some groups with a trailing +nil+ element as needed. A group can get at most one of these extra elements, the rightmost one if any. And the groups that have them are always the last ones. - -You can change this padding value using the second optional argument: - - -%w(1 2 3 4 5 6 7).in_groups(3, "0") -# => [["1", "2", "3"], ["4", "5", "0"], ["6", "7", "0"]] - - -And you can tell the method not to fill the smaller groups passing +false+: - - -%w(1 2 3 4 5 6 7).in_groups(3, false) -# => [["1", "2", "3"], ["4", "5"], ["6", "7"]] - - -As a consequence +false+ can't be a used as a padding value. - -h5. +split(value = nil)+ - -The method +split+ divides an array by a separator and returns the resulting chunks. - -If a block is passed the separators are those elements of the array for which the block returns true: - - -(-5..5).to_a.split { |i| i.multiple_of?(4) } -# => [[-5], [-3, -2, -1], [1, 2, 3], [5]] - - -Otherwise, the value received as argument, which defaults to +nil+, is the separator: - - -[0, 1, -5, 1, 1, "foo", "bar"].split(1) -# => [[0], [-5], [], ["foo", "bar"]] - - -NOTE: Observe in the previous example that consecutive separators result in empty arrays. - -h3. Extensions to +Hash+ - -... - -h3. Extensions to +Range+ - -... - -h3. Extensions to +Proc+ - -... - -h3. Extensions to +Date+ - -... - -h3. Extensions to +DateTime+ - -... - -h3. Extensions to +Time+ - -... - -h3. Extensions to +Process+ - -... - -h3. Extensions to +Pathname+ - -... - -h3. Extensions to +File+ - -... - -h3. Extensions to +Exception+ - -... - -h3. Extensions to +NameError+ - -... - -h3. Extensions to +LoadError+ - -... - -h3. Extensions to +CGI+ - -... - -h3. Extensions to +Benchmark+ - -... - -h3. Changelog - -"Lighthouse ticket":https://rails.lighthouseapp.com/projects/16213/tickets/67 - -* April 18, 2009: Initial version by "Xavier Noria":credits.html#fxn diff --git a/railties/guides/source/activerecord_validations_callbacks.textile b/railties/guides/source/activerecord_validations_callbacks.textile index 03d521ea1f968..9d0ee29ff2d32 100644 --- a/railties/guides/source/activerecord_validations_callbacks.textile +++ b/railties/guides/source/activerecord_validations_callbacks.textile @@ -403,6 +403,47 @@ WARNING. Note that some databases are configured to perform case-insensitive sea The default error message for +validates_uniqueness_of+ is "_has already been taken_". +h4. +validates_with+ + +This helper passes the record to a separate class for validation. + + +class Person < ActiveRecord::Base + validates_with GoodnessValidator +end + +class GoodnessValidator < ActiveRecord::Validator + def validate + if record.first_name == "Evil" + record.errors[:base] << "This person is evil" + end + end +end + + +The +validates_with+ helper takes a class, or a list of classes to use for validation. There is no default error message for +validates_with+. You must manually add errors to the record's errors collection in the validator class. + +The validator class has two attributes by default: + +* +record+ - the record to be validated +* +options+ - the extra options that were passed to +validates_with+ + +Like all other validations, +validates_with+ takes the +:if+, +:unless+ and +:on+ options. If you pass any other options, it will send those options to the validator class as +options+: + + +class Person < ActiveRecord::Base + validates_with GoodnessValidator, :fields => [:first_name, :last_name] +end + +class GoodnessValidator < ActiveRecord::Validator + def validate + if options[:fields].any?{|field| record.send(field) == "Evil" } + record.errors[:base] << "This person is evil" + end + end +end + + 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. diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile index 74e8dec5cf2f5..8a38cf2dc2831 100644 --- a/railties/guides/source/ajax_on_rails.textile +++ b/railties/guides/source/ajax_on_rails.textile @@ -3,9 +3,8 @@ h2. AJAX on Rails This guide covers the built-in Ajax/Javascript functionality of Rails (and more); it will enable you to create rich and dynamic AJAX applications with ease! We will cover the following topics: * Quick introduction to AJAX and related technologies -* Handling Javascript the Rails way: Rails helpers, RJS, Prototype and script.aculo.us +* Handling Javascript the Rails way: Rails helpers, RJS, Prototype and script.aculo.us * Testing Javascript functionality -* Becoming an Ajax Master on Rails: Plugins, Best Practices, Tips and Tricks endprologue. @@ -30,65 +29,314 @@ How do 'standard' and AJAX requests differ, why does this matter for understandi -h3. Built-in Rails Helpers +h3. Built-in Rails Helpers -Mostly a reference to standard JS helpers like link_to_remote, remote_form_for etc + some explanation +Rails' Javascript framework of choice is "Prototype":http://www.prototypejs.org. Prototype is a generic-purpose Javascript framework that aims to ease the development of dynamic web applications by offering DOM manipulation, AJAX and other Javascript functionality ranging from utility functions to object oriented constructs. It is not specifically written for any language, so Rails provides a set of helpers to enable seamless integration of Prototype with your Rails views. +To get access to these helpers, all you have to do is to include the prototype framework in your pages - typically in your master layout, application.html.erb - like so: + +javascript_include_tag 'prototype' + +You are ready to add some AJAX love to your Rails app! -h3. Responding to AJAX the Rails way: RJS +h4. The Quintessential AJAX Rails Helper: link_to_remote -In the last section we sent some AJAX requests to the server; now we need to respond, and the standard Rails way to this is using RJS; RJS intro, function reference +Let's start with the the probably most often used helper: +link_to_remote+, which has an interesting feature from the documentation point of view: the options supplied to +link_to_remote+ are shared by all other AJAX helpers, so learning the mechanics and options of +link_to_remote+ is a great help when using other helpers. +The signature of +link_to_remote+ function is the same as that of the standard +link_to+ helper: + +def link_to_remote(name, options = {}, html_options = nil) + -h3. I Want my Yellow Thingy: Prototype and Script.aculo.us +And here is a simple example of link_to_remote in action: -Walk through prototype and script.aculo.us, most important functionality, method reference etc. + +link_to_remote "Add to cart", + :url => add_to_cart_url(product.id), + :update => "cart" + +* The very first parameter, a string, is the text of the link which appears on the page. +* The second parameter, the +options+ hash is the most interesting part as it has the AJAX specific stuff: +** *:url* This is the only parameter that is always required to generate the simplest remote link (technically speaking, it is not required, you can pass an empty +options+ hash to +link_to_remote+ - but in this case the URL used for the POST request will be equal to your current URL which is probably not your intention). This URL points to your AJAX action handler. The URL is typically specified by Rails REST view helpers, but you can use the +url_for+ format too. +** *:update* There are basically two ways of injecting the server response into the page: One is involving RJS and we will discuss it in the next chapter, and the other is specifying a DOM id of the element we would like to update. The above example demonstrates the simplest way of accomplishing this - however, we are in trouble if the server responds with an error message because that will be injected into the page too! However, Rails has a solution for this situation: -h3. Testing Javascript + +link_to_remote "Add to cart", + :url => add_to_cart_url(product), + :update => { :success => "cart", :failure => "error" } + -Javascript testing reminds me the definition of the world 'classic' by Mark Twain: "A classic is something that everybody wants to have read and nobody wants to read." It's similar with Javascript testing: everyone would like to have it, yet it's not done by too much developers as it is tedious, complicated, there is a proliferation of tools and no consensus/accepted best practices, but we will nevertheless take a stab at it: +If the server returns 200, the output of the above example is equivalent to our first, simple one. However, in case of error, the element with the DOM id +error+ is updated rather than the +cart+ element. -* (Fire)Watir -* Selenium -* Celerity/Culerity -* Cucumber+Webrat -* Mention stuff like screw.unit/jsSpec +** *position* By default (i.e. when not specifying this option, like in the examples before) the repsonse is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities: +*** +:before+ Inserts the response text just before the target element. More precisely, it creates a text node from the response and inserts it as the left sibling of the target element. +*** +:after+ Similar behavior to +:before+, but in this case the response is inserted after the target element. +*** +:top+ Inserts the text into the target element, before it's original content. If the target element was empty, this is equivalent with not specifying +:position+ at all. +*** +:bottom+ The counterpart of +:top+: the response is inserted after the target element's original content. + +A typical example of using +:bottom+ is inserting a new <li> element into an existing list: + + +link_to_remote "Add new item", + :url => items_url, + :update => 'item_list', + :position => :bottom + + +** *:method* Most typically you want to use a POST request when adding a remote link to your view so this is the default behavior. However, sometimes you'll want to update (PUT) or delete/destroy (DELETE) something and you can specify this with the +:method+ option. Let's see an example for a typical AJAX link for deleting an item from a list: + + +link_to_remote "Delete the item", + :url => item_url(item), + :method => :delete + + +Note that if we wouldn't override the default behavior (POST), the above snippet would route to the create action rather than destroy. + +** *JavaScript filters* You can customize the remote call further by wrapping it with some JavaScript code. Let's say in the previous example, when deleting a link, you'd like to ask for a confirmation by showing a simple modal text box to the user. This is a typical example what you can accomplish with these options - let's see them one by one: +*** +:confirm+ => +msg+ Pops up a JavaScript confirmation dialog, displaying +msg+. If the user chooses 'OK', the request is launched, otherwise canceled. +*** +:condition+ => +code+ Evaluates +code+ (which should evaluate to a boolean) and proceeds if it's true, cancels the request otherwise. +*** +:before+ => +code+ Evaluates the +code+ just before launching the request. The output of the code has no influence on the execution. Typically used show a progress indicator (see this in action in the next example). +*** +:after+ => +code+ Evaluates the +code+ after launching the request. Note that this is different from the +:success+ or +:complete+ callback (covered in the next section) since those are triggered after the request is completed, while the code snippet passed to +:after+ is evaluated after the remote call is made. A common example is to disable elements on the page or otherwise prevent further action while the request is completed. +*** +:submit+ => +dom_id+ This option does not make sense for +link_to_remote+, but we'll cover it for the sake of completeness. By default, the parent element of the form elements the user is going to submit is the current form - use this option if you want to change the default behavior. By specifying this option you can change the parent element to the element specified by the DOM id +dom_id+. +*** +:with+ > +code+ The JavaScript code snippet in +code+ is evaluated and added to the request URL as a parameter (or set of parameters). Therefore, +code+ should return a valid URL query string (like "item_type=8" or "item_type=8&sort=true"). Usually you want to obtain some value(s) from the page - let's see an example: + + +link_to_remote "Update record", + :url => record_url(record), + :method => :put, + :with => "'status=' + 'encodeURIComponent($('status').value) + '&completed=' + $('completed')" + + +This generates a remote link which adds 2 parameters to the standard URL generated by Rails, taken from the page (contained in the elements matched by the 'status' and 'completed' DOM id). + +** *Callbacks* Since an AJAX call is typically asynchronous, as it's name suggests (this is not a rule, and you can fire a synchronous request - see the last option, +:type+) your only way of communicating with a request once it is fired is via specifying callbacks. There are six options at your disposal (in fact 508, counting all possible response types, but these six are the most frequent and therefore specified by a constant): +*** +:loading:+ => +code+ The request is in the process of receiving the data, but the transfer is not completed yet. +*** +:loaded:+ => +code+ The transfer is completed, but the data is not processed and returned yet +*** +:interactive:+ => +code+ One step after +:loaded+: The data is fully received and being processed +*** +:success:+ => +code+ The data is fully received, parsed and the server responded with "200 OK" +*** +:failure:+ => +code+ The data is fully received, parsed and the server responded with *anything* but "200 OK" (typically 404 or 500, but in general with any status code ranging from 100 to 509) +*** +:complete:+ => +code+ The combination of the previous two: The request has finished receiving and parsing the data, and returned a status code (which can be anything). +*** Any other status code ranging from 100 to 509: Additionally you might want to check for other HTTP status codes, such as 404. In this case simply use the status code as a number: + +link_to_remote "Add new item", + :url => items_url, + :update => "item_list", + 404 => "alert('Item not found!')" + +Let's see a typical example for the most frequent callbacks, +:success+, +:failure+ and +:complete+ in action: + +link_to_remote "Add new item", + :url => items_url, + :update => "item_list", + :before => "$('progress').show()", + :complete => "$('progress').hide()", + :success => "display_item_added(request)", + :failure => "display_error(request)", + +** *:type* If you want to fire a synchronous request for some obscure reason (blocking the browser while the request is processed and doesn't return a status code), you can use the +:type+ option with the value of +:synchronous+. +* Finally, using the +html_options+ parameter you can add HTML attributes to the generated tag. It works like the same parameter of the +link_to+ helper. There are interesting side effects for the +href+ and +onclick+ parameters though: +** If you specify the +href+ parameter, the AJAX link will degrade gracefully, i.e. the link will point to the URL even if JavaScript is disabled in the client browser +** +link_to_remote+ gains it's AJAX behavior by specifying the remote call in the onclick handler of the link. If you supply +html_options[:onclick]+ you override the default behavior, so use this with care! + +We are finished with +link_to_remote+. I know this is quite a lot to digest for one helper function, but remember, these options are common for all the rest of the Rails view helpers, so we will take a look at the differences / additional parameters in the next sections. + +h4. AJAX Forms + +There are three different ways of adding AJAX forms to your view using Rails Prototype helpers. They are slightly different, but striving for the same goal: instead of submitting the form using the standard HTTP request/response cycle, it is submitted asynchronously, thus not reloading the page. These methods are the following: + +* +remote_form_for+ (and it's alias +form_remote_for+) is tied to Rails most tightly of the three since it takes a resource, model or array of resources (in case of a nested resource) as a parameter. +* +form_remote_tag+ AJAXifies the form by serializing and sending it's data in the background +* +submit_to_remote+ and +button_to_remote+ is more rarely used than the previous two. Rather than creating an AJAX form, you add a button/input + +Let's se them in action one by one! + +h5. +remote_form_for+ + +h5. +form_remote_tag+ + +h5. +submit_to_remote+ + +h4. Observing Elements + +h5. +observe_field+ + +h5. +observe_form+ + +h4. Calling a Function Periodically + +h5. +periodically_call_remote+ + + +h4. Miscellaneous Functionality + +h5. +remote_function+ + +h5. +update_page+ + + +h3. JavaScript the Rails way: RJS + +In the last section we sent some AJAX requests to the server, and inserted the HTML response into the page (with the +:update+ option). However, sometimes a more complicated interaction with the page is needed, which you can either achieve with JavaScript... or with RJS! You are sending JavaScript instructions to the server in both cases, but while in the former case you have to write vanilla JavaScript, in the second you can code Rails, and sit back while Rails generates the JavaScript for you - so basically RJS is a Ruby DSL to write JavaScript in your Rails code. + +h4. Javascript without RJS + +First we'll check out how to send JavaScript to the server manually. You are practically never going to need this, but it's interesting to understand what's going on under the hood. + + +def javascript_test + render :text => "alert('Hello, world!')", + :content_type => "text/javascript" +end + + +(Note: if you want to test the above method, create a +link_to_remote+ with a single parameter - +:url+, pointing to the +javascript_test+ action) + +What happens here is that by specifying the Content-Type header variable, we instruct the browser to evaluate the text we are sending over (rather than displaying it as plain text, which is the default behavior). + +h4. Inline RJS + +As we said, the purpose of RJS is to write Ruby which is then auto-magically turned into JavaScript by Rails. The above example didn't look too Ruby-esque so let's see how to do it the Rails way: + + +def javascript_test + render :update do |page| + page.alert "Hello from inline RJS" + end +end + + +The above code snippet does exactly the same as the one in the previous section - going about it much more elegantly though. You don't need to worry about headers,write ugly JavaScript code into a string etc. When the first parameter to +render+ is +:update+, Rails expects a block with a single parameter (+page+ in our case, which is the traditional naming convention) which is an instance of the JavaScriptGenerator:"http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper/JavaScriptGenerator/GeneratorMethods.html" object. As it's name suggests, JavaScriptGenerator is responsible for generating JavaScript from your Ruby code. You can execute multiple method calls on the +page+ instance - it's all turned into JavaScript code and sent to the server with the appropriate Content Type, "text/javascript". + +h4. RJS Templates -Note to self: check out the RailsConf JS testing video +If you don't want to clutter your controllers with view code (especially when your inline RJS is more than a few lines), you can move your RJS code to a template file. RJS templates should go to the +/app/views/+ directory, just as +.html.erb+ or any other view files of the appropriate controller, conventionally named +js.rjs+. -h3. Useful Plugins +To rewrite the above example, you can leave the body of the action empty, and create a RJS template named +javascript_test.js.rjs+, containing the following line: -This was in the ticket description, but at the moment I don't really have clue what to add here, so please tell me + +page.alert "Hello from inline RJS" + +h4. RJS Reference +In this section we'll go through the methods RJS offers. -h3. Tips and Tricks +h5. JavaScriptGenerator Methods -* Unobtrusive Javascript (Prototype events, maybe the jQuery way (esp. jQeury.live())) +h6. DOM Element Manipulation -* Minimize communication with the server - there does not have to be a communication at all! -** If you absolutely don't have to, don't use Rails observers -** Cache stuff on the client side, e.g. with auto-complete +It is possible to manipulate multiple elements at once through the +page+ JavaScriptGenerator instance. Let's see this in action: -* Using AJAX to load stuff asynchronously -** To avoid page blocking -** Tricking page caching -*** inserting user-specific info into a cached page -*** anti-CSFR bit + +page.show :div_one, :div_two +page.hide :div_one +page.remove :div_one, :div_two, :div_three +page.toggle :other_div + -* Jumping to the top? Try event.stopPropagation +The above methods (+show+, +hide+, +remove+, +toggle+) have the same semantics as the Prototype methods of the same name. You can pass an arbitrary number (but at least one) of DOM ids to these calls. -* Performance -** pack your javascript (minify, asset packager) -** require your JS at the end of the file -** other perf tricks and optimization -* Don't overuse AJAX -** Usability first, cool effects second -** situations where AJAX is discouraged +h6. Inserting and Replacing Content + +You can insert content into an element on the page with the +insert_html+ method: + + +page.insert_html :top, :result, '42' + + +The first parameter is the position of the new content relative to the element specified by the second parameter, a DOM id. + +Position can be one of these four values: + +*** +:before+ Inserts the response text just before the target element. +*** +:after+ The response is inserted after the target element. +*** +:top+ Inserts the text into the target element, before it's original content. +*** +:bottom+ The counterpart of +:top+: the response is inserted after the target element's original content. + +The third parameter can either be a string, or a hash of options to be passed to ActionView::Base#render - for example: + + +page.insert_html :top, :result, :partial => "the_answer" + + +You can replace the contents (innerHTML) of an element with the +replace_html+ method. The only difference is that since it's clear where should the new content go, there is no need for a position parameter - so +replace_html+ takes only two arguments, +the DOM id of the element you wish to modify and a string or a hash of options to be passed to ActionView::Base#render. + +h6. Delay + +You can delay the execution of a block of code with +delay+: + + +page.delay(10) { page.alert('Hey! Just waited 10 seconds') } + + ++delay+ takes one parameter (time to wait in seconds) and a block which will be executed after the specified time has passed - whatever else follows a +page.delay+ line is executed immediately, the delay affects only the code in the block. + +h6. Reloading and Redirecting + +You can reload the page with the +reload+ method: + + +page.reload + + +When using AJAX, you can't rely on the standard +redirect_to+ controller method - you have to use the +page+'s instance method, also called +redirect_to+: + + +page.redirect_to some_url + + +h6. Generating Arbitrary JavaScript + +Sometimes even the full power of RJS is not enough to accomplish everything, but you still don't want to drop to pure JavaScript. A nice golden mean is offered by the combination of +<<+, +assign+ and +call+ methods: + + + page << "alert('1+1 equals 3')" + + +So +<<+ is used to execute an arbitrary JavaScript statement, passed as string to the method. The above code is equivalent to: + + + page.assign :result, 3 + page.call :alert, '1+1 equals ' + result + + ++assign+ simply assigns a value to a variable. +call+ is similar to +<<+ with a slightly different syntax: the first parameter is the name of the function to call, followed by the list of parameters passed to the function. + +h6. Class Proxies + +h5. Element Proxies + +h5. Collection Proxies + +h5. RJS Helpers + + + +h3. I Want my Yellow Thingy: Quick overview of Script.aculo.us + +h4. Introduction + +h4. Visual Effects + +h4. Drag and Drop + + + +h3. Testing Javascript + +Javascript testing reminds me the definition of the world 'classic' by Mark Twain: "A classic is something that everybody wants to have read and nobody wants to read." It's similar with Javascript testing: everyone would like to have it, yet it's not done by too much developers as it is tedious, complicated, there is a proliferation of tools and no consensus/accepted best practices, but we will nevertheless take a stab at it: + +* (Fire)Watir +* Selenium +* Celerity/Culerity +* Cucumber+Webrat +* Mention stuff like screw.unit/jsSpec -* Last but not least: Javascript is your friend :) +Note to self: check out the RailsConf JS testing video \ No newline at end of file diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index ca10014ee0104..757dce1f4e48f 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -973,7 +973,7 @@ h6. +:source_type+ The +:source_type+ option specifies the source association type for a +has_one :through+ association that proceeds through a polymorphic association. -h6. :through +h6. +:through+ The +:through+ option specifies a join model through which to perform the query. +has_one :through+ associations were discussed in detail earlier in this guide. diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index 2865bc504a761..ac6f944457558 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -67,6 +67,8 @@ If you want a more complicated expiration scheme, you can use cache sweepers to Note: Page caching ignores all parameters. For example +/products?page=1+ will be written out to the filesystem as +products.html+ with no reference to the +page+ parameter. Thus, if someone requests +/products?page=2+ later, they will get the cached first page. Be careful when page caching GET parameters in the URL! +INFO: Page caching runs in an after filter. Thus, invalid requests won't generate spurious cache entries as long as you halt them. Typically, a redirection in some before filter that checks request preconditions does the job. + 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. This allows authentication and other restriction to be run while still serving the result of the output from a cached copy. @@ -98,6 +100,8 @@ You can modify the default action cache path by passing a +:cache_path+ option. 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 store. +INFO: Action caching runs in an after filter. Thus, invalid requests won't generate spurious cache entries as long as you halt them. Typically, a redirection in some before filter that checks request preconditions does the job. + h4. Fragment Caching Life would be perfect if we could get away with caching the entire contents of a page or action and serving it out to the world. Unfortunately, dynamic web applications usually build pages with a variety of components not all of which have the same caching characteristics. In order to address such a dynamically created page where different parts of the page need to be cached and expired differently Rails provides a mechanism called Fragment Caching. diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 2711337d43bd8..0e6f9811685f4 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -7,7 +7,6 @@ This guide covers the configuration and initialization features available to Rai endprologue. - h3. Locations for Initialization Code Rails offers (at least) five good spots to place initialization code: @@ -38,31 +37,87 @@ config.active_record.colorize_logging = false Rails will use that particular setting to configure Active Record. +h4. Rails General Configuration + +* +config.routes_configuration_file+ overrides the default path for the routes configuration file. This defaults to +config/routes.rb+. + +* +config.cache_classes+ controls whether or not application classes should be reloaded on each request. + +* +config.cache_store+ configures which cache store to use for Rails caching. Options include +:memory_store+, +:file_store+, +:mem_cache_store+ or the name of your own custom class. + +* +config.controller_paths+ accepts an array of paths that will be searched for controllers. Defaults to +app/controllers+. + +* +config.database_configuration_file+ overrides the default path for the database configuration file. Default to +config/database.yml+. + +* +config.dependency_loading+ enables or disables dependency loading during the request cycle. Setting dependency_loading to _true_ will allow new classes to be loaded during a request and setting it to _false_ will disable this behavior. + +* +config.eager_load_paths+ accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. All elements of this array must also be in +load_paths+. + +* +config.frameworks+ accepts an array of rails framework components that should be loaded. (Defaults to +:active_record+, +:action_controller+, +:action_view+, +:action_mailer+, and +:active_resource+). + +* +config.load_once_paths+ accepts an array of paths from which Rails will automatically load from only once. All elements of this array must also be in +load_paths+. + +* +config.load_paths+ accepts an array of additional paths to prepend to the load path. By default, all app, lib, vendor and mock paths are included in this list. + +* +config.log_level+ defines the verbosity of the Rails logger. In production mode, this defaults to +:info+. In development mode, it defaults to +:debug+. + +* +config.log_path+ overrides the path to the log file to use. Defaults to +log/#{environment}.log+ (e.g. log/development.log or log/production.log). + +* +config.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Controller. Set to nil to disable logging. + +* +config.metals+ accepts an array used as the metals to load. If this is set to nil, all metals will be loaded in alphabetical order. If this is set to [], no metals will be loaded. Otherwise metals will be loaded in the order specified + +* +config.plugin_loader+ overrides the class that handles loading each plugin. Defaults to +Rails::Plugin::Loader+. + +* +config.plugin_locators+ overrides the class that handle finding the desired plugins that you‘d like to load for your application. By default it is the +Rails::Plugin::FileSystemLocator+. + +* +config.plugin_paths+ overrides the path to the root of the plugins directory. Defaults to +vendor/plugins+. + +* +config.plugins+ accepts the list of plugins to load. If this is set to nil, all plugins will be loaded. If this is set to [], no plugins will be loaded. Otherwise, plugins will be loaded in the order specified. + +* +config.preload_frameworks+ enables or disables preloading all frameworks at startup. + +* +config.reload_plugins+ enables or disables plugin reloading. + +* +config.root_path+ configures the root path of the application. + +* +config.time_zone+ sets the default time zone for the application and enables time zone awareness for Active Record. + +* +config.view_path+ sets the path of the root of an application's views. Defaults to +app/views+. + +* +config.whiny_nils+ enables or disabled warnings when an methods of nil are invoked. Defaults to _false_. + +h4. Configuring i18n + +* +config.i18n.default_locale+ sets the default locale of an application used for i18n. Defaults to +:en+. + +* +config.i18n.load_path+ sets the path Rails uses to look for locale files. Defaults to +config/locales/*.{yml,rb}+ + h4. Configuring Active Record -ActiveRecord::Base includes a variety of configuration options: +config.active_record includes a variety of configuration options: -* +logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8.x Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling +logger+ on either an ActiveRecord model class or an ActiveRecord model instance. Set to nil to disable logging. +* +config.active_record.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8.x Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling +logger+ on either an Active Record model class or an Active Record model instance. Set to nil to disable logging. -* +primary_key_prefix_type+ lets you adjust the naming for primary key columns. By default, Rails assumes that primary key columns are named +id+ (and this configuration option doesn't need to be set.) There are two other choices: +* +config.active_record.primary_key_prefix_type+ lets you adjust the naming for primary key columns. By default, Rails assumes that primary key columns are named +id+ (and this configuration option doesn't need to be set.) There are two other choices: ** +:table_name+ would make the primary key for the Customer class +customerid+ ** +:table_name_with_underscore+ would make the primary key for the Customer class +customer_id+ -* +table_name_prefix+ lets you set a global string to be prepended to table names. If you set this to +northwest_+, then the Customer class will look for +northwest_customers+ as its table. The default is an empty string. +* +config.active_record.table_name_prefix+ lets you set a global string to be prepended to table names. If you set this to +northwest_+, then the Customer class will look for +northwest_customers+ as its table. The default is an empty string. -* +table_name_suffix+ lets you set a global string to be appended to table names. If you set this to +_northwest+, then the Customer class will look for +customers_northwest+ as its table. The default is an empty string. +* +config.active_record.table_name_suffix+ lets you set a global string to be appended to table names. If you set this to +_northwest+, then the Customer class will look for +customers_northwest+ as its table. The default is an empty string. -* +pluralize_table_names+ specifies whether Rails will look for singular or plural table names in the database. If set to +true+ (the default), then the Customer class will use the +customers+ table. If set to +false+, then the Customers class will use the +customer+ table. +* +config.active_record.pluralize_table_names+ specifies whether Rails will look for singular or plural table names in the database. If set to +true+ (the default), then the Customer class will use the +customers+ table. If set to +false+, then the Customers class will use the +customer+ table. -* +colorize_logging+ (true by default) specifies whether or not to use ANSI color codes when logging information from ActiveRecord. +* +config.active_record.colorize_logging+ (true by default) specifies whether or not to use ANSI color codes when logging information from ActiveRecord. -* +default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:local+. +* +config.active_record.default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:local+. -* +schema_format+ controls the format for dumping the database schema to a file. The options are +:ruby+ (the default) for a database-independent version that depends on migrations, or +:sql+ for a set of (potentially database-dependent) SQL statements. +* +config.active_record.schema_format+ controls the format for dumping the database schema to a file. The options are +:ruby+ (the default) for a database-independent version that depends on migrations, or +:sql+ for a set of (potentially database-dependent) SQL statements. -* +timestamped_migrations+ controls whether migrations are numbered with serial integers or with timestamps. The default is +true+, to use timestamps, which are preferred if there are multiple developers working on the same application. +* +config.active_record.timestamped_migrations+ controls whether migrations are numbered with serial integers or with timestamps. The default is +true+, to use timestamps, which are preferred if there are multiple developers working on the same application. -* +lock_optimistically+ controls whether ActiveRecord will use optimistic locking. By default this is +true+. +* +config.active_record.lock_optimistically+ controls whether ActiveRecord will use optimistic locking. By default this is +true+. The MySQL adapter adds one additional configuration option: @@ -70,79 +125,81 @@ The MySQL adapter adds one additional configuration option: The schema dumper adds one additional configuration option: -* +ActiveRecord::SchemaDumper.ignore_tables+ accepts an array of tables that should _not_ be included in any generated schema file. This setting is ignored unless +ActiveRecord::Base.schema_format == :ruby+. +* +ActiveRecord::SchemaDumper.ignore_tables+ accepts an array of tables that should _not_ be included in any generated schema file. This setting is ignored unless +config.active_record.schema_format == :ruby+. h4. Configuring Action Controller -ActionController::Base includes a number of configuration settings: +config.action_controller includes a number of configuration settings: -* +asset_host+ provides a string that is prepended to all of the URL-generating helpers in +AssetHelper+. This is designed to allow moving all javascript, CSS, and image files to a separate asset host. +* +config.action_controller.asset_host+ provides a string that is prepended to all of the URL-generating helpers in +AssetHelper+. This is designed to allow moving all javascript, CSS, and image files to a separate asset host. -* +consider_all_requests_local+ is generally set to +true+ during development and +false+ during production; if it is set to +true+, then any error will cause detailed debugging information to be dumped in the HTTP response. For finer-grained control, set this to +false+ and implement +local_request?+ to specify which requests should provide debugging information on errors. +* +config.action_controller.consider_all_requests_local+ is generally set to +true+ during development and +false+ during production; if it is set to +true+, then any error will cause detailed debugging information to be dumped in the HTTP response. For finer-grained control, set this to +false+ and implement +local_request?+ to specify which requests should provide debugging information on errors. -* +allow_concurrency+ should be set to +true+ to allow concurrent (threadsafe) action processing. Set to +false+ by default. You probably don't want to call this one directly, though, because a series of other adjustments need to be made for threadsafe mode to work properly. Instead, you should simply call +config.threadsafe!+ inside your +production.rb+ file, which makes all the necessary adjustments. +* +config.action_controller.allow_concurrency+ should be set to +true+ to allow concurrent (threadsafe) action processing. Set to +false+ by default. You probably don't want to call this one directly, though, because a series of other adjustments need to be made for threadsafe mode to work properly. Instead, you should simply call +config.threadsafe!+ inside your +production.rb+ file, which makes all the necessary adjustments. WARNING: Threadsafe operation in incompatible with the normal workings of development mode Rails. In particular, automatic dependency loading and class reloading are automatically disabled when you call +config.threadsafe!+. -* +param_parsers+ provides an array of handlers that can extract information from incoming HTTP requests and add it to the +params+ hash. By default, parsers for multipart forms, URL-encoded forms, XML, and JSON are active. +* +config.action_controller.param_parsers+ provides an array of handlers that can extract information from incoming HTTP requests and add it to the +params+ hash. By default, parsers for multipart forms, URL-encoded forms, XML, and JSON are active. + +* +config.action_controller.default_charset+ specifies the default character set for all renders. The default is "utf-8". -* +default_charset+ specifies the default character set for all renders. The default is "utf-8". +* +config.action_controller.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Controller. Set to nil to disable logging. -* +logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Controller. Set to nil to disable logging. +* +config.action_controller.resource_action_separator+ gives the token to be used between resources and actions when building or interpreting RESTful URLs. By default, this is "/". -* +resource_action_separator+ gives the token to be used between resources and actions when building or interpreting RESTful URLs. By default, this is "/". +* +config.action_controller.resource_path_names+ is a hash of default names for several RESTful actions. By default, the new action is named +new+ and the edit action is named +edit+. -* +resource_path_names+ is a hash of default names for several RESTful actions. By default, the new action is named +new+ and the edit action is named +edit+. +* +config.action_controller.request_forgery_protection_token+ sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ sets it to +:authenticity_token+ by default. -* +request_forgery_protection_token+ sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ sets it to +:authenticity_token+ by default. +* +config.action_controller.optimise_named_routes+ turns on some optimizations in generating the routing table. It is set to +true+ by default. -* +optimise_named_routes+ turns on some optimizations in generating the routing table. It is set to +true+ by default. +* +config.action_controller.use_accept_header+ sets the rules for determining the response format. If this is set to +true+ (the default) then +respond_to+ and +Request#format+ will take the Accept header into account. If it is set to false then the request format will be determined solely by examining +params[:format]+. If there is no +format+ parameter, then the response format will be either HTML or Javascript depending on whether the request is an AJAX request. -* +use_accept_header+ sets the rules for determining the response format. If this is set to +true+ (the default) then +respond_to+ and +Request#format+ will take the Accept header into account. If it is set to false then the request format will be determined solely by examining +params[:format]+. If there is no +format+ parameter, then the response format will be either HTML or Javascript depending on whether the request is an AJAX request. +* +config.action_controller.allow_forgery_protection+ enables or disables CSRF protection. By default this is +false+ in test mode and +true+ in all other modes. -* +allow_forgery_protection+ enables or disables CSRF protection. By default this is +false+ in test mode and +true+ in all other modes. +* +config.action_controller.relative_url_root+ can be used to tell Rails that you are deploying to a subdirectory. The default is +ENV['RAILS_RELATIVE_URL_ROOT']+. -* +relative_url_root+ can be used to tell Rails that you are deploying to a subdirectory. The default is +ENV['RAILS_RELATIVE_URL_ROOT']+. +* +config.action_controller.session_store+ sets the name of the store for session data. The default is +:cookie_store+; other valid options include +:active_record_store+, +:mem_cache_store+ or the name of your own custom class. The caching code adds two additional settings: -* +ActionController::Caching::Pages.page_cache_directory+ sets the directory where Rails will create cached pages for your web server. The default is +Rails.public_path+ (which is usually set to +RAILS_ROOT + "/public"+). +* +ActionController::Base.page_cache_directory+ sets the directory where Rails will create cached pages for your web server. The default is +Rails.public_path+ (which is usually set to +RAILS_ROOT + "/public"+). -* +ActionController::Caching::Pages.page_cache_extension+ sets the extension to be used when generating pages for the cache (this is ignored if the incoming request already has an extension). The default is +.html+. +* +ActionController::Base.page_cache_extension+ sets the extension to be used when generating pages for the cache (this is ignored if the incoming request already has an extension). The default is +.html+. -The dispatcher includes one setting: +The Active Record session store can also be configured: -* +ActionController::Dispatcher.error_file_path+ gives the path where Rails will look for error files such as +404.html+. The default is +Rails.public_path+. +* +ActiveRecord::SessionStore::Session.table_name+ sets the name of the table uses to store sessions. Defaults to +sessions+. -The Active Record session store can also be configured: +* +ActiveRecord::SessionStore::Session.primary_key+ sets the name of the ID column uses in the sessions table. Defaults to +session_id+. -* +CGI::Session::ActiveRecordStore::Session.data_column_name+ sets the name of the column to use to store session data. By default it is 'data' +* +ActiveRecord::SessionStore::Session.data_column_name+ sets the name of the column which stores marshaled session data. Defaults to +data+. h4. Configuring Action View There are only a few configuration options for Action View, starting with four on +ActionView::Base+: -* +debug_rjs+ specifies whether RJS responses should be wrapped in a try/catch block that alert()s the caught exception (and then re-raises it). The default is +false+. +* +config.action_view.debug_rjs+ specifies whether RJS responses should be wrapped in a try/catch block that alert()s the caught exception (and then re-raises it). The default is +false+. -* +warn_cache_misses+ tells Rails to display a warning whenever an action results in a cache miss on your view paths. The default is +false+. +* +config.action_view.warn_cache_misses+ tells Rails to display a warning whenever an action results in a cache miss on your view paths. The default is +false+. -* +field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is Proc.new{ |html_tag, instance| "
    #{html_tag}
    " }
    +* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is Proc.new{ |html_tag, instance| "
    #{html_tag}
    " }
    -* +default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+. +* +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+. -The ERB template handler supplies one additional option: +* +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Mailer. Set to nil to disable logging. -* +ActionView::TemplateHandlers::ERB.erb_trim_mode+ gives the trim mode to be used by ERB. It defaults to +'-'+. See the "ERB documentation":http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/ for more information. +* +config.action_view.erb_trim_mode+ gives the trim mode to be used by ERB. It defaults to +'-'+. See the "ERB documentation":http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/ for more information. h4. Configuring Action Mailer -There are a number of settings available on +ActionMailer::Base+: +There are a number of settings available on +config.action_mailer+: -* +template_root+ gives the root folder for Action Mailer templates. +* +config.action_mailer.template_root+ gives the root folder for Action Mailer templates. -* +logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Mailer. Set to nil to disable logging. +* +config.action_mailer.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Mailer. Set to nil to disable logging. -* +smtp_settings+ allows detailed configuration for the +:smtp+ delivery method. It accepts a hash of options, which can include any of these options: +* +config.action_mailer.smtp_settings+ allows detailed configuration for the +:smtp+ delivery method. It accepts a hash of options, which can include any of these options: ** +:address+ - Allows you to use a remote mail server. Just change it from its default "localhost" setting. ** +:port+ - On the off chance that your mail server doesn't run on port 25, you can change it. ** +:domain+ - If you need to specify a HELO domain, you can do it here. @@ -150,48 +207,46 @@ There are a number of settings available on +ActionMailer::Base+: ** +:password+ - If your mail server requires authentication, set the password in this setting. ** +:authentication+ - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of +:plain+, +:login+, +:cram_md5+. -* +sendmail_settings+ allows detailed configuration for the +sendmail+ delivery method. It accepts a hash of options, which can include any of these options: +* +config.action_mailer.sendmail_settings+ allows detailed configuration for the +sendmail+ delivery method. It accepts a hash of options, which can include any of these options: ** +:location+ - The location of the sendmail executable. Defaults to +/usr/sbin/sendmail+. ** +:arguments+ - The command line arguments. Defaults to +-i -t+. -* +raise_delivery_errors+ specifies whether to raise an error if email delivery cannot be completed. It defaults to +true+. +* +config.action_mailer.raise_delivery_errors+ specifies whether to raise an error if email delivery cannot be completed. It defaults to +true+. -* +delivery_method+ defines the delivery method. The allowed values are +:smtp+ (default), +:sendmail+, and +:test+. +* +config.action_mailer.delivery_method+ defines the delivery method. The allowed values are +:smtp+ (default), +:sendmail+, and +:test+. -* +perform_deliveries+ specifies whether mail will actually be delivered. By default this is +true+; it can be convenient to set it to +false+ for testing. +* +config.action_mailer.perform_deliveries+ specifies whether mail will actually be delivered. By default this is +true+; it can be convenient to set it to +false+ for testing. -* +default_charset+ tells Action Mailer which character set to use for the body and for encoding the subject. It defaults to +utf-8+. +* +config.action_mailer.default_charset+ tells Action Mailer which character set to use for the body and for encoding the subject. It defaults to +utf-8+. -* +default_content_type+ specifies the default content type used for the main part of the message. It defaults to "text/plain" +* +config.action_mailer.default_content_type+ specifies the default content type used for the main part of the message. It defaults to "text/plain" -* +default_mime_version+ is the default MIME version for the message. It defaults to +1.0+. +* +config.action_mailer.default_mime_version+ is the default MIME version for the message. It defaults to +1.0+. -* +default_implicit_parts_order+ - When a message is built implicitly (i.e. multiple parts are assembled from templates +* +config.action_mailer.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. h4. Configuring Active Resource -There is a single configuration setting available on +ActiveResource::Base+: +There is a single configuration setting available on +config.active_resource+: -logger accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Active Resource. Set to nil to disable logging. +* +config.active_resource.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Active Resource. Set to nil to disable logging. h4. Configuring Active Support There are a few configuration options available in Active Support: +* +config.active_support.escape_html_entities_in_json+ enables or disables the escaping of HTML entities in JSON serialization. Defaults to _true_. + +* +config.active_support.use_standard_json_time_format+ enables or disables serializing dates to ISO 8601 format. Defaults to _false_. + * +ActiveSupport::BufferedLogger.silencer+ is set to +false+ to disable the ability to silence logging in a block. The default is +true+. * +ActiveSupport::Cache::Store.logger+ specifies the logger to use within cache store operations. * +ActiveSupport::Logger.silencer+ is set to +false+ to disable the ability to silence logging in a block. The default is +true+. -h4. Configuring Active Model - -Active Model currently has a single configuration setting: - -* +ActiveModel::Errors.default_error_messages+ is an array containing all of the validation error messages. - h3. Using Initializers After it loads the framework plus any gems and plugins in your application, Rails turns to loading initializers. An initializer is any file of ruby code stored under +config/initializers+ in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks and plugins are loaded. @@ -230,5 +285,6 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/28 +* August 13, 2009: Updated with config syntax and added general configuration options by "John Pignata" * January 3, 2009: First reasonably complete draft by "Mike Gunderloy":credits.html#mgunderloy * November 5, 2008: Rough outline by "Mike Gunderloy":credits.html#mgunderloy diff --git a/railties/guides/source/contributing_to_rails.textile b/railties/guides/source/contributing_to_rails.textile index a5912643c064d..805fcf1a3230c 100644 --- a/railties/guides/source/contributing_to_rails.textile +++ b/railties/guides/source/contributing_to_rails.textile @@ -14,7 +14,7 @@ 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. +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. You'll need to create a (free) Lighthouse account in order to comment on issues or to upload tests or patches. 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. @@ -32,6 +32,10 @@ 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. +WARNING: Just to emphasize the point, _please do not report security vulnerabilities on public Lighthouse tickets_. This will only expose your fellow Rails developers to needless risks. + +You should receive an acknowledgement and detailed response to any reported security issue within 48 hours. If you don't think you're getting adequate response from the security alias, refer to the "Rails security policy page":http://rubyonrails.org/security for direct emails for the current Rails security coordinators. + 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. @@ -47,6 +51,7 @@ Rails uses git for source code control. You won’t be able to do anything witho * "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. +* "Pro Git":http://progit.org/book/ is an entire book about git with a Creative Commons license. h4. Get the Rails Source Code @@ -57,9 +62,25 @@ git clone git://github.com/rails/rails.git cd rails +h4. Pick a Branch + +Currently, there is active work being done on both the 2-3-stable branch of Rails and on the master branch (which will become Rails 3.0). If you want to work with the master branch, you're all set. To work with 2.3, you'll need to set up and switch to your own local tracking branch: + + +git branch --track 2-3-stable origin/2-3-stable +git checkout 2-3-stable + + +TIP: You may want to "put your git branch name in your shell prompt":http://github.com/guides/put-your-git-branch-name-in-your-shell-prompt to make it easier to remember which version of the code you're working with. + 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: +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. Rails needs the +mocha+ gem for running some tests, so install it with: + +gem install mocha + + +For the tests that touch the database, this means creating the databases. If you're using MySQL: mysql> create database activerecord_unittest; @@ -79,15 +100,15 @@ rake test_sqlite3 rake test_sqlite3 TEST=test/cases/validations_test.rb -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. +You can replace +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. +NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, 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: +As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the "open tickets":https://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 @@ -110,7 +131,7 @@ git checkout -b testing_branch Then you can apply their patch: -git am < their-patch-file.diff +git apply their-patch-file.diff After applying a patch, test it out! Here are some things to think about: @@ -132,7 +153,7 @@ 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. +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 @@ -188,6 +209,8 @@ 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. +You might also want to check out the "RailsBridge BugMash":http://wiki.railsbridge.org/projects/railsbridge/wiki/BugMash as a way to get involved in a group effort to improve Rails. This can help you get started and help check your code when you're writing your first patches. + h4. Commit Your Changes When you're happy with the code on your computer, you need to commit the changes to git: @@ -243,6 +266,7 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/64 +* August 1, 2009: Updates/amplifications by "Mike Gunderloy":credits.html#mgunderloy * 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.textile.erb similarity index 100% rename from railties/guides/source/credits.erb.textile rename to railties/guides/source/credits.textile.erb diff --git a/railties/guides/source/debugging_rails_applications.textile b/railties/guides/source/debugging_rails_applications.textile index 9c0f22724eee6..94411a560edb2 100644 --- a/railties/guides/source/debugging_rails_applications.textile +++ b/railties/guides/source/debugging_rails_applications.textile @@ -330,7 +330,7 @@ h4. The Context When you start debugging your application, you will be placed in different contexts as you go through the different parts of the stack. -ruby-debug creates a content when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped. +ruby-debug creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped. At any time you can call the +backtrace+ command (or its alias +where+) to print the backtrace of the application. This can be very helpful to know how you got where you are. If you ever wondered about how you got somewhere in your code, then +backtrace+ will supply the answer. diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index ac0769e219e81..0a76a0f06f3d8 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -754,9 +754,9 @@ Many apps grow beyond simple forms editing a single object. For example when cre * Ryan Bates' series of Railscasts on "complex forms":http://railscasts.com/episodes/75 * Handle Multiple Models in One Form from "Advanced Rails Recipes":http://media.pragprog.com/titles/fr_arr/multiple_models_one_form.pdf -* Eloy Duran's "nested_params":http://github.com/alloy/complex-form-examples/tree/alloy-nested_params plugin +* Eloy Duran's "complex-forms-examples":http://github.com/alloy/complex-form-examples/ application * Lance Ivy's "nested_assignment":http://github.com/cainlevy/nested_assignment/tree/master plugin and "sample application":http://github.com/cainlevy/complex-form-examples/tree/cainlevy -* James Golick's "attribute_fu":http://github.com/giraffesoft/attribute_fu/tree plugin +* James Golick's "attribute_fu":http://github.com/jamesgolick/attribute_fu plugin h3. Changelog diff --git a/railties/guides/source/generators.textile b/railties/guides/source/generators.textile new file mode 100644 index 0000000000000..fcd91f8956d40 --- /dev/null +++ b/railties/guides/source/generators.textile @@ -0,0 +1,378 @@ +h2. Creating and customizing Rails Generators + +Rails generators are an essential tool if you plan to improve your workflow and in this guide you will learn how to create and customize already existing generators. + +In this guide you will: + +* Learn how to see which generators are available in your application; +* Create a generator using templates; +* Learn how Rails searches for generators before invoking them; +* Customize your scaffold by creating new generators; +* Customize your scaffold by changing generators templates; +* Learn how to use fallbacks to avoid overwriting a huge set of generators; + +endprologue. + +NOTE: This guide is about Rails generators for versions >= 3.0. Rails generators from previous versions are not supported. + +h3. First contact + +When you create an application using the +rails+ command, you are in fact using a Rails generator. After that, you can get a list of all available generators by just invoking +script/generate+: + + +$ rails myapp +$ cd myapp +$ ruby script/generate + + +You will get a list of all generators that comes with Rails. If you need a detailed description, for instance about the helper generator, you can simply do: + + +$ ruby script/generate helper --help + + +h3. Creating your first generator + +Since Rails 3.0, generators are built on top of "Thor":http://github.com/wycats/thor. Thor has a powerful options parsing and a great API for manipulating files. For instance, let's build a generator that creates an initializer file named +initializer.rb+ inside +config/initializers+. + +The first step is to create a file at +RAILS_APP/lib/generators/initializer_generator.rb+ with the following content: + + +class InitializerGenerator < Rails::Generators::Base + def create_initializer_file + create_file "config/initializers/initializer.rb", "# Add initialization content here" + end +end + + +Our new generator is quite simple: it inherits from +Rails::Generators::Base+ and have one method definition. Each public method in the generator is executed when a generator is invoked. Finally, we invoke the +create_file+ method that will create a file at the given destination with the given content. If you are familiar with Rails Application Templates API, you are at home with new generators API. + +To invoke our new generator, we just need to do: + + +$ ruby script/generate initializer + + +Before we go on, let's see our brand new generator description: + + +$ ruby script/generate initializer --help + + +Rails usually is able to generate good descriptions if a generator is namespaced, as +ActiveRecord::Generators::ModelGenerator+, but not in this particular case. We can solve this problem in two ways. The first one is calling +desc+ inside our generator: + + +class InitializerGenerator < Rails::Generators::Base + desc "This generator creates an initializer file at config/initializers" + def create_initializer_file + create_file "config/initializers/initializer.rb", "# Add initialization content here" + end +end + + +Now we can see the new description by invoking +--help+ in the new generator. The second way to add a description is by creating a file named +USAGE+ in the same directory as our generator. We are going to do that in the next step. + +h3. Creating generators with generators + +A faster way to create a generator is using the generator's generator: + + +$ ruby script/generate generator initializer + create lib/generators/initializer + create lib/generators/initializer/initializer_generator.rb + create lib/generators/initializer/USAGE + create lib/generators/initializer/templates + + +And it will create a new generator as follow: + + +class InitializerGenerator < Rails::Generators::NamedBase + def self.source_root + @source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'templates')) + end +end + + +At first, we can notice that we are inheriting from +Rails::Generators::NamedBase+ instead of +Rails::Generators::Base+. This means that our generator expects as least one argument, which will be the name of the initializer. + +We can see that by invoking the description of this new generator (don't forget to delete the old generator file): + + +$ ruby script/generate initializer --help +Usage: + script/generate initializer NAME [options] + + +We can also see in our new generator that it has a class method called +source_root+. This method points to where our generator templates will be placed and by default it points to the created directory under +RAILS_APP/lib/generators/initializer/templates+. In order to understand what a generator template means, let's create a file at +RAILS_APP/lib/generators/initializer/templates/initializer.rb+ with the following content: + + +# Add initialization content here + + + +And now let's change the generator to copy this template when invoked: + + +class InitializerGenerator < Rails::Generators::NamedBase + def self.source_root + @source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'templates')) + end + + def copy_initializer_file + copy_file "initializer.rb", "config/initializers/#{file_name}.rb" + end +end + + +And let's execute our generator: + + +$ ruby script/generate initializer foo + + +We can see that now a initializer named foo was created at +config/initializers/foo.rb+ with the contents of our template. That means that copy_file copied a file in our source root to the destination path we gave. The method +file_name+ is automatically created when we inherit from +Rails::Generators::NamedBase+. + +h3. Generators lookup + +Now that we know how to create generators, we must know where Rails looks for generators before invoking them. When we invoke the initializer generator, Rails looks at the following paths in the given order: + + +RAILS_APP/lib/generators +RAILS_APP/lib/rails_generators +RAILS_APP/vendor/plugins/*/lib/generators +RAILS_APP/vendor/plugins/*/lib/rails_generators +GEMS_PATH/*/lib/generators +GEMS_PATH/*/lib/rails_generators +~/rails/generators +~/rails/rails_generators +RAILS_GEM/lib/rails/generators + + +First Rails looks for generators in your application, then in plugins and/or gems, then in your home and finally the builtin generators. One very important thing to keep in mind is that in Rails 3.0 and after it only looks for generators in gems being used in your application. So if you have rspec installed as a gem, but it's not declared in your application, Rails won't be able to invoke it. + +h3. Customizing your workflow + +Rails generators are flexible enough to let you customize your scaffold the way you want. In your +config/application.rb+ there is a section just for generators: + + +config.generators do |g| + g.orm :active_record + g.template_engine :erb + g.test_framework :test_unit, :fixture => true +end + + +Before we customize our workflow, let's first see how our scaffold looks like: + + +$ ruby script/generate scaffold User name:string + invoke active_record + create db/migrate/20091120125558_create_users.rb + create app/models/user.rb + invoke test_unit + create test/unit/user_test.rb + create test/fixtures/users.yml + route map.resources :users + invoke scaffold_controller + create app/controllers/users_controller.rb + invoke erb + create app/views/users + create app/views/users/index.html.erb + create app/views/users/edit.html.erb + create app/views/users/show.html.erb + create app/views/users/new.html.erb + create app/views/users/_form.html.erb + create app/views/layouts/users.html.erb + invoke test_unit + create test/functional/users_controller_test.rb + invoke helper + create app/helpers/users_helper.rb + invoke test_unit + create test/unit/helpers/users_helper_test.rb + invoke stylesheets + create public/stylesheets/scaffold.css + + +Looking at this output, is easy to understand how generators work on Rails 3.0 and above. The scaffold generator actually doesn't generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication. + +Our first customization on the workflow will be to stop generating stylesheets and test fixtures on scaffold. We can achieve that by changing our application to the following: + + +config.generators do |g| + g.orm :active_record + g.template_engine :erb + g.test_framework :test_unit, :fixture => false + g.stylesheets false +end + + +If we generate another resource on scaffold, we can notice that neither stylesheets nor fixtures are created anymore. If you want to customize it further, for example to use +Datamapper+ and +Rspec+ instead of +ActiveRecord+ and +TestUnit+, is just a matter of adding their gems to your application and configuring your generators. + +To show that, we are going to create a new helper generator that simply adds some instance variable readers. First, we create a generator: + + +$ ruby script/generate generator my_helper + + +After that, we can delete both templates directory and the +source_root+ class method from our new generators, because we are not going to need them. So our new generator looks like the following: + + +class MyHelperGenerator < Rails::Generators::NamedBase + def create_helper_file + create_file "app/helpers/#{file_name}_helper.rb", <<-FILE +module #{class_name}Helper + attr_reader :#{plural_name}, :#{plural_name.singularize} +end + FILE + end +end + + +We can try out our new generator by creating a helper for users: + + +$ ruby script/generate my_helper users + + +And it will generate the following helper file in app/helpers: + + +module UsersHelper + attr_reader :users, :user +end + + +Which is what we expected. We can now tell scaffold to use our new helper generator by configuring +config/application.rb+ once again: + + +config.generators do |g| + g.orm :active_record + g.template_engine :erb + g.test_framework :test_unit, :fixture => false + g.stylesheets false + g.helper :my_helper +end + + +And see it in action when invoking generator once again: + + +$ ruby script/generate scaffold Post body:text + [...] + invoke my_helper + create app/helpers/posts_helper.rb + + +We can notice on the output that our new helper was invoked instead of the Rails default. However one thing is missing, which is tests for our new generator and to do that, we are going to reuse old helpers test generators. + +Since Rails 3.0, this is easy to do due to the hooks concept. Our new helper does not need to be focused in one specific test framework, it can simply provide a hook and a test framework just need to implement this hook in order to be compatible. + +To do that, we can change your generator to the following: + + +class MyHelperGenerator < Rails::Generators::NamedBase + def create_helper_file + create_file "app/helpers/#{file_name}_helper.rb", <<-FILE +module #{class_name}Helper + attr_reader :#{plural_name}, :#{plural_name.singularize} +end + FILE + end + + hook_for :test_framework +end + + +Now, when the helper generator is invoked and let's say test unit is configured as test framework, it will try to invoke both +MyHelper::Generators::TestUnitGenerator+ and +TestUnit::Generators::MyHelperGenerator+. Since none of those are defined, we can tell our generator to invoke +TestUnit::Generators::HelperGenerator+ instead, which is defined since it's a Rails hook. To do that, we just need to add: + + + # Search for :helper instead of :my_helper + hook_for :test_framework, :as => :helper + + +And now you can re-run scaffold for another resource and see it generating tests as well! + +h3. Customizing your workflow by changing generators templates + +In the step above, we simply wanted to add a line to the generated helper, without adding any extra functionality. There is a simpler way to do that, and it's by replacing the templates of already existing generators. + +In Rails 3.0 and above, generators does not look only in the source root for templates, they also search for templates in other paths. And one of them is inside +RAILS_APP/lib/templates+. Since we want to customize +Rails::Generators::HelperGenerator+, we can do that by simple making a template copy inside +RAILS_APP/lib/templates/rails/helper+ with the name +helper.rb+. So let's create such file with the following content: + + +module <%= class_name %>Helper + attr_reader :<%= plural_name %>, <%= plural_name.singularize %> +end + + +So now we can revert the changes in +config/application.rb+: + + +config.generators do |g| + g.orm :active_record + g.template_engine :erb + g.test_framework :test_unit, :fixture => false + g.stylesheets false +end + + +If you generate another resource, you can see that we got exactly the same result! This is useful if you want to customize your scaffold templates and/or layout by just creating +edit.html.erb+, +index.html.erb+ and so on inside +RAILS_APP/lib/templates/erb/scaffold+. + +h3. Adding generators fallbacks + +One last feature about generators which is quite useful for plugin generators is fallbacks. For example, imagine that you want to add a feature on top of TestUnit test framework, like "shoulda":http://github.com/thoughtbot/shoulda does. Since TestUnit already implements all generators required by Rails and shoulda just want to overwrite part of it, there is no need for shoulda to reimplement some generators again, they can simply tell Rails to use a +TestUnit+ generator if none was found under +Shoulda+ namespace. + +We can easily simulate this behavior by changing our +config/application.rb+ once again: + + +config.generators do |g| + g.orm :active_record + g.template_engine :erb + g.test_framework :shoulda, :fixture => false + g.stylesheets false +end + + +And at the end of the same file: + + +require 'rails/generators' +Rails::Generators.fallbacks[:shoulda] = :test_unit + + +Now, if create a Comment scaffold, you will see that shoulda generators are being invoked, and at the end, they are just falling back to test unit generators: + + +$ ruby script/generate scaffold Comment body:text + invoke active_record + create db/migrate/20091120151323_create_comments.rb + create app/models/comment.rb + invoke shoulda + create test/unit/comment_test.rb + create test/fixtures/comments.yml + route map.resources :comments + invoke scaffold_controller + create app/controllers/comments_controller.rb + invoke erb + create app/views/comments + create app/views/comments/index.html.erb + create app/views/comments/edit.html.erb + create app/views/comments/show.html.erb + create app/views/comments/new.html.erb + create app/views/comments/_form.html.erb + create app/views/layouts/comments.html.erb + invoke shoulda + create test/functional/comments_controller_test.rb + invoke my_helper + create app/helpers/comments_helper.rb + invoke shoulda + create test/unit/helpers/comments_helper_test.rb + + +Such tool allows your generators to have single responsibility, increasing the code reuse and reducing the amount of code duplication. + +h3. Changelog + +"Lighthouse Ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/102 + +* November 20, 2009: First release version by José Valim diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 5c05648f12cf4..bd6dbda199bb2 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -22,8 +22,8 @@ This guide is designed for beginners who want to get started with a Rails applic It is highly recommended that you *familiarize yourself with Ruby before diving into Rails*. You will find it much easier to follow what's going on with a Rails application if you understand basic Ruby syntax. Rails isn't going to magically revolutionize the way you write web applications if you have no experience with the language it uses. There are some good free resources on the internet for learning Ruby, including: * "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/ +* "Programming Ruby":http://www.ruby-doc.org/docs/ProgrammingRuby/ +* "Why's (Poignant) Guide to Ruby":http://mislav.uniqpath.com/poignant-guide/ h3. What is Rails? @@ -51,7 +51,7 @@ A model represents the information (data) of the application and the rules to ma h5. Views -Views represent the user interface of your application. In Rails, views are often HTML files with embedded Ruby code that performs tasks related solely to the presentation of the data. Views handle the job of providing data to the web browser or other tool that is used to make requests from your application. +Views represent the user interface of your application. In Rails, views are often HTML files with embedded Ruby code that perform tasks related solely to the presentation of the data. Views handle the job of providing data to the web browser or other tool that is used to make requests from your application. h5. Controllers @@ -381,7 +381,7 @@ $ rake db:migrate Remember, you can't run migrations before running +rake db:create+ to create your database, as we covered earlier. -NOTE: Because you're working in the development environment by default, this command will apply to the database defined in the +development+ section of your +config/database.yml+ file. +NOTE. Because you're working in the development environment by default, this command will apply to the database defined in the +development+ section of your +config/database.yml+ file. h4. Adding a Link @@ -1052,7 +1052,7 @@ This creates a new +Comment+ object _and_ sets up the +post_id+ field to have th h4. Building Views -Because you skipped scaffolding, you'll need to build views for comments "by hand." Invoking +script/generate controller+ will give you skeleton views, but they'll be devoid of actual content. Here's a first pass at fleshing out the comment views. +Because you skipped scaffolding, you'll need to build views for comments "by hand". Invoking +script/generate controller+ will give you skeleton views, but they'll be devoid of actual content. Here's a first pass at fleshing out the comment views. The +views/comments/index.html.erb+ view: diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile index b588656821a9e..ec69f770ee7ee 100644 --- a/railties/guides/source/i18n.textile +++ b/railties/guides/source/i18n.textile @@ -243,7 +243,7 @@ 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 explicitly, 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. +IMPORTANT: This solution has currently one rather big *downside*. Due to the _default_url_options_ implementation, you have to pass the +:id+ option explicitly, 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/wikipages/how-to-encode-the-current-locale-in-the-url in the Rails i18n Wiki. h4. Setting the Locale from the Client Supplied Information diff --git a/railties/guides/source/index.erb.textile b/railties/guides/source/index.textile.erb similarity index 100% rename from railties/guides/source/index.erb.textile rename to railties/guides/source/index.textile.erb diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index 5f49ac41f5104..771c3e2523f55 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -582,7 +582,7 @@ The Active Record way claims that intelligence belongs in your models, not in th Validations such as +validates_uniqueness_of+ are one way in which models can enforce data integrity. The +:dependent+ option on associations allows models to automatically destroy child objects when the parent is destroyed. Like anything which operates at the application level these cannot guarantee referential integrity and so some people augment them with foreign key constraints. -Although Active Record does not provide any tools for working directly with such features, the +execute+ method can be used to execute arbitrary SQL. There are also a number of plugins such as "foreign_key_migrations":http://github.com/harukizaemon/foreign_key_migrations/tree/master which add foreign key support to Active Record (including support for dumping foreign keys in +db/schema.rb+). +Although Active Record does not provide any tools for working directly with such features, the +execute+ method can be used to execute arbitrary SQL. There are also a number of plugins such as "foreign_key_migrations":http://github.com/harukizaemon/redhillonrails/tree/master/foreign_key_migrations/ which add foreign key support to Active Record (including support for dumping foreign keys in +db/schema.rb+). h3. Changelog diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile index a5d39c3d09806..06d0d493e4c9a 100644 --- a/railties/guides/source/plugins.textile +++ b/railties/guides/source/plugins.textile @@ -1406,7 +1406,7 @@ rake rdoc h3. Appendix -If you prefer to use RSpec instead of Test::Unit, you may be interested in the "RSpec Plugin Generator":http://github.com/pat-maddox/rspec-plugin-generator/tree/master. +If you prefer to use RSpec instead of Test::Unit, you may be interested in the "RSpec Plugin Generator":http://github.com/patmaddox/rspec-plugin-generator. h4. References diff --git a/railties/guides/source/rails_on_rack.textile b/railties/guides/source/rails_on_rack.textile index 1ad45f1ccaa5e..df93580e893a5 100644 --- a/railties/guides/source/rails_on_rack.textile +++ b/railties/guides/source/rails_on_rack.textile @@ -68,7 +68,7 @@ run ActionController::Dispatcher.new And start the server: -[lifo@null application]$ rackup +[lifo@null application]$ rackup config.ru To find out more about different +rackup+ options: diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 355f385d492d8..24f0578545b13 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -871,13 +871,13 @@ h5. The +assert_recognizes+ Assertion The +assert_recognizes+ assertion is the inverse of +assert_generates+. It asserts that Rails recognizes the given path and routes it to a particular spot in your application. -assert_recognizes { :controller => "photos", :action => "show", :id => "1" }, "/photos/1" +assert_recognizes({ :controller => "photos", :action => "show", :id => "1" }, "/photos/1") You can supply a +:method+ argument to specify the HTTP verb: -assert_recognizes { :controller => "photos", :action => "create" }, { :path => "photos", :method => :post } +assert_recognizes({ :controller => "photos", :action => "create" }, { :path => "photos", :method => :post }) You can also use the RESTful helpers to test recognition of a RESTful route: @@ -891,7 +891,7 @@ h5. The +assert_routing+ Assertion The +assert_routing+ assertion checks the route both ways: it tests that the path generates the options, and that the options generate the path. Thus, it combines the functions of +assert_generates+ and +assert_recognizes+. -assert_routing { :path => "photos", :method => :post }, { :controller => "photos", :action => "create" } +assert_routing({ :path => "photos", :method => :post }, { :controller => "photos", :action => "create" }) h3. Changelog diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index c26bea5519606..ecf68b56f9be2 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -149,26 +149,24 @@ 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._ -One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _(highlight)expire sessions in a database table_. Call +Session.sweep("20m")+ to expire sessions that were used longer than 20 minutes ago. +One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _(highlight)expire sessions in a database table_. Call +Session.sweep("20 minutes")+ to expire sessions that were used longer than 20 minutes ago. class Session < ActiveRecord::Base - def self.sweep(time_ago = nil) -
 time = case time_ago -
 when /^(\d+)m$/ then Time.now - $1.to_i.minute -
 when /^(\d+)h$/ then Time.now - $1.to_i.hour -
 when /^(\d+)d$/ then Time.now - $1.to_i.day -
 else Time.now - 1.hour -
 end -
 self.delete_all "updated_at < '#{time.to_s(:db)}'" -
 end -
end + def self.sweep(time = 1.hour) + time = time.split.inject { |count, unit| + count.to_i.send(unit) + } if time.is_a?(String) + + delete_all "updated_at < '#{time.ago.to_s(:db)}'" + end +end The section about session fixation introduced the problem of maintained sessions. An attacker maintaining a session every five minutes can keep the session alive forever, although you are expiring sessions. A simple solution for this would be to add a created_at column to the sessions table. Now you can delete sessions that were created a long time ago. Use this line in the sweep method above: -self.delete_all "updated_at < '#{time.to_s(:db)}' OR +delete_all "updated_at < '#{time.to_s(:db)}' OR created_at < '#{2.days.ago.to_s(:db)}'" @@ -291,7 +289,7 @@ def sanitize_filename(filename) returning filename.strip do |name| # NOTE: File.basename doesn't work right with Windows paths on Unix # get only the filename, not the whole path - name.gsub! /^.*(\\|\/)/, '' + name.sub! /\A.*(\\|\/)/, '' # Finally, replace all non alphanumeric, underscore # or periods with underscore name.gsub! /[^\w\.\-]/, '_' @@ -326,7 +324,7 @@ Simply pass a file name like “../../../etc/passwd” to download the server's basename = File.expand_path(File.join(File.dirname(__FILE__), '../../files')) filename = File.expand_path(File.join(basename, @file.public_filename)) -raise if basename =! +raise if basename != File.expand_path(File.join(File.dirname(filename), '../../../')) send_file filename, :disposition => 'inline' @@ -381,7 +379,7 @@ end Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the new() method, or assign attributes=(attributes) a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this:
    -"name":http://www.example.com/user/signup?user=ow3ned&user[admin]=1
    +"name":http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1
     
    This will set the following parameters in the controller: @@ -396,7 +394,7 @@ Note that this vulnerability is not restricted to database columns. Any setter class Person < ActiveRecord::Base - has_many :credits + has_many :children accepts_nested_attributes_for :children end @@ -471,7 +469,7 @@ 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._ -A list of user names for your web application may be misused to brute-force the corresponding passwords, because most people don't use sophisticated passwords. Most passwords are a combination of dictionary words and possibly numbers. So armed with a list of user name's and a dictionary, an automatic program may find the correct password in a matter of minutes. +A list of user names for your web application may be misused to brute-force the corresponding passwords, because most people don't use sophisticated passwords. Most passwords are a combination of dictionary words and possibly numbers. So armed with a list of user names and a dictionary, an automatic program may find the correct password in a matter of minutes. Because of this, most web applications will display a generic error message “user name or password not correct”, if one of these are not correct. If it said “the user name you entered has not been found”, an attacker could automatically compile a list of user names. @@ -536,7 +534,7 @@ 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._ -Bruce Schneier, a security technologist, "has analysed":http://www.schneier.com/blog/archives/2006/12/realworld_passw.html 34,000 real-world user names and passwords from the MySpace phishing attack mentioned earlier. It turns out that most of the passwords are quite easy to crack. The 20 most common passwords are: +Bruce Schneier, a security technologist, "has analysed":http://www.schneier.com/blog/archives/2006/12/realworld_passw.html 34,000 real-world user names and passwords from the MySpace phishing attack mentioned below. It turns out that most of the passwords are quite easy to crack. The 20 most common passwords are: password1, abc123, myspace1, password, blink182, qwerty1, ****you, 123abc, baseball1, football1, 123456, soccer, monkey1, liverpool1, princess1, jordan23, slipknot1, superman1, iloveyou1, and monkey. @@ -556,7 +554,7 @@ class File < ActiveRecord::Base end -This means, upon saving, the model will validate the file name to consist only of alphanumeric characters, dots, + and -. And the programmer added \^ and $ so that file name will contain these characters from the beginning to the end of the string. However, _(highlight)in Ruby ^ and $ matches the *line* beginning and line end_. And thus a file name like this passes the filter without problems: +This means, upon saving, the model will validate the file name to consist only of alphanumeric characters, dots, + and -. And the programmer added ^ and $ so that file name will contain these characters from the beginning to the end of the string. However, _(highlight)in Ruby ^ and $ matches the *line* beginning and line end_. And thus a file name like this passes the filter without problems: file.txt%0A @@ -572,7 +570,7 @@ 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._ -The most common parameter that a user might tamper with, is the id parameter, as in +":id":http://www.domain.com/project/1+, whereas 1 is the id. It will be available in params in the controller. There, you will most likely do something like this: +The most common parameter that a user might tamper with, is the id parameter, as in +http://www.domain.com/project/1+, whereas 1 is the id. It will be available in params in the controller. There, you will most likely do something like this: @project = Project.find(params[:id]) @@ -621,7 +619,7 @@ SQL injection attacks aim at influencing database queries by manipulating web ap Project.find(:all, :conditions => "name = '#{params[:name]}'") -This could be in a search action and the user may enter a project's name that he wants to find. If a malicious user enters ' OR 1=1', the resulting SQL query will be: +This could be in a search action and the user may enter a project's name that he wants to find. If a malicious user enters ' OR 1 --, the resulting SQL query will be: SELECT * FROM projects WHERE name = '' OR 1 --' @@ -631,7 +629,7 @@ The two dashes start a comment ignoring everything after it. So the query return 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. +Usually a web application includes access control. The user enters his login credentials, the web application 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. User.find(:first, "login = '#{params[:name]}' AND password = '#{params[:password]}'") @@ -640,7 +638,7 @@ User.find(:first, "login = '#{params[:name]}' AND password = '#{params[:password If an attacker enters ' OR '1'='1 as the name, and ' OR '2'>'1 as the password, the resulting SQL query will be: -SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1 +SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1 This will simply find the first record in the database, and grants access to this user. @@ -663,7 +661,7 @@ This will result in the following SQL query: SELECT * FROM projects WHERE (name = '') UNION - SELECT id,login AS name,password AS description,1,1,1 FROM users --') + SELECT id,login AS name,password AS description,1,1,1 FROM users --' The result won't be a list of projects (because there is no project with an empty name), but a list of user names and their password. So hopefully you encrypted the passwords in the database! The only problem for the attacker is, that the number of columns has to be the same in both queries. That's why the second query includes a list of ones (1), which will be always the value 1, in order to match the number of columns in the first query. @@ -729,7 +727,7 @@ These examples don't do any harm so far, so let's see how an attacker can steal -For an attacker, of course, this is not useful, as the victim will see his own cookie. The next example will try to load an image from the URL http://www.attacker.com/ plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review his web server's access log files to see the victims cookie. +For an attacker, of course, this is not useful, as the victim will see his own cookie. The next example will try to load an image from the URL http://www.attacker.com/ plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review his web server's access log files to see the victim's cookie. @@ -753,7 +751,7 @@ With web page defacement an attacker can do a lot of things, for example, presen 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. +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 attacker's 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. Reflected injection attacks are those where the payload is not stored to present it to the victim later on, but included in the URL. Especially search forms fail to escape the search string. The following link presented a page which stated that "George Bush appointed a 9 year old boy to be the chairperson...": @@ -828,7 +826,7 @@ MySpace blocks many tags, however it allows CSS. So the worm's author put JavaSc
    -So the payload is in the style attribute. But there are no quotes allowed in the payload, because single and double quotes have already been used. But JavaScript allows has a handy eval() function which executes any string as code. +So the payload is in the style attribute. But there are no quotes allowed in the payload, because single and double quotes have already been used. But JavaScript has a handy eval() function which executes any string as code.
    @@ -901,7 +899,7 @@ h4. Command Line Injection -- _Use user-supplied command line parameters with caution._ -If your application has to execute commands in the underlying operating system, there are several methods in Ruby: exec(command), syscall(command), system(command) and \+command+. You will have to be especially careful with these functions if the user may enter the whole command, or a part of it. This is because in most shells, you can execute another command at the end of the first one, concatenating them with a semicolon (;) or a vertical bar (|). +If your application has to execute commands in the underlying operating system, there are several methods in Ruby: exec(command), syscall(command), system(command) and `command`. You will have to be especially careful with these functions if the user may enter the whole command, or a part of it. This is because in most shells, you can execute another command at the end of the first one, concatenating them with a semicolon (;) or a vertical bar (|). A countermeasure is to _(highlight)use the +system(command, parameters)+ method which passes command line parameters safely_. diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile index 8318146ed36d5..c7b475899f0aa 100644 --- a/railties/guides/source/testing.textile +++ b/railties/guides/source/testing.textile @@ -162,7 +162,7 @@ require 'test_helper' class PostTest < ActiveSupport::TestCase # Replace this with your real tests. - def test_truth + test "the truth" do assert true end end @@ -186,7 +186,17 @@ The +PostTest+ class defines a _test case_ because it inherits from +ActiveSuppo def test_truth -Any method defined within a test case that begins with +test+ (case sensitive) is simply called a test. So, +test_password+, +test_valid_password+ and +testValidPassword+ all are legal test names and are run automatically when the test case is run. +Any method defined within a +Test::Unit+ test case that begins with +test+ (case sensitive) is simply called a test. So, +test_password+, +test_valid_password+ and +testValidPassword+ all are legal test names and are run automatically when the test case is run. + +Rails adds a +test+ method that takes a test name and a block. It generates a normal +Test::Unit+ test with method names prefixed with +test_+. + + +test "the truth" do + # ... +end + + +This makes test names more readable by replacing underscores with regular language. assert true @@ -262,7 +272,7 @@ The +.+ (dot) above indicates a passing test. When a test fails you see an +F+; To see how a test failure is reported, you can add a failing test to the +post_test.rb+ test case. -def test_should_not_save_post_without_title +test "should not save post without title" do post = Post.new assert !post.save end @@ -272,16 +282,13 @@ Let us run this newly added test.
     $ ruby unit/post_test.rb -n test_should_not_save_post_without_title
    -Loaded suite unit/post_test
    +Loaded suite -e
     Started
     F
    -Finished in 0.197094 seconds.
    +Finished in 0.102072 seconds.
     
       1) Failure:
    -test_should_not_save_post_without_title(PostTest)
    -    [unit/post_test.rb:11:in `test_should_not_save_post_without_title'
    -     /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__'
    -     /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run']:
    +test_should_not_save_post_without_title(PostTest) [/test/unit/post_test.rb:6]:
      is not true.
     
     1 tests, 1 assertions, 1 failures, 0 errors
    @@ -290,7 +297,7 @@ test_should_not_save_post_without_title(PostTest)
     In the output, +F+ denotes a failure. You can see the corresponding trace shown under +1)+ along with the name of the failing test. The next few lines contain the stack trace followed by a message which mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable every assertion provides an optional message parameter, as shown here:
     
     
    -def test_should_not_save_post_without_title
    +test "should not save post without title" do
       post = Post.new
       assert !post.save, "Saved the post without a title"
     end
    @@ -299,21 +306,10 @@ end
     Running this test shows the friendlier assertion message:
     
     
    -$ ruby unit/post_test.rb -n test_should_not_save_post_without_title
    -Loaded suite unit/post_test
    -Started
    -F
    -Finished in 0.198093 seconds.
    -
       1) Failure:
    -test_should_not_save_post_without_title(PostTest)
    -    [unit/post_test.rb:11:in `test_should_not_save_post_without_title'
    -     /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__'
    -     /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run']:
    +test_should_not_save_post_without_title(PostTest) [/test/unit/post_test.rb:6]:
     Saved the post without a title.
      is not true.
    -
    -1 tests, 1 assertions, 1 failures, 0 errors
     
    Now to get this test to pass we can add a model level validation for the _title_ field. @@ -343,7 +339,7 @@ TIP: Many Rails developers practice _Test-Driven Development_ (TDD). This is an To see how an error gets reported, here's a test containing an error: -def test_should_report_error +test "should report error" do # some_undefined_variable is not defined elsewhere in the test case some_undefined_variable assert true @@ -354,18 +350,15 @@ Now you can see even more output in the console from running the tests:
     $ ruby unit/post_test.rb -n test_should_report_error
    -Loaded suite unit/post_test
    +Loaded suite -e
     Started
     E
    -Finished in 0.195757 seconds.
    +Finished in 0.082603 seconds.
     
       1) Error:
     test_should_report_error(PostTest):
    -NameError: undefined local variable or method `some_undefined_variable' for #
    -    /opt/local/lib/ruby/gems/1.8/gems/actionpack-2.1.1/lib/action_controller/test_process.rb:467:in `method_missing'
    -    unit/post_test.rb:16:in `test_should_report_error'
    -    /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `__send__'
    -    /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.1.1/lib/active_support/testing/setup_and_teardown.rb:33:in `run'
    +NameError: undefined local variable or method `some_undefined_variable' for #
    +    /test/unit/post_test.rb:6:in `test_should_report_error'
     
     1 tests, 0 assertions, 0 failures, 1 errors
     
    @@ -446,7 +439,7 @@ Now that we have used Rails scaffold generator for our +Post+ resource, it has a Let me take you through one such test, +test_should_get_index+ from the file +posts_controller_test.rb+. -def test_should_get_index +test "should get index" do get :index assert_response :success assert_not_nil assigns(:posts) @@ -479,7 +472,7 @@ NOTE: If you try running +test_should_create_post+ test from +posts_controller_t Let us modify +test_should_create_post+ test in +posts_controller_test.rb+ so that all our test pass: -def test_should_create_post +test "should create post" do assert_difference('Post.count') do post :create, :post => { :title => 'Some title'} end @@ -535,7 +528,7 @@ h4. A Fuller Functional Test Example Here's another example that uses +flash+, +assert_redirected_to+, and +assert_difference+: -def test_should_create_post +test "should create post" do assert_difference('Post.count') do post :create, :post => { :title => 'Hi', :body => 'This is my first post.'} end @@ -625,7 +618,7 @@ class UserFlowsTest < ActionController::IntegrationTest # fixtures :your, :models # Replace this with your real tests. - def test_truth + test "the truth" do assert true end end @@ -660,7 +653,7 @@ require 'test_helper' class UserFlowsTest < ActionController::IntegrationTest fixtures :users - def test_login_and_browse_site + test "login and browse site" do # login via https https! get "/login" @@ -688,7 +681,7 @@ require 'test_helper' class UserFlowsTest < ActionController::IntegrationTest fixtures :users - def test_login_and_browse_site + test "login and browse site" do # User avs logs in avs = login(:avs) @@ -771,12 +764,12 @@ class PostsControllerTest < ActionController::TestCase @post = nil end - def test_should_show_post + test "should show post" do get :show, :id => @post.id assert_response :success end - def test_should_destroy_post + test "should destroy post" do assert_difference('Post.count', -1) do delete :destroy, :id => @post.id end @@ -809,17 +802,17 @@ class PostsControllerTest < ActionController::TestCase @post = nil end - def test_should_show_post + test "should show post" do get :show, :id => @post.id assert_response :success end - def test_should_update_post + test "should update post" do put :update, :id => @post.id, :post => { } assert_redirected_to post_path(assigns(:post)) end - def test_should_destroy_post + test "should destroy post" do assert_difference('Post.count', -1) do delete :destroy, :id => @post.id end @@ -841,7 +834,7 @@ h3. Testing Routes Like everything else in your Rails application, it is recommended that you test your routes. An example test for a route in the default +show+ action of +Posts+ controller above should look like: -def test_should_route_to_post +test "should route to post" do assert_routing '/posts/1', { :controller => "posts", :action => "show", :id => "1" } end @@ -883,7 +876,7 @@ require 'test_helper' class UserMailerTest < ActionMailer::TestCase tests UserMailer - def test_invite + test "invite" do @expected.from = 'me@example.com' @expected.to = 'friend@example.com' @expected.subject = "You have been invited by #{@expected.from}" @@ -920,7 +913,7 @@ Functional testing for mailers involves more than just checking that the email b require 'test_helper' class UserControllerTest < ActionController::TestCase - def test_invite_friend + test "invite friend" do assert_difference 'ActionMailer::Base.deliveries.size', +1 do post :invite_friend, :email => 'friend@example.com' end From 603d4fbacd69d95987d3f7fed91631e481f5a1cf Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sat, 16 Jan 2010 16:14:35 -0600 Subject: [PATCH 18/82] Build middleware stack and reload routes after app initializers run [#3709 state:resolved] --- actionpack/lib/action_controller/railtie.rb | 1 - railties/lib/rails/application.rb | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 741101a21007b..f070b13dbf2fd 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -23,7 +23,6 @@ class Railtie < Rails::Railtie initializer "action_controller.initialize_routing" do |app| app.route_configuration_files << app.config.routes_configuration_file app.route_configuration_files << app.config.builtin_routes_configuration_file - app.reload_routes! end initializer "action_controller.initialize_framework_caches" do diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index c95316a4daf10..4d05f8115c920 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -99,9 +99,15 @@ def plugins end end + def app + @app ||= begin + reload_routes! + middleware.build(routes) + end + end + def call(env) - @app ||= middleware.build(routes) - @app.call(env) + app.call(env) end initializer :load_application_initializers do @@ -110,6 +116,10 @@ def call(env) end end + initializer :build_middleware_stack do + app + end + # Fires the user-supplied after_initialize block (Configuration#after_initialize) initializer :after_initialize do config.after_initialize_blocks.each do |block| From b8b6621acbc26a41421e9f9daded9b2a9ab82f1b Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 03:54:52 +0530 Subject: [PATCH 19/82] Simplify Model.scoped definition --- activerecord/lib/active_record/named_scope.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 90fd70043734b..bc04ffc089d56 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -26,13 +26,7 @@ def scoped(options = {}, &block) if options.present? Scope.new(self, options, &block) else - current_scope = current_scoped_methods - - unless current_scope - unscoped.spawn - else - construct_finder_arel({}, current_scoped_methods) - end + current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.spawn end end From 7921a73acda62c3208b173858a40221cb33f9ff8 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 04:20:11 +0530 Subject: [PATCH 20/82] Use relations to build scope for named scopes --- activerecord/lib/active_record/named_scope.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index bc04ffc089d56..3f5cf68fd2906 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -124,7 +124,7 @@ class Scope end end - delegate :scopes, :with_scope, :scoped_methods, :to => :proxy_scope + delegate :scopes, :with_scope, :scoped_methods, :unscoped, :to => :proxy_scope def initialize(proxy_scope, options, &block) options ||= {} @@ -186,6 +186,11 @@ def many? end protected + + def relation + @relation ||= unscoped.apply_finder_options(proxy_options) + end + def proxy_found @found || load_found end @@ -195,7 +200,7 @@ def method_missing(method, *args, &block) if scopes.include?(method) scopes[method].call(self, *args) else - with_scope({:find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {}}, :reverse_merge) do + with_scope(relation, :reverse_merge) do method = :new if method == :build if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) with_scope current_scoped_methods_when_defined do From 8d87c80c19b7f0c0966fcfd52e7a6ed99d347a36 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 04:25:59 +0530 Subject: [PATCH 21/82] Make Relation#reload force load the records immediately --- activerecord/lib/active_record/relation.rb | 5 +++-- activerecord/test/cases/relations_test.rb | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 85bf878416137..7c2a080eadbe8 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -124,12 +124,13 @@ def loaded? end def reload - @loaded = false reset + to_a # force reload + self end def reset - @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = nil + @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil @records = [] self end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 195889f1df787..acf247d27b9ec 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -68,10 +68,12 @@ def test_reload assert topics.loaded? - topics.reload - assert ! topics.loaded? + original_size = topics.to_a.size + Topic.create! :title => 'fake' - assert_queries(1) { topics.to_a } + assert_queries(1) { topics.reload } + assert_equal original_size + 1, topics.size + assert topics.loaded? end def test_finding_with_conditions From d2d4acf02793580e0f0c1bc380389527325b6254 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sat, 16 Jan 2010 17:21:46 -0600 Subject: [PATCH 22/82] Cookies middleware --- .../lib/action_controller/metal/cookies.rb | 186 +-------------- .../lib/action_controller/metal/testing.rb | 3 + actionpack/lib/action_dispatch.rb | 1 + .../lib/action_dispatch/http/response.rb | 2 +- .../lib/action_dispatch/middleware/cookies.rb | 214 ++++++++++++++++++ actionpack/test/abstract_unit.rb | 1 + actionpack/test/controller/cookie_test.rb | 32 +-- actionpack/test/dispatch/response_test.rb | 9 +- .../dispatch/session/cookie_store_test.rb | 4 +- railties/lib/rails/configuration.rb | 1 + railties/test/application/middleware_test.rb | 1 + 11 files changed, 235 insertions(+), 219 deletions(-) create mode 100644 actionpack/lib/action_dispatch/middleware/cookies.rb diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb index 5b51bd21d059a..7aa687b52c4c3 100644 --- a/actionpack/lib/action_controller/metal/cookies.rb +++ b/actionpack/lib/action_controller/metal/cookies.rb @@ -1,48 +1,4 @@ module ActionController #:nodoc: - # Cookies are read and written through ActionController#cookies. - # - # The cookies being read are the ones received along with the request, the cookies - # being written will be sent out with the response. Reading a cookie does not get - # the cookie object itself back, just the value it holds. - # - # Examples for writing: - # - # # Sets a simple session cookie. - # cookies[:user_name] = "david" - # - # # Sets a cookie that expires in 1 hour. - # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now } - # - # Examples for reading: - # - # cookies[:user_name] # => "david" - # cookies.size # => 2 - # - # Example for deleting: - # - # cookies.delete :user_name - # - # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie: - # - # cookies[:key] = { - # :value => 'a yummy cookie', - # :expires => 1.year.from_now, - # :domain => 'domain.com' - # } - # - # cookies.delete(:key, :domain => 'domain.com') - # - # The option symbols for setting cookies are: - # - # * :value - The cookie's value or list of values (as an array). - # * :path - The path for which this cookie applies. Defaults to the root - # of the application. - # * :domain - The domain for which this cookie applies. - # * :expires - The time at which this cookie expires, as a Time object. - # * :secure - Whether this cookie is a only transmitted to HTTPS servers. - # Default is +false+. - # * :httponly - Whether this cookie is accessible via scripting or - # only HTTP. Defaults to +false+. module Cookies extend ActiveSupport::Concern @@ -52,146 +8,10 @@ module Cookies helper_method :cookies cattr_accessor :cookie_verifier_secret end - - protected - # Returns the cookie container, which operates as described above. + + private def cookies - @cookies ||= CookieJar.build(request, response) - end - end - - class CookieJar < Hash #:nodoc: - def self.build(request, response) - new.tap do |hash| - hash.update(request.cookies) - hash.response = response - end - end - - attr_accessor :response - - # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. - def [](name) - super(name.to_s) - end - - # Sets the cookie named +name+. The second argument may be the very cookie - # value, or a hash of options as documented above. - def []=(key, options) - if options.is_a?(Hash) - options.symbolize_keys! - value = options[:value] - else - value = options - options = { :value => value } + request.cookie_jar end - - super(key.to_s, value) - - options[:path] ||= "/" - response.set_cookie(key, options) - end - - # Removes the cookie on the client machine by setting the value to an empty string - # and setting its expiration date into the past. Like []=, you can pass in - # an options hash to delete cookies with extra data such as a :path. - def delete(key, options = {}) - options.symbolize_keys! - options[:path] ||= "/" - value = super(key.to_s) - response.delete_cookie(key, options) - value - end - - # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example: - # - # cookies.permanent[:prefers_open_id] = true - # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT - # - # This jar is only meant for writing. You'll read permanent cookies through the regular accessor. - # - # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples: - # - # cookies.permanent.signed[:remember_me] = current_user.id - # # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT - def permanent - @permanent ||= PermanentCookieJar.new(self) - end - - # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from - # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed - # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will - # be raised. - # - # This jar requires that you set a suitable secret for the verification on ActionController::Base.cookie_verifier_secret. - # - # Example: - # - # cookies.signed[:discount] = 45 - # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/ - # - # cookies.signed[:discount] # => 45 - def signed - @signed ||= SignedCookieJar.new(self) - end - end - - class PermanentCookieJar < CookieJar #:nodoc: - def initialize(parent_jar) - @parent_jar = parent_jar - end - - def []=(key, options) - if options.is_a?(Hash) - options.symbolize_keys! - else - options = { :value => options } - end - - options[:expires] = 20.years.from_now - @parent_jar[key] = options - end - - def signed - @signed ||= SignedCookieJar.new(self) - end - - def controller - @parent_jar.controller - end - - def method_missing(method, *arguments, &block) - @parent_jar.send(method, *arguments, &block) - end - end - - class SignedCookieJar < CookieJar #:nodoc: - def initialize(parent_jar) - unless ActionController::Base.cookie_verifier_secret - raise "You must set ActionController::Base.cookie_verifier_secret to use signed cookies" - end - - @parent_jar = parent_jar - @verifier = ActiveSupport::MessageVerifier.new(ActionController::Base.cookie_verifier_secret) - end - - def [](name) - @verifier.verify(@parent_jar[name]) - end - - def []=(key, options) - if options.is_a?(Hash) - options.symbolize_keys! - options[:value] = @verifier.generate(options[:value]) - else - options = { :value => @verifier.generate(options) } - end - - @parent_jar[key] = options - end - - def method_missing(method, *arguments, &block) - @parent_jar.send(method, *arguments, &block) - end end end diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index c193a5eff4492..d62269b9af11f 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -10,6 +10,9 @@ def process_with_new_base_test(request, response) @_response = response @_response.request = request ret = process(request.parameters[:action]) + if cookies = @_request.env['action_dispatch.cookies'] + cookies.write(@_response) + end @_response.body ||= self.response_body @_response.prepare! set_test_assigns diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 7b4421231078b..763a090de23de 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -43,6 +43,7 @@ module ActionDispatch autoload_under 'middleware' do autoload :Callbacks autoload :Cascade + autoload :Cookies autoload :Flash autoload :Head autoload :ParamsParser diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 65df9b1f03470..f299306ff44be 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -119,7 +119,7 @@ def location=(url) def to_a assign_default_content_type_and_charset! handle_conditional_get! - self["Set-Cookie"] = @cookie.join("\n") + self["Set-Cookie"] = @cookie.join("\n") unless @cookie.blank? self["ETag"] = @etag if @etag super end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb new file mode 100644 index 0000000000000..5d2734a15e476 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -0,0 +1,214 @@ +module ActionDispatch + class Request + def cookie_jar + env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self) + end + end + + # Cookies are read and written through ActionController#cookies. + # + # The cookies being read are the ones received along with the request, the cookies + # being written will be sent out with the response. Reading a cookie does not get + # the cookie object itself back, just the value it holds. + # + # Examples for writing: + # + # # Sets a simple session cookie. + # cookies[:user_name] = "david" + # + # # Sets a cookie that expires in 1 hour. + # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now } + # + # Examples for reading: + # + # cookies[:user_name] # => "david" + # cookies.size # => 2 + # + # Example for deleting: + # + # cookies.delete :user_name + # + # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie: + # + # cookies[:key] = { + # :value => 'a yummy cookie', + # :expires => 1.year.from_now, + # :domain => 'domain.com' + # } + # + # cookies.delete(:key, :domain => 'domain.com') + # + # The option symbols for setting cookies are: + # + # * :value - The cookie's value or list of values (as an array). + # * :path - The path for which this cookie applies. Defaults to the root + # of the application. + # * :domain - The domain for which this cookie applies. + # * :expires - The time at which this cookie expires, as a Time object. + # * :secure - Whether this cookie is a only transmitted to HTTPS servers. + # Default is +false+. + # * :httponly - Whether this cookie is accessible via scripting or + # only HTTP. Defaults to +false+. + class Cookies + class CookieJar < Hash #:nodoc: + def self.build(request) + new.tap do |hash| + hash.update(request.cookies) + end + end + + def initialize + @set_cookies = {} + @delete_cookies = {} + + super + end + + # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. + def [](name) + super(name.to_s) + end + + # Sets the cookie named +name+. The second argument may be the very cookie + # value, or a hash of options as documented above. + def []=(key, options) + if options.is_a?(Hash) + options.symbolize_keys! + value = options[:value] + else + value = options + options = { :value => value } + end + + value = super(key.to_s, value) + + options[:path] ||= "/" + @set_cookies[key] = options + value + end + + # Removes the cookie on the client machine by setting the value to an empty string + # and setting its expiration date into the past. Like []=, you can pass in + # an options hash to delete cookies with extra data such as a :path. + def delete(key, options = {}) + options.symbolize_keys! + options[:path] ||= "/" + value = super(key.to_s) + @delete_cookies[key] = options + value + end + + # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example: + # + # cookies.permanent[:prefers_open_id] = true + # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + # + # This jar is only meant for writing. You'll read permanent cookies through the regular accessor. + # + # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples: + # + # cookies.permanent.signed[:remember_me] = current_user.id + # # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + def permanent + @permanent ||= PermanentCookieJar.new(self) + end + + # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from + # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed + # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will + # be raised. + # + # This jar requires that you set a suitable secret for the verification on ActionController::Base.cookie_verifier_secret. + # + # Example: + # + # cookies.signed[:discount] = 45 + # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/ + # + # cookies.signed[:discount] # => 45 + def signed + @signed ||= SignedCookieJar.new(self) + end + + def write(response) + @set_cookies.each { |k, v| response.set_cookie(k, v) } + @delete_cookies.each { |k, v| response.delete_cookie(k, v) } + end + end + + class PermanentCookieJar < CookieJar #:nodoc: + def initialize(parent_jar) + @parent_jar = parent_jar + end + + def []=(key, options) + if options.is_a?(Hash) + options.symbolize_keys! + else + options = { :value => options } + end + + options[:expires] = 20.years.from_now + @parent_jar[key] = options + end + + def signed + @signed ||= SignedCookieJar.new(self) + end + + def controller + @parent_jar.controller + end + + def method_missing(method, *arguments, &block) + @parent_jar.send(method, *arguments, &block) + end + end + + class SignedCookieJar < CookieJar #:nodoc: + def initialize(parent_jar) + unless ActionController::Base.cookie_verifier_secret + raise "You must set ActionController::Base.cookie_verifier_secret to use signed cookies" + end + + @parent_jar = parent_jar + @verifier = ActiveSupport::MessageVerifier.new(ActionController::Base.cookie_verifier_secret) + end + + def [](name) + @verifier.verify(@parent_jar[name]) + end + + def []=(key, options) + if options.is_a?(Hash) + options.symbolize_keys! + options[:value] = @verifier.generate(options[:value]) + else + options = { :value => @verifier.generate(options) } + end + + @parent_jar[key] = options + end + + def method_missing(method, *arguments, &block) + @parent_jar.send(method, *arguments, &block) + end + end + + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + + if cookie_jar = env['action_dispatch.cookies'] + response = Rack::Response.new(body, status, headers) + cookie_jar.write(response) + response.to_a + else + [status, headers, body] + end + end + end +end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 10913c0fdb620..7b04638ccc246 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -92,6 +92,7 @@ def self.build_app(routes = nil) middleware.use "ActionDispatch::ShowExceptions" middleware.use "ActionDispatch::Callbacks" middleware.use "ActionDispatch::ParamsParser" + middleware.use "ActionDispatch::Cookies" middleware.use "ActionDispatch::Flash" middleware.use "ActionDispatch::Head" }.build(routes || ActionController::Routing::Routes) diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index c8e8b3857ed2f..fd6538b27ae0a 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -54,12 +54,12 @@ def set_permanent_cookie cookies.permanent[:user_name] = "Jamie" head :ok end - + def set_signed_cookie cookies.signed[:user_id] = 45 head :ok end - + def set_permanent_signed_cookie cookies.permanent.signed[:remember_me] = 100 head :ok @@ -120,28 +120,6 @@ def test_expiring_cookie assert_equal({"user_name" => nil}, @response.cookies) end - def test_cookiejar_accessor - @request.cookies["user_name"] = "david" - @controller.request = @request - jar = ActionController::CookieJar.build(@controller.request, @controller.response) - assert_equal "david", jar["user_name"] - assert_equal nil, jar["something_else"] - end - - def test_cookiejar_accessor_with_array_value - @request.cookies["pages"] = %w{1 2 3} - @controller.request = @request - jar = ActionController::CookieJar.build(@controller.request, @controller.response) - assert_equal %w{1 2 3}, jar["pages"] - end - - def test_cookiejar_delete_removes_item_and_returns_its_value - @request.cookies["user_name"] = "david" - @controller.response = @response - jar = ActionController::CookieJar.build(@controller.request, @controller.response) - assert_equal "david", jar.delete("user_name") - end - def test_delete_cookie_with_path get :delete_cookie_with_path assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT" @@ -157,19 +135,19 @@ def test_permanent_cookie assert_match /Jamie/, @response.headers["Set-Cookie"] assert_match %r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"] end - + def test_signed_cookie get :set_signed_cookie assert_equal 45, @controller.send(:cookies).signed[:user_id] end - + def test_permanent_signed_cookie get :set_permanent_signed_cookie assert_match %r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"] assert_equal 100, @controller.send(:cookies).signed[:remember_me] end - + private def assert_cookie_header(expected) header = @response.headers["Set-Cookie"] diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 02f63f7006624..4697fa3e2b50a 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -13,8 +13,7 @@ def setup assert_equal({ "Content-Type" => "text/html; charset=utf-8", "Cache-Control" => "max-age=0, private, must-revalidate", - "ETag" => '"65a8e27d8879283831b664bd8b7f0ad4"', - "Set-Cookie" => "" + "ETag" => '"65a8e27d8879283831b664bd8b7f0ad4"' }, headers) parts = [] @@ -30,8 +29,7 @@ def setup assert_equal({ "Content-Type" => "text/html; charset=utf-8", "Cache-Control" => "max-age=0, private, must-revalidate", - "ETag" => '"ebb5e89e8a94e9dd22abf5d915d112b2"', - "Set-Cookie" => "" + "ETag" => '"ebb5e89e8a94e9dd22abf5d915d112b2"' }, headers) end @@ -44,8 +42,7 @@ def setup assert_equal 200, status assert_equal({ "Content-Type" => "text/html; charset=utf-8", - "Cache-Control" => "no-cache", - "Set-Cookie" => "" + "Cache-Control" => "no-cache" }, headers) parts = [] diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index ab7b9bc31bb17..d2c1758af1c82 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -137,7 +137,7 @@ def test_doesnt_write_session_cookie_if_session_is_not_accessed with_test_route_set do get '/no_session_access' assert_response :success - assert_equal "", headers['Set-Cookie'] + assert_equal nil, headers['Set-Cookie'] end end @@ -147,7 +147,7 @@ def test_doesnt_write_session_cookie_if_session_is_unchanged "fef868465920f415f2c0652d6910d3af288a0367" get '/no_session_access' assert_response :success - assert_equal "", headers['Set-Cookie'] + assert_equal nil, headers['Set-Cookie'] end end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index a2fab120cf80f..a597a01d72250 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -15,6 +15,7 @@ def self.default_middleware_stack middleware.use('::Rack::Runtime') middleware.use('ActionDispatch::ShowExceptions', lambda { ActionController::Base.consider_all_requests_local }) middleware.use('ActionDispatch::Callbacks', lambda { ActionController::Dispatcher.prepare_each_request }) + middleware.use('ActionDispatch::Cookies') middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options }) middleware.use('ActionDispatch::Flash', :if => lambda { ActionController::Base.session_store }) middleware.use('ActionDispatch::ParamsParser') diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 7b3077bb6e5ff..f2bfeb79fe497 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -19,6 +19,7 @@ def setup "Rack::Runtime", "ActionDispatch::ShowExceptions", "ActionDispatch::Callbacks", + "ActionDispatch::Cookies", "ActionDispatch::Session::CookieStore", "ActionDispatch::Flash", "ActionDispatch::Cascade", From eb39d0f7b999f09c4e13f035634887a8f5592443 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sat, 16 Jan 2010 21:34:35 -0600 Subject: [PATCH 23/82] Use backtrace cleaner for dev mode exception page --- .../middleware/show_exceptions.rb | 21 +++++- .../middleware/templates/rescues/_trace.erb | 6 +- .../templates/rescues/diagnostics.erb | 2 +- .../lib/active_support/backtrace_cleaner.rb | 32 ++++++--- activesupport/lib/active_support/cache.rb | 8 +-- .../lib/active_support/core_ext/exception.rb | 47 ------------- activesupport/test/core_ext/exception_test.rb | 69 ------------------- railties/lib/rails/backtrace_cleaner.rb | 31 +++++---- railties/test/backtrace_cleaner_test.rb | 4 +- 9 files changed, 68 insertions(+), 152 deletions(-) delete mode 100644 activesupport/test/core_ext/exception_test.rb diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 10f04dcdf60bb..56fd4f45c0db6 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -88,7 +88,10 @@ def render_exception(env, exception) def rescue_action_locally(request, exception) template = ActionView::Base.new([RESCUES_TEMPLATE_PATH], :request => request, - :exception => exception + :exception => exception, + :application_trace => application_trace(exception), + :framework_trace => framework_trace(exception), + :full_trace => full_trace(exception) ) file = "rescues/#{@@rescue_templates[exception.class.name]}.erb" body = template.render(:file => file, :layout => 'rescues/layout.erb') @@ -148,9 +151,21 @@ def log_error(exception) end end - def clean_backtrace(exception) + def application_trace(exception) + clean_backtrace(exception, :silent) + end + + def framework_trace(exception) + clean_backtrace(exception, :noise) + end + + def full_trace(exception) + clean_backtrace(exception, :all) + end + + def clean_backtrace(exception, *args) defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ? - Rails.backtrace_cleaner.clean(exception.backtrace) : + Rails.backtrace_cleaner.clean(exception.backtrace, *args) : exception.backtrace end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb index 07b4919934b0b..d18b162a93d6e 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb @@ -1,8 +1,8 @@ <% traces = [ - ["Application Trace", @exception.application_backtrace], - ["Framework Trace", @exception.framework_backtrace], - ["Full Trace", @exception.clean_backtrace] + ["Application Trace", @application_trace], + ["Framework Trace", @framework_trace], + ["Full Trace", @full_trace] ] names = traces.collect {|name, trace| name} %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb index 693e56270a3d9..bd6ffbab5d82b 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb @@ -4,7 +4,7 @@ in <%=h @request.parameters['controller'].humanize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %> <% end %> -
    <%=h @exception.clean_message %>
    +
    <%=h @exception.message %>
    <%= render :file => "rescues/_trace.erb" %> <%= render :file => "rescues/_request_and_response.erb" %> diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 0e262c003e431..6fab565646199 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -9,7 +9,7 @@ module ActiveSupport # Example: # # bc = BacktraceCleaner.new - # bc.add_filter { |line| line.gsub(Rails.root, '') } + # bc.add_filter { |line| line.gsub(Rails.root, '') } # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems # @@ -18,10 +18,19 @@ class BacktraceCleaner def initialize @filters, @silencers = [], [] end - + # Returns the backtrace after all filters and silencers has been run against it. Filters run first, then silencers. - def clean(backtrace) - silence(filter(backtrace)) + def clean(backtrace, kind = :silent) + filtered = filter(backtrace) + + case kind + when :silent + silence(filtered) + when :noise + noise(filtered) + else + filtered + end end # Adds a filter from the block provided. Each line in the backtrace will be mapped against this filter. @@ -51,21 +60,28 @@ def remove_silencers! @silencers = [] end - private def filter(backtrace) @filters.each do |f| backtrace = backtrace.map { |line| f.call(line) } end - + backtrace end - + def silence(backtrace) @silencers.each do |s| backtrace = backtrace.reject { |line| s.call(line) } end - + + backtrace + end + + def noise(backtrace) + @silencers.each do |s| + backtrace = backtrace.select { |line| s.call(line) } + end + backtrace end end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 6360a4614ed99..7213b24f2d003 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -30,7 +30,7 @@ module Strategy # # ActiveSupport::Cache.lookup_store(:memory_store) # # => returns a new ActiveSupport::Cache::MemoryStore object - # + # # ActiveSupport::Cache.lookup_store(:mem_cache_store) # # => returns a new ActiveSupport::Cache::MemCacheStore object # @@ -97,7 +97,7 @@ def self.expand_cache_key(key, namespace = nil) # Ruby objects, but don't count on every cache store to be able to do that. # # cache = ActiveSupport::Cache::MemoryStore.new - # + # # cache.read("city") # => nil # cache.write("city", "Duckburgh") # cache.read("city") # => "Duckburgh" @@ -139,7 +139,7 @@ def self.instrument # # cache.write("today", "Monday") # cache.fetch("today") # => "Monday" - # + # # cache.fetch("city") # => nil # cache.fetch("city") do # "Duckburgh" @@ -198,7 +198,7 @@ def read(key, options = nil, &block) # You may also specify additional options via the +options+ argument. # The specific cache store implementation will decide what to do with # +options+. - # + # # For example, MemCacheStore supports the +:expires_in+ option, which # tells the memcached server to automatically expire the cache item after # a certain period: diff --git a/activesupport/lib/active_support/core_ext/exception.rb b/activesupport/lib/active_support/core_ext/exception.rb index b594fbae8e131..ef801e713dd15 100644 --- a/activesupport/lib/active_support/core_ext/exception.rb +++ b/activesupport/lib/active_support/core_ext/exception.rb @@ -1,50 +1,3 @@ module ActiveSupport FrozenObjectError = RUBY_VERSION < '1.9' ? TypeError : RuntimeError end - -# TODO: Turn all this into using the BacktraceCleaner. -class Exception # :nodoc: - # Clean the paths contained in the message. - def self.clean_paths(string) - require 'pathname' unless defined? Pathname - string.gsub(%r{[\w. ]+(/[\w. ]+)+(\.rb)?(\b|$)}) do |path| - Pathname.new(path).cleanpath - end - end - - def clean_message - Exception.clean_paths(message) - end - - TraceSubstitutions = [] - FrameworkStart = /action_controller\/dispatcher\.rb/.freeze - FrameworkRegexp = /generated|vendor|dispatch|ruby|script\/\w+/.freeze - - def clean_backtrace - backtrace.collect do |line| - substituted = TraceSubstitutions.inject(line) do |result, (regexp, sub)| - result.gsub regexp, sub - end - Exception.clean_paths(substituted) - end - end - - def application_backtrace - before_framework_frame = nil - before_application_frame = true - - trace = clean_backtrace.reject do |line| - before_framework_frame ||= (line =~ FrameworkStart) - non_app_frame = (line =~ FrameworkRegexp) - before_application_frame = false unless non_app_frame - before_framework_frame || (non_app_frame && !before_application_frame) - end - - # If we didn't find any application frames, return an empty app trace. - before_application_frame ? [] : trace - end - - def framework_backtrace - clean_backtrace.grep FrameworkRegexp - end -end diff --git a/activesupport/test/core_ext/exception_test.rb b/activesupport/test/core_ext/exception_test.rb deleted file mode 100644 index e63842c0bd975..0000000000000 --- a/activesupport/test/core_ext/exception_test.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/exception' - -class ExceptionExtTests < Test::Unit::TestCase - - def get_exception(cls = RuntimeError, msg = nil, trace = nil) - begin raise cls, msg, (trace || caller) - rescue Exception => e # passed Exception - return e - end - end - - def setup - Exception::TraceSubstitutions.clear - end - - def test_clean_backtrace - Exception::TraceSubstitutions << [/\s*hidden.*/, ''] - e = get_exception RuntimeError, 'RAWR', ['bhal.rb', 'rawh hid den stuff is not here', 'almost all'] - assert_kind_of Exception, e - assert_equal ['bhal.rb', 'rawh hid den stuff is not here', 'almost all'], e.clean_backtrace - end - - def test_app_backtrace - Exception::TraceSubstitutions << [/\s*hidden.*/, ''] - e = get_exception RuntimeError, 'RAWR', ['bhal.rb', ' vendor/file.rb some stuff', 'almost all'] - assert_kind_of Exception, e - assert_equal ['bhal.rb', 'almost all'], e.application_backtrace - end - - def test_app_backtrace_with_before - Exception::TraceSubstitutions << [/\s*hidden.*/, ''] - e = get_exception RuntimeError, 'RAWR', ['vendor/file.rb some stuff', 'bhal.rb', ' vendor/file.rb some stuff', 'almost all'] - assert_kind_of Exception, e - assert_equal ['vendor/file.rb some stuff', 'bhal.rb', 'almost all'], e.application_backtrace - end - - def test_framework_backtrace_with_before - Exception::TraceSubstitutions << [/\s*hidden.*/, ''] - e = get_exception RuntimeError, 'RAWR', ['vendor/file.rb some stuff', 'bhal.rb', ' vendor/file.rb some stuff', 'almost all'] - assert_kind_of Exception, e - assert_equal ['vendor/file.rb some stuff', ' vendor/file.rb some stuff'], e.framework_backtrace - end - - def test_backtrace_should_clean_paths - Exception::TraceSubstitutions << [/\s*hidden.*/, ''] - e = get_exception RuntimeError, 'RAWR', ['a/b/c/../d/../../../bhal.rb', 'rawh hid den stuff is not here', 'almost all'] - assert_kind_of Exception, e - assert_equal ['bhal.rb', 'rawh hid den stuff is not here', 'almost all'], e.clean_backtrace - end - - def test_clean_message_should_clean_paths - Exception::TraceSubstitutions << [/\s*hidden.*/, ''] - e = get_exception RuntimeError, "I dislike a/z/x/../../b/y/../c", ['a/b/c/../d/../../../bhal.rb', 'rawh hid den stuff is not here', 'almost all'] - assert_kind_of Exception, e - assert_equal "I dislike a/b/c", e.clean_message - end - - def test_app_trace_should_be_empty_when_no_app_frames - Exception::TraceSubstitutions << [/\s*hidden.*/, ''] - e = get_exception RuntimeError, 'RAWR', ['vendor/file.rb some stuff', 'generated/bhal.rb', ' vendor/file.rb some stuff', 'generated/almost all'] - assert_kind_of Exception, e - assert_equal [], e.application_backtrace - end - - def test_frozen_error - assert_raise(ActiveSupport::FrozenObjectError) { "foo".freeze.gsub!(/oo/,'aa') } - end -end diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb index cd7dd0f80ac64..40198306f91b7 100644 --- a/railties/lib/rails/backtrace_cleaner.rb +++ b/railties/lib/rails/backtrace_cleaner.rb @@ -3,17 +3,7 @@ module Rails class BacktraceCleaner < ActiveSupport::BacktraceCleaner ERB_METHOD_SIG = /:in `_run_erb_.*/ - - RAILS_GEMS = %w( actionpack activerecord actionmailer activesupport activeresource rails ) - - VENDOR_DIRS = %w( vendor/rails ) - SERVER_DIRS = %w( lib/mongrel bin/mongrel - lib/passenger bin/passenger-spawn-server - lib/rack ) - RAILS_NOISE = %w( script/server ) - RUBY_NOISE = %w( rubygems/custom_require benchmark.rb ) - - ALL_NOISE = VENDOR_DIRS + SERVER_DIRS + RAILS_NOISE + RUBY_NOISE + APP_DIRS = %w( app config lib test ) def initialize super @@ -22,10 +12,9 @@ def initialize add_filter { |line| line.sub('./', '/') } # for tests add_gem_filters + add_bundler_filters - add_silencer { |line| ALL_NOISE.any? { |dir| line.include?(dir) } } - add_silencer { |line| RAILS_GEMS.any? { |gem| line =~ /^#{gem} / } } - add_silencer { |line| line =~ %r(vendor/plugins/[^\/]+/lib) } + add_silencer { |line| !APP_DIRS.any? { |dir| line =~ /^#{dir}/ } } end private @@ -33,9 +22,21 @@ def add_gem_filters return unless defined? Gem (Gem.path + [Gem.default_dir]).uniq.each do |path| # http://gist.github.com/30430 - add_filter { |line| line.sub(/(#{path})\/gems\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) \4')} + add_filter { |line| + line.sub(%r{(#{path})/gems/([^/]+)-([0-9.]+)/(.*)}, '\2 (\3) \4') + } end end + + def add_bundler_filters + return unless defined? Bundler + add_filter { |line| + line.sub(%r{vendor/gems/[^/]+/[^/]+/gems/([^/]+)-([0-9.]+)/(.*)}, '\1 (\2) \3') + } + add_filter { |line| + line.sub(%r{vendor/gems/[^/]+/[^/]+/dirs/([^/]+)/(.*)}, '\1 \2') + } + end end # For installing the BacktraceCleaner in the test/unit diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb index 6cff591b94f67..80077378db93c 100644 --- a/railties/test/backtrace_cleaner_test.rb +++ b/railties/test/backtrace_cleaner_test.rb @@ -37,7 +37,7 @@ def setup test "should format installed gems correctly" do @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ] - @result = @cleaner.clean(@backtrace) + @result = @cleaner.clean(@backtrace, :all) assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] end @@ -46,7 +46,7 @@ def setup # skip this test if default_dir is the only directory on Gem.path if @target_dir @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] - @result = @cleaner.clean(@backtrace) + @result = @cleaner.clean(@backtrace, :all) assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] end end From f9a4300415a320e1cb01fb3806fbd3637df38183 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sat, 16 Jan 2010 21:43:03 -0600 Subject: [PATCH 24/82] debug helper output should not be sanitized --- actionpack/lib/action_view/helpers/debug_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index 90863fca08d0e..885945fde3e6b 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -27,10 +27,10 @@ module DebugHelper def debug(object) begin Marshal::dump(object) - "
    #{h(object.to_yaml).gsub("  ", "  ")}
    " + "
    #{h(object.to_yaml).gsub("  ", "  ")}
    ".html_safe! rescue Exception => e # errors from Marshal or YAML # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback - "#{h(object.inspect)}" + "#{h(object.inspect)}".html_safe! end end end From 54a043895f904e0237a590738d494554ec555265 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 14:16:50 +0530 Subject: [PATCH 25/82] Make merging of order values consistent --- activerecord/lib/active_record/relation/spawn_methods.rb | 2 +- activerecord/test/cases/base_test.rb | 4 +++- activerecord/test/cases/method_scoping_test.rb | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index f4abaae43e938..953ea5ea1cfa6 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -33,7 +33,7 @@ def merge(r) from(r.from_value). having(r.having_values) - merged_relation.order_values = Array.wrap(order_values) + Array.wrap(r.order_values) + merged_relation.order_values = r.order_values if r.order_values.present? merged_relation.create_with_value = @create_with_value diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index aea6aed8d9964..1441b4278da28 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1865,7 +1865,9 @@ def test_scoped_find_order end assert scoped_developers.include?(developers(:poor_jamis)) assert scoped_developers.include?(developers(:david)) - assert scoped_developers.include?(developers(:dev_10)) + assert ! scoped_developers.include?(developers(:jamis)) + assert_equal 3, scoped_developers.size + # Test without scoped find conditions to ensure we get the right thing developers = Developer.find(:all, :order => 'id', :limit => 1) assert scoped_developers.include?(developers(:david)) diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 26aa3ed8d5c73..a502b50c3ef2c 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -625,21 +625,21 @@ def test_default_scoping_with_inheritance end def test_method_scope - expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.salary } + expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary } received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary } assert_equal expected, received end def test_nested_scope - expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.salary } + expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary } received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } end assert_equal expected, received end - def test_named_scope_order_appended_to_default_scope_order - expected = Developer.find(:all, :order => 'name DESC, salary DESC').collect { |dev| dev.name } + def test_named_scope_overwrites_default + expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name } received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name } assert_equal expected, received end From 645832cfdd90b6e8cb6e043bfeb25da0c39401af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Jan 2010 09:53:57 +0100 Subject: [PATCH 26/82] Clean up filter parameter logging tests. --- .../test/controller/filter_params_test.rb | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/actionpack/test/controller/filter_params_test.rb b/actionpack/test/controller/filter_params_test.rb index d0635669c2b03..45949636c37a3 100644 --- a/actionpack/test/controller/filter_params_test.rb +++ b/actionpack/test/controller/filter_params_test.rb @@ -9,23 +9,6 @@ def payment class FilterParamTest < ActionController::TestCase tests FilterParamController - class MockLogger - attr_reader :logged - attr_accessor :level - - def initialize - @level = Logger::DEBUG - end - - def method_missing(method, *args) - @logged ||= [] - @logged << args.first unless block_given? - @logged << yield if block_given? - end - end - - setup :set_logger - def test_filter_parameters_must_have_one_word assert_raises RuntimeError do FilterParamController.filter_parameter_logging @@ -65,14 +48,4 @@ def test_filter_parameters_is_protected assert !FilterParamController.action_methods.include?('filter_parameters') assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) } end - - private - - def set_logger - @controller.logger = MockLogger.new - end - - def logs - @logs ||= @controller.logger.logged.compact.map {|l| l.to_s.strip} - end end From afd0c06dfa8d3e04e85f9f0ea65c9beb376cb79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Jan 2010 09:57:16 +0100 Subject: [PATCH 27/82] Validates needs hash slice. --- activemodel/lib/active_model/validations/validates.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index 4c82a993ae199..90b244228a112 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/slice' + module ActiveModel module Validations module ClassMethods From 0334f9f6cfa4c4c746de7e19250a13366b616c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Jan 2010 11:17:42 +0100 Subject: [PATCH 28/82] Add ActionDispatch::Notifications middleware. --- actionpack/lib/action_dispatch.rb | 1 + .../action_dispatch/middleware/callbacks.rb | 2 - .../middleware/notifications.rb | 24 +++++++ .../middleware/show_exceptions.rb | 7 +- actionpack/test/dispatch/callbacks_test.rb | 12 ---- .../test/dispatch/notifications_test.rb | 69 +++++++++++++++++++ .../test/dispatch/show_exceptions_test.rb | 23 ------- .../lib/active_support/notifications.rb | 2 +- .../notifications/instrumenter.rb | 9 +++ activesupport/test/notifications_test.rb | 16 +++++ 10 files changed, 122 insertions(+), 43 deletions(-) create mode 100644 actionpack/lib/action_dispatch/middleware/notifications.rb create mode 100644 actionpack/test/dispatch/notifications_test.rb diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 763a090de23de..082562d921195 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -46,6 +46,7 @@ module ActionDispatch autoload :Cookies autoload :Flash autoload :Head + autoload :Notifications autoload :ParamsParser autoload :Rescue autoload :ShowExceptions diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 5ec406e1341bf..d07841218a12f 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -45,8 +45,6 @@ def call(env) run_callbacks(:prepare) if @prepare_each_request @app.call(env) end - ensure - ActiveSupport::Notifications.instrument "action_dispatch.callback" end end end diff --git a/actionpack/lib/action_dispatch/middleware/notifications.rb b/actionpack/lib/action_dispatch/middleware/notifications.rb new file mode 100644 index 0000000000000..01d2cbb435b4a --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/notifications.rb @@ -0,0 +1,24 @@ +module ActionDispatch + # Provide notifications in the middleware stack. Notice that for the before_dispatch + # and after_dispatch notifications, we just send the original env, so we don't pile + # up large env hashes in the queue. However, in exception cases, the whole env hash + # is actually useful, so we send it all. + class Notifications + def initialize(app) + @app = app + end + + def call(stack_env) + env = stack_env.dup + ActiveSupport::Notifications.instrument("action_dispatch.before_dispatch", :env => env) + + ActiveSupport::Notifications.instrument!("action_dispatch.after_dispatch", :env => env) do + @app.call(stack_env) + end + rescue Exception => exception + ActiveSupport::Notifications.instrument('action_dispatch.exception', + :env => stack_env, :exception => exception) + raise exception + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 56fd4f45c0db6..0977f5c60ac5f 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -61,11 +61,8 @@ def initialize(app, consider_all_requests_local = false) def call(env) @app.call(env) rescue Exception => exception - ActiveSupport::Notifications.instrument 'action_dispatch.show_exception', - :env => env, :exception => exception do - raise exception if env['action_dispatch.show_exceptions'] == false - render_exception(env, exception) - end + raise exception if env['action_dispatch.show_exceptions'] == false + render_exception(env, exception) end private diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb index f3ea5209f401e..9df882ce75ba4 100644 --- a/actionpack/test/dispatch/callbacks_test.rb +++ b/actionpack/test/dispatch/callbacks_test.rb @@ -85,18 +85,6 @@ def test_before_and_after_callbacks assert_equal 4, Foo.b end - def test_should_send_an_instrumentation_callback_for_async_processing - ActiveSupport::Notifications.expects(:instrument).with("action_dispatch.callback") - dispatch - end - - def test_should_send_an_instrumentation_callback_for_async_processing_even_on_failure - ActiveSupport::Notifications.notifier.expects(:publish) - assert_raise RuntimeError do - dispatch { |env| raise "OMG" } - end - end - private def dispatch(cache_classes = true, &block) diff --git a/actionpack/test/dispatch/notifications_test.rb b/actionpack/test/dispatch/notifications_test.rb new file mode 100644 index 0000000000000..d834a734ef8c1 --- /dev/null +++ b/actionpack/test/dispatch/notifications_test.rb @@ -0,0 +1,69 @@ +require 'abstract_unit' + +class NotificationsMiddlewareTest < ActionController::IntegrationTest + Boomer = lambda do |env| + req = ActionDispatch::Request.new(env) + case req.path + when "/" + [200, {}, []] + else + raise "puke!" + end + end + + App = ActionDispatch::Notifications.new(Boomer) + + def setup + @queue = ActiveSupport::Notifications::Fanout.new + @notifier = ActiveSupport::Notifications::Notifier.new(@queue) + ActiveSupport::Notifications.notifier = @notifier + + @events = [] + ActiveSupport::Notifications.subscribe do |*args| + @events << args + end + + @app = App + end + + test "publishes notifications" do + get "/" + ActiveSupport::Notifications.notifier.wait + + assert_equal 2, @events.size + before, after = @events + + assert_equal 'action_dispatch.before_dispatch', before[0] + assert_kind_of Hash, before[4][:env] + assert_equal 'GET', before[4][:env]["REQUEST_METHOD"] + + assert_equal 'action_dispatch.after_dispatch', after[0] + assert_kind_of Hash, after[4][:env] + assert_equal 'GET', after[4][:env]["REQUEST_METHOD"] + end + + test "publishes notifications on failure" do + begin + get "/puke" + rescue + end + + ActiveSupport::Notifications.notifier.wait + + assert_equal 3, @events.size + before, after, exception = @events + + assert_equal 'action_dispatch.before_dispatch', before[0] + assert_kind_of Hash, before[4][:env] + assert_equal 'GET', before[4][:env]["REQUEST_METHOD"] + + assert_equal 'action_dispatch.after_dispatch', after[0] + assert_kind_of Hash, after[4][:env] + assert_equal 'GET', after[4][:env]["REQUEST_METHOD"] + + assert_equal 'action_dispatch.exception', exception[0] + assert_kind_of Hash, exception[4][:env] + assert_equal 'GET', exception[4][:env]["REQUEST_METHOD"] + assert_kind_of RuntimeError, exception[4][:exception] + end +end \ No newline at end of file diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index 951fb4a22e122..9f6a93756cc34 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -104,27 +104,4 @@ class ShowExceptionsTest < ActionController::IntegrationTest assert_response 405 assert_match /ActionController::MethodNotAllowed/, body end - - test "publishes notifications" do - # Wait pending notifications to be published - ActiveSupport::Notifications.notifier.wait - - @app, event = ProductionApp, nil - self.remote_addr = '127.0.0.1' - - ActiveSupport::Notifications.subscribe('action_dispatch.show_exception') do |*args| - event = args - end - - get "/" - assert_response 500 - assert_match /puke/, body - - ActiveSupport::Notifications.notifier.wait - - assert_equal 'action_dispatch.show_exception', event.first - assert_kind_of Hash, event.last[:env] - assert_equal 'GET', event.last[:env]["REQUEST_METHOD"] - assert_kind_of RuntimeError, event.last[:exception] - end end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 3e96decb8cb8a..a1383bb478a66 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -45,7 +45,7 @@ module Notifications class << self attr_writer :notifier delegate :publish, :subscribe, :to => :notifier - delegate :instrument, :to => :instrumenter + delegate :instrument, :instrument!, :to => :instrumenter def notifier @notifier ||= Notifier.new diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index f3d877efe712e..7c5b118ee30c7 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -20,6 +20,15 @@ def instrument(name, payload={}) result end + # The same as instrument, but sends the notification even if the yielded + # block raises an error. + def instrument!(name, payload={}) + time = Time.now + yield(payload) if block_given? + ensure + @notifier.publish(name, time, Time.now, @id, payload) + end + private def unique_id SecureRandom.hex(10) diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index c3eb1a4eb5ca1..c41d81fe7e9df 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -90,6 +90,22 @@ def test_instrument_returns_block_result drain end + def test_instrument_with_bang_returns_result_even_on_failure + begin + instrument!(:awesome, :payload => "notifications") do + raise "OMG" + end + flunk + rescue + end + + drain + + assert_equal 1, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload + end + def test_instrument_yields_the_paylod_for_further_modification assert_equal 2, instrument(:awesome) { |p| p[:result] = 1 + 1 } drain From 8f5e733998c2f06b63e72072c152546f2074c08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Jan 2010 11:29:41 +0100 Subject: [PATCH 29/82] Add notifications to default middleware stack and update subscriber tests. --- railties/lib/rails/configuration.rb | 1 + railties/lib/rails/subscriber.rb | 2 +- railties/test/application/middleware_test.rb | 1 + railties/test/application/notifications_test.rb | 2 +- railties/test/subscriber_test.rb | 6 +++--- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index a597a01d72250..72bebe085d62a 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -14,6 +14,7 @@ def self.default_middleware_stack middleware.use('::Rack::Lock', :if => lambda { !ActionController::Base.allow_concurrency }) middleware.use('::Rack::Runtime') middleware.use('ActionDispatch::ShowExceptions', lambda { ActionController::Base.consider_all_requests_local }) + middleware.use('ActionDispatch::Notifications') middleware.use('ActionDispatch::Callbacks', lambda { ActionController::Dispatcher.prepare_each_request }) middleware.use('ActionDispatch::Cookies') middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options }) diff --git a/railties/lib/rails/subscriber.rb b/railties/lib/rails/subscriber.rb index e8d13babf06ee..2674bf003efe7 100644 --- a/railties/lib/rails/subscriber.rb +++ b/railties/lib/rails/subscriber.rb @@ -66,7 +66,7 @@ def self.dispatch(args) subscriber.send(name, ActiveSupport::Notifications::Event.new(*args)) end - if args[0] == "action_dispatch.callback" && !subscribers.empty? + if args[0] == "action_dispatch.after_dispatch" && !subscribers.empty? flush_all! log_tailer.tail! if log_tailer end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index f2bfeb79fe497..1c5cc62ecd118 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -18,6 +18,7 @@ def setup "Rack::Lock", "Rack::Runtime", "ActionDispatch::ShowExceptions", + "ActionDispatch::Notifications", "ActionDispatch::Callbacks", "ActionDispatch::Cookies", "ActionDispatch::Session::CookieStore", diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb index 1eb0777db83fc..db8605edbe273 100644 --- a/railties/test/application/notifications_test.rb +++ b/railties/test/application/notifications_test.rb @@ -64,7 +64,7 @@ def wait ActiveRecord::Base.logger = logger = MockLogger.new - # Mimic an ActiveRecord notifications + # Mimic ActiveRecord notifications instrument "active_record.sql", :name => "SQL", :sql => "SHOW tables" wait diff --git a/railties/test/subscriber_test.rb b/railties/test/subscriber_test.rb index ac349395103e5..fa3f7bfabbdf3 100644 --- a/railties/test/subscriber_test.rb +++ b/railties/test/subscriber_test.rb @@ -92,7 +92,7 @@ def test_flushes_loggers def test_flushes_loggers_when_action_dispatch_callback_is_received Rails::Subscriber.add :my_subscriber, @subscriber - instrument "action_dispatch.callback" + instrument "action_dispatch.after_dispatch" wait assert_equal 1, @logger.flush_count end @@ -100,7 +100,7 @@ def test_flushes_loggers_when_action_dispatch_callback_is_received def test_flushes_the_same_logger_just_once Rails::Subscriber.add :my_subscriber, @subscriber Rails::Subscriber.add :another, @subscriber - instrument "action_dispatch.callback" + instrument "action_dispatch.after_dispatch" wait assert_equal 1, @logger.flush_count end @@ -111,7 +111,7 @@ def test_tails_logs_when_action_dispatch_callback_is_received Rails::Subscriber.log_tailer = log_tailer Rails::Subscriber.add :my_subscriber, @subscriber - instrument "action_dispatch.callback" + instrument "action_dispatch.after_dispatch" wait ensure Rails::Subscriber.log_tailer = nil From 61ada28ed3cdc42efa6f9714e909231959b88c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Jan 2010 11:44:04 +0100 Subject: [PATCH 30/82] Get rid of prepare_each_request, since now it's a middleware initialization parameter. --- actionpack/lib/action_controller/deprecated/dispatcher.rb | 3 --- actionpack/lib/action_controller/railtie.rb | 1 - railties/lib/rails/configuration.rb | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/actionpack/lib/action_controller/deprecated/dispatcher.rb b/actionpack/lib/action_controller/deprecated/dispatcher.rb index 3da3c8ce7dd23..1a70d2ee4da19 100644 --- a/actionpack/lib/action_controller/deprecated/dispatcher.rb +++ b/actionpack/lib/action_controller/deprecated/dispatcher.rb @@ -1,8 +1,5 @@ module ActionController class Dispatcher - cattr_accessor :prepare_each_request - self.prepare_each_request = false - class << self def before_dispatch(*args, &block) ActiveSupport::Deprecation.warn "ActionController::Dispatcher.before_dispatch is deprecated. " << diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index f070b13dbf2fd..d93155e7a5208 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -73,7 +73,6 @@ def name # TODO: This used to say unless defined?(Dispatcher). Find out why and fix. # Notice that at this point, ActionDispatch::Callbacks were already loaded. require 'rails/dispatcher' - ActionController::Dispatcher.prepare_each_request = true unless app.config.cache_classes unless app.config.cache_classes # Setup dev mode route reloading diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 72bebe085d62a..c3e1120fa3a81 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -15,7 +15,7 @@ def self.default_middleware_stack middleware.use('::Rack::Runtime') middleware.use('ActionDispatch::ShowExceptions', lambda { ActionController::Base.consider_all_requests_local }) middleware.use('ActionDispatch::Notifications') - middleware.use('ActionDispatch::Callbacks', lambda { ActionController::Dispatcher.prepare_each_request }) + middleware.use('ActionDispatch::Callbacks', lambda { !Rails.application.config.cache_classes }) middleware.use('ActionDispatch::Cookies') middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options }) middleware.use('ActionDispatch::Flash', :if => lambda { ActionController::Base.session_store }) From 27d9836ad3519d57462b86a4e539c1aa176b6d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Jan 2010 12:41:55 +0100 Subject: [PATCH 31/82] Add ActionDispatch::Railties::Subscriber and finish tidying up the logging. --- .../metal/instrumentation.rb | 4 - actionpack/lib/action_controller/railtie.rb | 48 -------- .../action_controller/railties/subscriber.rb | 7 +- actionpack/lib/action_dispatch/railtie.rb | 58 +++++++++ .../action_dispatch/railties/subscriber.rb | 17 +++ .../activerecord/controller_runtime_test.rb | 4 +- actionpack/test/controller/subscriber_test.rb | 42 +++---- .../test/dispatch/notifications_test.rb | 69 ----------- actionpack/test/dispatch/subscriber_test.rb | 112 ++++++++++++++++++ .../lib/active_record/railties/subscriber.rb | 2 +- railties/lib/rails.rb | 5 +- 11 files changed, 208 insertions(+), 160 deletions(-) create mode 100644 actionpack/lib/action_dispatch/railtie.rb create mode 100644 actionpack/lib/action_dispatch/railties/subscriber.rb delete mode 100644 actionpack/test/dispatch/notifications_test.rb create mode 100644 actionpack/test/dispatch/subscriber_test.rb diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 7222b7b2fa561..876f778751c80 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -20,11 +20,7 @@ def process_action(action, *args) result = super payload[:controller] = self.class.name payload[:action] = self.action_name - payload[:formats] = request.formats.map(&:to_s) - payload[:remote_ip] = request.remote_ip - payload[:method] = request.method payload[:status] = response.status - payload[:request_uri] = request.request_uri rescue "unknown" append_info_to_payload(payload) result end diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index d93155e7a5208..7ea64c19236a8 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -39,53 +39,5 @@ class Railtie < Rails::Railtie ActionController::Base.view_paths = view_path if ActionController::Base.view_paths.blank? end - class MetalMiddlewareBuilder - def initialize(metals) - @metals = metals - end - - def new(app) - ActionDispatch::Cascade.new(@metals, app) - end - - def name - ActionDispatch::Cascade.name - end - alias_method :to_s, :name - end - - initializer "action_controller.initialize_metal" do |app| - metal_root = "#{Rails.root}/app/metal" - load_list = app.config.metals || Dir["#{metal_root}/**/*.rb"] - - metals = load_list.map { |metal| - metal = File.basename(metal.gsub("#{metal_root}/", ''), '.rb') - require_dependency metal - metal.camelize.constantize - }.compact - - middleware = MetalMiddlewareBuilder.new(metals) - app.config.middleware.insert_before(:"ActionDispatch::ParamsParser", middleware) - end - - # Prepare dispatcher callbacks and run 'prepare' callbacks - initializer "action_controller.prepare_dispatcher" do |app| - # TODO: This used to say unless defined?(Dispatcher). Find out why and fix. - # Notice that at this point, ActionDispatch::Callbacks were already loaded. - require 'rails/dispatcher' - - unless app.config.cache_classes - # Setup dev mode route reloading - routes_last_modified = app.routes_changed_at - reload_routes = lambda do - unless app.routes_changed_at == routes_last_modified - routes_last_modified = app.routes_changed_at - app.reload_routes! - end - end - ActionDispatch::Callbacks.before { |callbacks| reload_routes.call } - end - end - end end diff --git a/actionpack/lib/action_controller/railties/subscriber.rb b/actionpack/lib/action_controller/railties/subscriber.rb index a9f5d16c58a88..6659e5df4703f 100644 --- a/actionpack/lib/action_controller/railties/subscriber.rb +++ b/actionpack/lib/action_controller/railties/subscriber.rb @@ -3,18 +3,13 @@ module Railties class Subscriber < Rails::Subscriber def process_action(event) payload = event.payload - - info "\nProcessed #{payload[:controller]}##{payload[:action]} " \ - "to #{payload[:formats].join(', ')} (for #{payload[:remote_ip]} at #{event.time.to_s(:db)}) " \ - "[#{payload[:method].to_s.upcase}]" - info " Parameters: #{payload[:params].inspect}" unless payload[:params].blank? additions = ActionController::Base.log_process_action(payload) message = "Completed in %.0fms" % event.duration message << " (#{additions.join(" | ")})" unless additions.blank? - message << " | #{payload[:status]} [#{payload[:request_uri]}]\n\n" + message << " by #{payload[:controller]}##{payload[:action]} [#{payload[:status]}]" info(message) end diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb new file mode 100644 index 0000000000000..9fd034cdd2bca --- /dev/null +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -0,0 +1,58 @@ +require "action_dispatch" +require "rails" + +module ActionDispatch + class Railtie < Rails::Railtie + plugin_name :action_dispatch + + require "action_dispatch/railties/subscriber" + subscriber ActionDispatch::Railties::Subscriber.new + + class MetalMiddlewareBuilder + def initialize(metals) + @metals = metals + end + + def new(app) + ActionDispatch::Cascade.new(@metals, app) + end + + def name + ActionDispatch::Cascade.name + end + alias_method :to_s, :name + end + + initializer "action_dispatch.initialize_metal" do |app| + metal_root = "#{Rails.root}/app/metal" + load_list = app.config.metals || Dir["#{metal_root}/**/*.rb"] + + metals = load_list.map { |metal| + metal = File.basename(metal.gsub("#{metal_root}/", ''), '.rb') + require_dependency metal + metal.camelize.constantize + }.compact + + middleware = MetalMiddlewareBuilder.new(metals) + app.config.middleware.insert_before(:"ActionDispatch::ParamsParser", middleware) + end + + # Prepare dispatcher callbacks and run 'prepare' callbacks + initializer "action_dispatch.prepare_dispatcher" do |app| + # TODO: This used to say unless defined?(Dispatcher). Find out why and fix. + require 'rails/dispatcher' + + unless app.config.cache_classes + # Setup dev mode route reloading + routes_last_modified = app.routes_changed_at + reload_routes = lambda do + unless app.routes_changed_at == routes_last_modified + routes_last_modified = app.routes_changed_at + app.reload_routes! + end + end + ActionDispatch::Callbacks.before { |callbacks| reload_routes.call } + end + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_dispatch/railties/subscriber.rb b/actionpack/lib/action_dispatch/railties/subscriber.rb new file mode 100644 index 0000000000000..c08a844c6a021 --- /dev/null +++ b/actionpack/lib/action_dispatch/railties/subscriber.rb @@ -0,0 +1,17 @@ +module ActionDispatch + module Railties + class Subscriber < Rails::Subscriber + def before_dispatch(event) + request = Request.new(event.payload[:env]) + path = request.request_uri.inspect rescue "unknown" + + info "\n\nProcessing #{path} to #{request.formats.join(', ')} " << + "(for #{request.remote_ip} at #{event.time.to_s(:db)}) [#{request.method.to_s.upcase}]" + end + + def logger + ActionController::Base.logger + end + end + end +end \ No newline at end of file diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionpack/test/activerecord/controller_runtime_test.rb index 37c7738301e8b..d6f7cd80ab46c 100644 --- a/actionpack/test/activerecord/controller_runtime_test.rb +++ b/actionpack/test/activerecord/controller_runtime_test.rb @@ -37,8 +37,8 @@ def test_log_with_active_record get :show wait - assert_equal 2, @logger.logged(:info).size - assert_match /\(Views: [\d\.]+ms | ActiveRecord: [\d\.]+ms\)/, @logger.logged(:info)[1] + assert_equal 1, @logger.logged(:info).size + assert_match /\(Views: [\d\.]+ms | ActiveRecord: [\d\.]+ms\)/, @logger.logged(:info)[0] end class SyncSubscriberTest < ActionController::TestCase diff --git a/actionpack/test/controller/subscriber_test.rb b/actionpack/test/controller/subscriber_test.rb index ef1a3257998d4..24132ee92869f 100644 --- a/actionpack/test/controller/subscriber_test.rb +++ b/actionpack/test/controller/subscriber_test.rb @@ -66,15 +66,10 @@ def set_logger(logger) def test_process_action get :show wait - assert_equal 2, logs.size - assert_match /Processed\sAnother::SubscribersController#show/, logs[0] - end - - def test_process_action_formats - get :show - wait - assert_equal 2, logs.size - assert_match /text\/html/, logs[0] + assert_equal 1, logs.size + assert_match /Completed/, logs.first + assert_match /\[200\]/, logs.first + assert_match /Another::SubscribersController#show/, logs.first end def test_process_action_without_parameters @@ -87,23 +82,14 @@ def test_process_action_with_parameters get :show, :id => '10' wait - assert_equal 3, logs.size - assert_equal 'Parameters: {"id"=>"10"}', logs[1] + assert_equal 2, logs.size + assert_equal 'Parameters: {"id"=>"10"}', logs[0] end def test_process_action_with_view_runtime get :show wait - assert_match /\(Views: [\d\.]+ms\)/, logs[1] - end - - def test_process_action_with_status_and_request_uri - get :show - wait - last = logs.last - assert_match /Completed/, last - assert_match /200/, last - assert_match /another\/subscribers\/show/, last + assert_match /\(Views: [\d\.]+ms\)/, logs[0] end def test_process_action_with_filter_parameters @@ -112,7 +98,7 @@ def test_process_action_with_filter_parameters get :show, :lifo => 'Pratik', :amount => '420', :step => '1' wait - params = logs[1] + params = logs[0] assert_match /"amount"=>"\[FILTERED\]"/, params assert_match /"lifo"=>"\[FILTERED\]"/, params assert_match /"step"=>"1"/, params @@ -122,7 +108,7 @@ def test_redirect_to get :redirector wait - assert_equal 3, logs.size + assert_equal 2, logs.size assert_equal "Redirected to http://foo.bar/", logs[0] end @@ -130,7 +116,7 @@ def test_send_data get :data_sender wait - assert_equal 3, logs.size + assert_equal 2, logs.size assert_match /Sent data omg\.txt/, logs[0] end @@ -138,7 +124,7 @@ def test_send_file get :file_sender wait - assert_equal 3, logs.size + assert_equal 2, logs.size assert_match /Sent file/, logs[0] assert_match /test\/fixtures\/company\.rb/, logs[0] end @@ -147,7 +133,7 @@ def test_send_xfile get :xfile_sender wait - assert_equal 3, logs.size + assert_equal 2, logs.size assert_match /Sent X\-Sendfile header/, logs[0] assert_match /test\/fixtures\/company\.rb/, logs[0] end @@ -157,7 +143,7 @@ def test_with_fragment_cache get :with_fragment_cache wait - assert_equal 4, logs.size + assert_equal 3, logs.size assert_match /Exist fragment\? views\/foo/, logs[0] assert_match /Write fragment views\/foo/, logs[1] ensure @@ -169,7 +155,7 @@ def test_with_page_cache get :with_page_cache wait - assert_equal 3, logs.size + assert_equal 2, logs.size assert_match /Write page/, logs[0] assert_match /\/index\.html/, logs[0] ensure diff --git a/actionpack/test/dispatch/notifications_test.rb b/actionpack/test/dispatch/notifications_test.rb deleted file mode 100644 index d834a734ef8c1..0000000000000 --- a/actionpack/test/dispatch/notifications_test.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'abstract_unit' - -class NotificationsMiddlewareTest < ActionController::IntegrationTest - Boomer = lambda do |env| - req = ActionDispatch::Request.new(env) - case req.path - when "/" - [200, {}, []] - else - raise "puke!" - end - end - - App = ActionDispatch::Notifications.new(Boomer) - - def setup - @queue = ActiveSupport::Notifications::Fanout.new - @notifier = ActiveSupport::Notifications::Notifier.new(@queue) - ActiveSupport::Notifications.notifier = @notifier - - @events = [] - ActiveSupport::Notifications.subscribe do |*args| - @events << args - end - - @app = App - end - - test "publishes notifications" do - get "/" - ActiveSupport::Notifications.notifier.wait - - assert_equal 2, @events.size - before, after = @events - - assert_equal 'action_dispatch.before_dispatch', before[0] - assert_kind_of Hash, before[4][:env] - assert_equal 'GET', before[4][:env]["REQUEST_METHOD"] - - assert_equal 'action_dispatch.after_dispatch', after[0] - assert_kind_of Hash, after[4][:env] - assert_equal 'GET', after[4][:env]["REQUEST_METHOD"] - end - - test "publishes notifications on failure" do - begin - get "/puke" - rescue - end - - ActiveSupport::Notifications.notifier.wait - - assert_equal 3, @events.size - before, after, exception = @events - - assert_equal 'action_dispatch.before_dispatch', before[0] - assert_kind_of Hash, before[4][:env] - assert_equal 'GET', before[4][:env]["REQUEST_METHOD"] - - assert_equal 'action_dispatch.after_dispatch', after[0] - assert_kind_of Hash, after[4][:env] - assert_equal 'GET', after[4][:env]["REQUEST_METHOD"] - - assert_equal 'action_dispatch.exception', exception[0] - assert_kind_of Hash, exception[4][:env] - assert_equal 'GET', exception[4][:env]["REQUEST_METHOD"] - assert_kind_of RuntimeError, exception[4][:exception] - end -end \ No newline at end of file diff --git a/actionpack/test/dispatch/subscriber_test.rb b/actionpack/test/dispatch/subscriber_test.rb new file mode 100644 index 0000000000000..a7f1a2659ad02 --- /dev/null +++ b/actionpack/test/dispatch/subscriber_test.rb @@ -0,0 +1,112 @@ +require "abstract_unit" +require "rails/subscriber/test_helper" +require "action_dispatch/railties/subscriber" + +module DispatcherSubscriberTest + Boomer = lambda do |env| + req = ActionDispatch::Request.new(env) + case req.path + when "/" + [200, {}, []] + else + raise "puke!" + end + end + + App = ActionDispatch::Notifications.new(Boomer) + + def setup + Rails::Subscriber.add(:action_dispatch, ActionDispatch::Railties::Subscriber.new) + @app = App + super + + @events = [] + ActiveSupport::Notifications.subscribe do |*args| + @events << args + end + end + + def set_logger(logger) + ActionController::Base.logger = logger + end + + def test_publishes_notifications + get "/" + wait + + assert_equal 2, @events.size + before, after = @events + + assert_equal 'action_dispatch.before_dispatch', before[0] + assert_kind_of Hash, before[4][:env] + assert_equal 'GET', before[4][:env]["REQUEST_METHOD"] + + assert_equal 'action_dispatch.after_dispatch', after[0] + assert_kind_of Hash, after[4][:env] + assert_equal 'GET', after[4][:env]["REQUEST_METHOD"] + end + + def test_publishes_notifications_even_on_failures + begin + get "/puke" + rescue + end + + wait + + assert_equal 3, @events.size + before, after, exception = @events + + assert_equal 'action_dispatch.before_dispatch', before[0] + assert_kind_of Hash, before[4][:env] + assert_equal 'GET', before[4][:env]["REQUEST_METHOD"] + + assert_equal 'action_dispatch.after_dispatch', after[0] + assert_kind_of Hash, after[4][:env] + assert_equal 'GET', after[4][:env]["REQUEST_METHOD"] + + assert_equal 'action_dispatch.exception', exception[0] + assert_kind_of Hash, exception[4][:env] + assert_equal 'GET', exception[4][:env]["REQUEST_METHOD"] + assert_kind_of RuntimeError, exception[4][:exception] + end + + def test_subscriber_logs_notifications + get "/" + wait + + log = @logger.logged(:info).first + assert_equal 1, @logger.logged(:info).size + + assert_match %r{^Processing "/" to text/html}, log + assert_match %r{\(for 127\.0\.0\.1}, log + assert_match %r{\[GET\]}, log + end + + def test_subscriber_has_its_logged_flushed_after_request + assert_equal 0, @logger.flush_count + get "/" + wait + assert_equal 1, @logger.flush_count + end + + def test_subscriber_has_its_logged_flushed_even_after_busted_requests + assert_equal 0, @logger.flush_count + begin + get "/puke" + rescue + end + wait + assert_equal 1, @logger.flush_count + end + + class SyncSubscriberTest < ActionController::IntegrationTest + include Rails::Subscriber::SyncTestHelper + include DispatcherSubscriberTest + end + + class AsyncSubscriberTest < ActionController::IntegrationTest + include Rails::Subscriber::AsyncTestHelper + include DispatcherSubscriberTest + end +end \ No newline at end of file diff --git a/activerecord/lib/active_record/railties/subscriber.rb b/activerecord/lib/active_record/railties/subscriber.rb index 7c2a10cf0f954..fd873dbff8ba0 100644 --- a/activerecord/lib/active_record/railties/subscriber.rb +++ b/activerecord/lib/active_record/railties/subscriber.rb @@ -12,7 +12,7 @@ def sql(event) name = color(name, :magenta, true) end - debug "#{name} #{sql}" + debug " #{name} #{sql}" end def odd? diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index 4ded2515fcd49..0bc71608159f1 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -1,9 +1,8 @@ -require "pathname" +require 'pathname' require 'active_support' require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/logger' -require 'action_dispatch' require 'rails/initializable' require 'rails/application' @@ -18,6 +17,8 @@ require 'rails/subscriber' require 'rails/ruby_version_check' +require 'action_dispatch/railtie' + # For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the # multibyte safe operations. Plugin authors supporting other encodings # should override this behaviour and set the relevant +default_charset+ From 020e656447a7cc2ce9cbf83483ab3b31730a565e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Jan 2010 13:23:14 +0100 Subject: [PATCH 32/82] Move middleware builder back to Rails::Rack::Metal without losing the new behavior. --- actionpack/lib/action_dispatch/railtie.rb | 29 ----------------------- railties/lib/rails/configuration.rb | 1 + railties/lib/rails/rack.rb | 5 ++-- railties/lib/rails/rack/metal.rb | 26 ++++++++++++++++++++ 4 files changed, 30 insertions(+), 31 deletions(-) create mode 100644 railties/lib/rails/rack/metal.rb diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 9fd034cdd2bca..18978bfb39ad2 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -8,35 +8,6 @@ class Railtie < Rails::Railtie require "action_dispatch/railties/subscriber" subscriber ActionDispatch::Railties::Subscriber.new - class MetalMiddlewareBuilder - def initialize(metals) - @metals = metals - end - - def new(app) - ActionDispatch::Cascade.new(@metals, app) - end - - def name - ActionDispatch::Cascade.name - end - alias_method :to_s, :name - end - - initializer "action_dispatch.initialize_metal" do |app| - metal_root = "#{Rails.root}/app/metal" - load_list = app.config.metals || Dir["#{metal_root}/**/*.rb"] - - metals = load_list.map { |metal| - metal = File.basename(metal.gsub("#{metal_root}/", ''), '.rb') - require_dependency metal - metal.camelize.constantize - }.compact - - middleware = MetalMiddlewareBuilder.new(metals) - app.config.middleware.insert_before(:"ActionDispatch::ParamsParser", middleware) - end - # Prepare dispatcher callbacks and run 'prepare' callbacks initializer "action_dispatch.prepare_dispatcher" do |app| # TODO: This used to say unless defined?(Dispatcher). Find out why and fix. diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index c3e1120fa3a81..45e2e521ef2b7 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -19,6 +19,7 @@ def self.default_middleware_stack middleware.use('ActionDispatch::Cookies') middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options }) middleware.use('ActionDispatch::Flash', :if => lambda { ActionController::Base.session_store }) + middleware.use(lambda { Rails::Rack::Metal.new(Rails.application.config.paths.app.metals.to_a, Rails.application.config.metals) }) middleware.use('ActionDispatch::ParamsParser') middleware.use('::Rack::MethodOverride') middleware.use('::ActionDispatch::Head') diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb index d487bd0542e5e..36945a6b0fd2d 100644 --- a/railties/lib/rails/rack.rb +++ b/railties/lib/rails/rack.rb @@ -1,7 +1,8 @@ module Rails module Rack - autoload :Debugger, "rails/rack/debugger" + autoload :Debugger, "rails/rack/debugger" autoload :LogTailer, "rails/rack/log_tailer" - autoload :Static, "rails/rack/static" + autoload :Metal, "rails/rack/metal" + autoload :Static, "rails/rack/static" end end diff --git a/railties/lib/rails/rack/metal.rb b/railties/lib/rails/rack/metal.rb new file mode 100644 index 0000000000000..565f95d7c4fa4 --- /dev/null +++ b/railties/lib/rails/rack/metal.rb @@ -0,0 +1,26 @@ +require 'action_dispatch' + +module Rails + module Rack + class Metal + def initialize(metal_roots, metals=nil) + load_list = metals || Dir["{#{metal_roots.join(",")}}/**/*.rb"] + + @metals = load_list.map { |metal| + metal = File.basename(metal, '.rb') + require_dependency metal + metal.camelize.constantize + }.compact + end + + def new(app) + ActionDispatch::Cascade.new(@metals, app) + end + + def name + ActionDispatch::Cascade.name + end + alias_method :to_s, :name + end + end +end From c0d31ca41b2f019d3bf940ac79f104c412b115bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Jan 2010 14:22:27 +0100 Subject: [PATCH 33/82] save(false) is gone, use save(:validate => false) instead. --- .../has_and_belongs_to_many_association.rb | 2 +- .../associations/has_many_association.rb | 2 +- .../associations/has_many_through_association.rb | 2 +- .../lib/active_record/autosave_association.rb | 10 +++++----- activerecord/lib/active_record/base.rb | 10 +++++----- activerecord/lib/active_record/transactions.rb | 4 ++-- activerecord/lib/active_record/validations.rb | 12 +++++++++++- .../test/cases/autosave_association_test.rb | 12 ++++++------ activerecord/test/cases/validations_test.rb | 10 +++++++++- activeresource/lib/active_resource/validations.rb | 15 ++++++++++++--- activeresource/test/cases/validations_test.rb | 13 +++++++++++++ 11 files changed, 66 insertions(+), 26 deletions(-) diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index bd05d1014c744..7f39a189e4947 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -37,7 +37,7 @@ def insert_record(record, force = true, validate = true) if force record.save! else - return false unless record.save(validate) + return false unless record.save(:validate => validate) end end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index d3336cf2d2cff..146a6ca55fe9c 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -58,7 +58,7 @@ def cached_counter_attribute_name def insert_record(record, force = false, validate = true) set_belongs_to_association_for(record) - force ? record.save! : record.save(validate) + force ? record.save! : record.save(:validate => validate) end # Deletes the records according to the :dependent option. diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 387b85aacd110..bd2acd43409ca 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -60,7 +60,7 @@ def insert_record(record, force = true, validate = true) if force record.save! else - return false unless record.save(validate) + return false unless record.save(:validate => validate) end end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index e178cb4ef296c..325a8aa7eca26 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -116,14 +116,14 @@ module ActiveRecord # post = Post.find(1) # post.author.name = '' # post.save # => false - # post.errors # => #["can't be blank"]}, @base=#> + # post.errors # => #["can't be blank"]}, @base=#> # # No validations will be performed on the associated models when validations # are skipped for the parent: # # post = Post.find(1) # post.author.name = '' - # post.save(false) # => true + # post.save(:validate => false) # => true module AutosaveAssociation extend ActiveSupport::Concern @@ -302,7 +302,7 @@ def save_collection_association(reflection) association.send(:insert_record, record) end elsif autosave - saved = record.save(false) + saved = record.save(:validate => false) end raise ActiveRecord::Rollback if saved == false @@ -332,7 +332,7 @@ def save_has_one_association(reflection) key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave) association[reflection.primary_key_name] = key - saved = association.save(!autosave) + saved = association.save(:validate => !autosave) raise ActiveRecord::Rollback if !saved && autosave saved end @@ -355,7 +355,7 @@ def save_belongs_to_association(reflection) if autosave && association.marked_for_destruction? association.destroy elsif autosave != false - saved = association.save(!autosave) if association.new_record? || autosave + saved = association.save(:validate => !autosave) if association.new_record? || autosave if association.updated? association_id = association.send(reflection.options[:primary_key] || :id) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 4ee988718637d..06244d1132ad9 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2136,16 +2136,16 @@ def destroyed? end # :call-seq: - # save(perform_validation = true) + # save(options) # # Saves the model. # # If the model is new a record gets created in the database, otherwise # the existing record gets updated. # - # If +perform_validation+ is true validations run. If any of them fail - # the action is cancelled and +save+ returns +false+. If the flag is - # false validations are bypassed altogether. See + # By default, save always run validations. If any of them fail the action + # is cancelled and +save+ returns +false+. However, if you supply + # :validate => false, validations are bypassed altogether. See # ActiveRecord::Validations for more information. # # There's a series of callbacks associated with +save+. If any of the @@ -2220,7 +2220,7 @@ def becomes(klass) # in Base is replaced with this when the validations module is mixed in, which it is by default. def update_attribute(name, value) send(name.to_s + '=', value) - save(false) + save(:validate => false) end # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 4f8ccdd40e3ca..cf0fe8934d31a 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -192,8 +192,8 @@ def destroy_with_transactions #:nodoc: with_transaction_returning_status(:destroy_without_transactions) end - def save_with_transactions(perform_validation = true) #:nodoc: - rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, perform_validation) } + def save_with_transactions(*args) #:nodoc: + rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, *args) } end def save_with_transactions! #:nodoc: diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index d5adcba3ba0b2..a9743aa1ea166 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -42,7 +42,17 @@ def create!(attributes = nil, &block) module InstanceMethods # The validation process on save can be skipped by passing false. The regular Base#save method is # replaced with this when the validations module is mixed in, which it is by default. - def save_with_validation(perform_validation = true) + def save_with_validation(options=nil) + perform_validation = case options + when NilClass + true + when Hash + options[:validate] != false + else + ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller + options + end + if perform_validation && valid? || !perform_validation save_without_validation else diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index cc36a6dc5ba76..cc5460ddb7548 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -805,7 +805,7 @@ def test_should_not_ignore_different_error_messages_on_the_same_attribute def test_should_still_allow_to_bypass_validations_on_the_associated_model @pirate.catchphrase = '' @pirate.ship.name = '' - @pirate.save(false) + @pirate.save(:validate => false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil], [@pirate.reload.catchphrase, @pirate.ship.name] @@ -820,7 +820,7 @@ def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth @pirate.catchphrase = '' @pirate.ship.name = '' @pirate.ship.parts.each { |part| part.name = '' } - @pirate.save(false) + @pirate.save(:validate => false) values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)] # Oracle saves empty string as NULL @@ -917,7 +917,7 @@ def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_ def test_should_still_allow_to_bypass_validations_on_the_associated_model @ship.pirate.catchphrase = '' @ship.name = '' - @ship.save(false) + @ship.save(:validate => false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase] @@ -1029,7 +1029,7 @@ def test_should_allow_to_bypass_validations_on_the_associated_models_on_update @pirate.catchphrase = '' @pirate.send(@association_name).each { |child| child.name = '' } - assert @pirate.save(false) + assert @pirate.save(:validate => false) # Oracle saves empty string as NULL if current_adapter?(:OracleAdapter) assert_equal [nil, nil, nil], [ @@ -1049,14 +1049,14 @@ def test_should_allow_to_bypass_validations_on_the_associated_models_on_update def test_should_validation_the_associated_models_on_create assert_no_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count") do 2.times { @pirate.send(@association_name).build } - @pirate.save(true) + @pirate.save end end def test_should_allow_to_bypass_validations_on_the_associated_models_on_create assert_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count", +2) do 2.times { @pirate.send(@association_name).build } - @pirate.save(false) + @pirate.save(:validate => false) end end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 3a1d5ae212f77..8314f880be7e5 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -124,7 +124,15 @@ def test_create_with_exceptions_using_scope_and_empty_attributes def test_create_without_validation reply = WrongReply.new assert !reply.save - assert reply.save(false) + assert reply.save(:validate => false) + end + + def test_deprecated_create_without_validation + reply = WrongReply.new + assert !reply.save + assert_deprecated do + assert reply.save(false) + end end def test_create_without_validation_bang diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb index 67b69fa50564c..7b2382bd8cd4b 100644 --- a/activeresource/lib/active_resource/validations.rb +++ b/activeresource/lib/active_resource/validations.rb @@ -58,9 +58,8 @@ def from_xml(xml, save_cache = false) # person.save # => true (and person is now saved to the remote service) # module Validations - extend ActiveSupport::Concern + extend ActiveSupport::Concern include ActiveModel::Validations - extend ActiveModel::Validations::ClassMethods included do alias_method_chain :save, :validation @@ -68,7 +67,17 @@ module Validations # Validate a resource and save (POST) it to the remote web service. # If any local validations fail - the save (POST) will not be attempted. - def save_with_validation(perform_validation = true) + def save_with_validation(options=nil) + perform_validation = case options + when Hash + options[:validate] != false + when NilClass + true + else + ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller + options + end + # clear the remote validations so they don't interfere with the local # ones. Otherwise we get an endless loop and can never change the # fields so as to make the resource valid diff --git a/activeresource/test/cases/validations_test.rb b/activeresource/test/cases/validations_test.rb index c05f625fb7a4e..82546424f2a0f 100644 --- a/activeresource/test/cases/validations_test.rb +++ b/activeresource/test/cases/validations_test.rb @@ -30,6 +30,19 @@ def test_fails_save! assert_raise(ActiveResource::ResourceInvalid) { p.save! } end + def test_save_without_validation + p = new_project(:name => nil) + assert !p.save + assert p.save(:validate => false) + end + + def test_deprecated_save_without_validation + p = new_project(:name => nil) + assert !p.save + assert_deprecated do + assert p.save(false) + end + end def test_validate_callback # we have a callback ensuring the description is longer than three letters From 31ea83eb898786f853bd7ce7075986fa120f949d Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Sun, 17 Jan 2010 12:15:52 +0100 Subject: [PATCH 34/82] Adding Proc support to validation messages so that they can become a little more dynamic, allowing for customisations during the request [#3514 status:resolved]. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activemodel/lib/active_model/errors.rb | 2 ++ activemodel/test/cases/validations_test.rb | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 0b6c75c46ebe3..2e5bcab0706ec 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -60,9 +60,11 @@ def to_xml(options={}) # error can be added to the same +attribute+ in which case an array will be returned on a call to on(attribute). # If no +messsage+ is supplied, :invalid is assumed. # If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error). + # If +message+ is a Proc, it will be called, allowing for things like Time.now to be used within an error def add(attribute, message = nil, options = {}) message ||= :invalid message = generate_message(attribute, message, options) if message.is_a?(Symbol) + message = message.call if message.is_a?(Proc) self[attribute] << message end diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index 38a2a716a73fc..eb100d1c3528a 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -212,4 +212,12 @@ def test_deprecated_errors_on_base_and_each all_errors = t.errors.to_a assert_deprecated { assert_equal all_errors, t.errors.each_full{|err| err} } end + + def test_validation_with_message_as_proc + Topic.validates_presence_of(:title, :message => proc { "no blanks here".upcase }) + + t = Topic.new + assert !t.valid? + assert ["NO BLANKS HERE"], t.errors[:title] + end end From 8268d68b509cc9da1f6fe14dc61430f781222fd0 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 16 Jan 2010 16:26:53 -0200 Subject: [PATCH 35/82] mail.create_path returns an array test fixed [#3712 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionmailer/test/mail_service_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index cd41739f1a7ed..03bd0c238c50c 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -1078,7 +1078,7 @@ def test_return_path_with_create def test_return_path_with_create mail = TestMailer.create_return_path - assert_equal "another@somewhere.test", mail.return_path + assert_equal ["another@somewhere.test"], mail.return_path end def test_return_path_with_deliver From cf109342a710e64fbaabc97c90e4af84f7c20a32 Mon Sep 17 00:00:00 2001 From: Rizwan Reza Date: Sat, 16 Jan 2010 23:05:39 +0330 Subject: [PATCH 36/82] Fixes rake:template task [#3714 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- railties/lib/rails/tasks/framework.rake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index f7b53885c84a6..93ba309039caf 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -85,8 +85,9 @@ namespace :rails do template = ENV["LOCATION"] template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} - require 'generators' - generator = Rails::Generators::App.new [ Rails.root ], {}, :destination_root => Rails.root + require 'rails/generators' + require 'rails/generators/rails/app/app_generator' + generator = Rails::Generators::AppGenerator.new [ Rails.root ], {}, :destination_root => Rails.root generator.apply template, :verbose => false end From e3898bda9cb0e6d7e63cd8f6d4ecec96209bfd4d Mon Sep 17 00:00:00 2001 From: Rizwan Reza Date: Sun, 17 Jan 2010 02:04:38 +0330 Subject: [PATCH 37/82] Take --skip-activerecord into account boot.rb requires. [#3711 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- .../lib/rails/generators/rails/app/app_generator.rb | 2 +- .../generators/rails/app/templates/config/boot.rb | 13 ++++++++++++- railties/test/generators/app_generator_test.rb | 9 ++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index d58d245168ddf..4b73576b07d97 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -87,7 +87,7 @@ def create_config_files end def create_boot_file - copy_file "config/boot.rb" + template "config/boot.rb" end def create_activerecord_files diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb index 6de1725260f87..466e1e50ec7c4 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb @@ -13,7 +13,9 @@ require 'rubygems' end +<% unless options[:skip_activerecord] -%> require 'rails/all' + # To pick the frameworks you want, remove 'require "rails/all"' # and list the framework railties that you want: # @@ -22,4 +24,13 @@ # require "action_controller/railtie" # require "action_view/railtie" # require "action_mailer/railtie" -# require "active_resource/railtie" \ No newline at end of file +# require "active_resource/railtie" +<% else -%> +# Pick the frameworks you want: +# require "active_record/railtie" +require "active_model/railtie" +require "action_controller/railtie" +require "action_view/railtie" +require "action_mailer/railtie" +require "active_resource/railtie" +<% end -%> \ No newline at end of file diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 62ea07f14e0f0..5fab233c8dcae 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -80,11 +80,10 @@ def test_config_database_is_not_added_if_skip_activerecord_is_given assert_no_file "config/database.yml" end - # TODO: Bring this back using requires - # def test_activerecord_is_removed_from_frameworks_if_skip_activerecord_is_given - # run_generator ["--skip-activerecord"] - # assert_file "config/application.rb", /config\.frameworks \-= \[ :active_record \]/ - # end + def test_activerecord_is_removed_from_frameworks_if_skip_activerecord_is_given + run_generator [destination_root, "--skip-activerecord"] + assert_file "config/boot.rb", /# require "active_record\/railtie"/ + end def test_prototype_and_test_unit_are_added_by_default run_generator From 58136e1bd359ce71275eb8e8e5c20d1c9b286b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Jan 2010 15:17:50 +0100 Subject: [PATCH 38/82] Plugin generator should use templates. --- .../templates/{MIT-LICENSE => MIT-LICENSE.tt} | 0 .../rails/plugin/templates/{README => README.tt} | 0 .../plugin/templates/{Rakefile => Rakefile.tt} | 0 railties/test/generators/plugin_generator_test.rb | 15 +++++++++++++++ 4 files changed, 15 insertions(+) rename railties/lib/rails/generators/rails/plugin/templates/{MIT-LICENSE => MIT-LICENSE.tt} (100%) rename railties/lib/rails/generators/rails/plugin/templates/{README => README.tt} (100%) rename railties/lib/rails/generators/rails/plugin/templates/{Rakefile => Rakefile.tt} (100%) diff --git a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt similarity index 100% rename from railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE rename to railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/README b/railties/lib/rails/generators/rails/plugin/templates/README.tt similarity index 100% rename from railties/lib/rails/generators/rails/plugin/templates/README rename to railties/lib/rails/generators/rails/plugin/templates/README.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt similarity index 100% rename from railties/lib/rails/generators/rails/plugin/templates/Rakefile rename to railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index f84b8b6d50db1..2989cdb67de1e 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -6,12 +6,27 @@ class PluginGeneratorTest < GeneratorsTestCase def test_plugin_skeleton_is_created run_generator + year = Date.today.year %w( vendor/plugins vendor/plugins/plugin_fu + vendor/plugins/plugin_fu/init.rb + vendor/plugins/plugin_fu/install.rb + vendor/plugins/plugin_fu/uninstall.rb vendor/plugins/plugin_fu/lib + vendor/plugins/plugin_fu/lib/plugin_fu.rb ).each{ |path| assert_file path } + + %w( + vendor/plugins/plugin_fu/README + vendor/plugins/plugin_fu/Rakefile + ).each{ |path| assert_file path, /PluginFu/ } + + %w( + vendor/plugins/plugin_fu/README + vendor/plugins/plugin_fu/MIT-LICENSE + ).each{ |path| assert_file path, /#{year}/ } end def test_check_class_collision From 44a10a2440640edae5a39a0be562093503dc7bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Jan 2010 16:18:14 +0100 Subject: [PATCH 39/82] Fix resources_path_names branch which was sending an array as entity. [#3715 status:resolved] --- .../lib/action_dispatch/routing/mapper.rb | 59 ++++++++----------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 9aaa4355f2640..811c287355683 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -418,28 +418,12 @@ def initialize(*args) def resource(*resources, &block) options = resources.extract_options! - if resources.length > 1 - raise ArgumentError if block_given? - resources.each { |r| resource(r, options) } - return self - end - - if path_names = options.delete(:path_names) - scope(:resources_path_names => path_names) do - resource(resources, options) - end + if verify_common_behavior_for(:resource, resources, options, &block) return self end resource = SingletonResource.new(resources.pop, options) - if @scope[:scope_level] == :resources - nested do - resource(resource.name, options, &block) - end - return self - end - scope(:path => resource.name.to_s, :controller => resource.controller) do with_scope_level(:resource, resource) do yield if block_given? @@ -459,28 +443,12 @@ def resource(*resources, &block) def resources(*resources, &block) options = resources.extract_options! - if resources.length > 1 - raise ArgumentError if block_given? - resources.each { |r| resources(r, options) } - return self - end - - if path_names = options.delete(:path_names) - scope(:resources_path_names => path_names) do - resources(resources, options) - end + if verify_common_behavior_for(:resources, resources, options, &block) return self end resource = Resource.new(resources.pop, options) - if @scope[:scope_level] == :resources - nested do - resources(resource.name, options, &block) - end - return self - end - scope(:path => resource.name.to_s, :controller => resource.controller) do with_scope_level(:resources, resource) do yield if block_given? @@ -595,6 +563,29 @@ def action_path(name, path_names = nil) path_names[name.to_sym] || name.to_s end + def verify_common_behavior_for(method, resources, options, &block) + if resources.length > 1 + resources.each { |r| send(method, r, options, &block) } + return true + end + + if path_names = options.delete(:path_names) + scope(:resources_path_names => path_names) do + send(method, resources.pop, options, &block) + end + return true + end + + if @scope[:scope_level] == :resources + nested do + send(method, resources.pop, options, &block) + end + return true + end + + false + end + def with_exclusive_name_prefix(prefix) begin old_name_prefix = @scope[:name_prefix] From a0dc6cc70b694349a592b42ec3b58001ce82fec9 Mon Sep 17 00:00:00 2001 From: Rizwan Reza Date: Sat, 16 Jan 2010 21:48:24 +0330 Subject: [PATCH 40/82] Fix RAILS_ROOT deprecation on guides generation. [#3710 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- railties/lib/rails/tasks/documentation.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index db1939c45f411..65d0d476ba839 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -55,7 +55,7 @@ namespace :doc do desc "Generate Rails guides" task :guides do require File.join(RAILTIES_PATH, "guides/rails_guides") - RailsGuides::Generator.new(File.join(RAILS_ROOT, "doc/guides")).generate + RailsGuides::Generator.new(Rails.root.join("doc/guides")).generate end namespace :plugins do From eb67532bc1c1ce5c494b71b980c04c8aa83efb74 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Sat, 16 Jan 2010 20:51:42 +0700 Subject: [PATCH 41/82] Make local_request? to returns true when facing ::1 IPv6 address [#3257 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- .../middleware/show_exceptions.rb | 4 ++-- .../test/dispatch/show_exceptions_test.rb | 22 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 0977f5c60ac5f..3bcd004e12bde 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -20,7 +20,7 @@ module ActionDispatch # * :exception - The exception raised; # class ShowExceptions - LOCALHOST = '127.0.0.1'.freeze + LOCALHOST = ['127.0.0.1', '::1'].freeze RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates') @@ -118,7 +118,7 @@ def rescue_action_in_public(exception) # True if the request came from localhost, 127.0.0.1. def local_request?(request) - request.remote_addr == LOCALHOST && request.remote_ip == LOCALHOST + LOCALHOST.any?{ |local_ip| request.remote_addr == local_ip && request.remote_ip == local_ip } end def status_code(exception) diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index 9f6a93756cc34..def86c83232a8 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -53,19 +53,21 @@ class ShowExceptionsTest < ActionController::IntegrationTest test "rescue locally from a local request" do @app = ProductionApp - self.remote_addr = '127.0.0.1' + ['127.0.0.1', '::1'].each do |ip_address| + self.remote_addr = ip_address - get "/" - assert_response 500 - assert_match /puke/, body + get "/" + assert_response 500 + assert_match /puke/, body - get "/not_found" - assert_response 404 - assert_match /#{ActionController::UnknownAction.name}/, body + get "/not_found" + assert_response 404 + assert_match /#{ActionController::UnknownAction.name}/, body - get "/method_not_allowed" - assert_response 405 - assert_match /ActionController::MethodNotAllowed/, body + get "/method_not_allowed" + assert_response 405 + assert_match /ActionController::MethodNotAllowed/, body + end end test "localize public rescue message" do From 5a6596787b9489c62d149b61935057cee3dcf61a Mon Sep 17 00:00:00 2001 From: Sam Elliott and Santiago Pastorino Date: Sun, 17 Jan 2010 01:39:30 -0200 Subject: [PATCH 42/82] Module lookup issue on flash_test using ruby 1.9 solved [#3716 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionpack/test/controller/flash_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 85a2e7f44be46..3c651ebebc63d 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -220,7 +220,7 @@ def test_flash def with_test_route_set with_routing do |set| set.draw do |map| - match ':action', :to => ActionDispatch::Session::CookieStore.new(TestController, :key => SessionKey, :secret => SessionSecret) + match ':action', :to => ActionDispatch::Session::CookieStore.new(FlashIntegrationTest::TestController, :key => FlashIntegrationTest::SessionKey, :secret => FlashIntegrationTest::SessionSecret) end yield end From f0cde5be541e1f3877a15fb5d39c87a487a14381 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 21:34:26 +0530 Subject: [PATCH 43/82] Make sure named_scope names are not used as method names already --- activerecord/lib/active_record/named_scope.rb | 5 +++++ activerecord/test/cases/named_scope_test.rb | 16 +++++++++++----- activerecord/test/models/post.rb | 4 ++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 3f5cf68fd2906..5a1cb7e769548 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -99,6 +99,11 @@ def scopes # assert_equal expected_options, Shirt.colored('red').proxy_options def named_scope(name, options = {}, &block) name = name.to_sym + + if !scopes[name] && respond_to?(name, true) + raise ArgumentError, "Cannot define named_scope :#{name} because #{self.name}.#{name} method already exists." + end + scopes[name] = lambda do |parent_scope, *args| Scope.new(parent_scope, case options when Hash diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 5d9232bc528e6..6f84e12a49a24 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -150,13 +150,13 @@ def test_has_many_through_associations_have_access_to_named_scopes end def test_named_scopes_honor_current_scopes_from_when_defined - assert !Post.ranked_by_comments.limit(5).empty? - assert !authors(:david).posts.ranked_by_comments.limit(5).empty? - assert_not_equal Post.ranked_by_comments.limit(5), authors(:david).posts.ranked_by_comments.limit(5) + assert !Post.ranked_by_comments.limit_by(5).empty? + assert !authors(:david).posts.ranked_by_comments.limit_by(5).empty? + assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5) assert_not_equal Post.top(5), authors(:david).posts.top(5) # Oracle sometimes sorts differently if WHERE condition is changed - assert_equal authors(:david).posts.ranked_by_comments.limit(5).sort_by(&:id), authors(:david).posts.top(5).sort_by(&:id) - assert_equal Post.ranked_by_comments.limit(5), Post.top(5) + assert_equal authors(:david).posts.ranked_by_comments.limit_by(5).sort_by(&:id), authors(:david).posts.top(5).sort_by(&:id) + assert_equal Post.ranked_by_comments.limit_by(5), Post.top(5) end def test_active_records_have_scope_named__all__ @@ -374,6 +374,12 @@ def test_table_names_for_chaining_scopes_with_and_without_table_name_included Comment.for_first_post.for_first_author.all end end + + def test_named_scopes_with_reserved_names + [:where, :with_scope].each do |protected_method| + assert_raises(ArgumentError) { Topic.named_scope protected_method } + end + end end class DynamicScopeMatchTest < ActiveRecord::TestCase diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 7392515ec7cea..662f75c39f5b3 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -1,7 +1,7 @@ class Post < ActiveRecord::Base named_scope :containing_the_letter_a, :conditions => "body LIKE '%a%'" named_scope :ranked_by_comments, :order => "comments_count DESC" - named_scope :limit, lambda {|limit| {:limit => limit} } + named_scope :limit_by, lambda {|limit| {:limit => limit} } named_scope :with_authors_at_address, lambda { |address| { :conditions => [ 'authors.author_address_id = ?', address.id ], :joins => 'JOIN authors ON authors.id = posts.author_id' @@ -73,7 +73,7 @@ def add_joins_and_select has_many :impatient_people, :through => :skimmers, :source => :person def self.top(limit) - ranked_by_comments.limit(limit) + ranked_by_comments.limit_by(limit) end def self.reset_log From dca3de3bc766175f49b56202246d3625c58fd763 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sun, 17 Jan 2010 23:22:11 +0530 Subject: [PATCH 44/82] Make relations work as scopes --- activerecord/lib/active_record/named_scope.rb | 26 +++++++++++++++---- .../active_record/relation/spawn_methods.rb | 10 ++++--- activerecord/test/models/post.rb | 8 +++--- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 5a1cb7e769548..30e75534dd558 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -106,7 +106,7 @@ def named_scope(name, options = {}, &block) scopes[name] = lambda do |parent_scope, *args| Scope.new(parent_scope, case options - when Hash + when Hash, Relation options when Proc options.call(*args) @@ -132,13 +132,21 @@ class Scope delegate :scopes, :with_scope, :scoped_methods, :unscoped, :to => :proxy_scope def initialize(proxy_scope, options, &block) - options ||= {} - [options[:extend]].flatten.each { |extension| extend extension } if options[:extend] extend Module.new(&block) if block_given? + + options ||= {} + if options.is_a?(Hash) + Array.wrap(options[:extend]).each {|extension| extend extension } + @proxy_options = options.except(:extend) + else + @proxy_options = options + end + unless Scope === proxy_scope @current_scoped_methods_when_defined = proxy_scope.send(:current_scoped_methods) end - @proxy_scope, @proxy_options = proxy_scope, options.except(:extend) + + @proxy_scope = proxy_scope end def reload @@ -193,7 +201,13 @@ def many? protected def relation - @relation ||= unscoped.apply_finder_options(proxy_options) + @relation ||= begin + if proxy_options.is_a?(Hash) + unscoped.apply_finder_options(proxy_options) + else + unscoped.merge(proxy_options) + end + end end def proxy_found @@ -201,6 +215,7 @@ def proxy_found end private + def method_missing(method, *args, &block) if scopes.include?(method) scopes[method].call(self, *args) @@ -221,6 +236,7 @@ def method_missing(method, *args, &block) def load_found @found = find(:all) end + end end end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 953ea5ea1cfa6..2979f4b82d4db 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -19,7 +19,10 @@ def merge(r) raise ArgumentError, "Cannot merge a #{r.klass.name}(##{r.klass.object_id}) relation with #{@klass.name}(##{@klass.object_id}) relation" end - merged_relation = spawn.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values) + merged_relation = spawn + return merged_relation unless r + + merged_relation = merged_relation.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values) merged_relation.readonly_value = r.readonly_value unless r.readonly_value.nil? merged_relation.limit_value = r.limit_value if r.limit_value.present? @@ -94,9 +97,10 @@ def only(*onlies) :order, :select, :readonly, :group, :having, :from, :lock ] def apply_finder_options(options) - options.assert_valid_keys(VALID_FIND_OPTIONS) - relation = spawn + return relation unless options + + options.assert_valid_keys(VALID_FIND_OPTIONS) relation = relation.joins(options[:joins]). where(options[:conditions]). diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 662f75c39f5b3..b5f93281394a5 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -1,7 +1,7 @@ class Post < ActiveRecord::Base - named_scope :containing_the_letter_a, :conditions => "body LIKE '%a%'" - named_scope :ranked_by_comments, :order => "comments_count DESC" - named_scope :limit_by, lambda {|limit| {:limit => limit} } + named_scope :containing_the_letter_a, where("body LIKE '%a%'") + named_scope :ranked_by_comments, order("comments_count DESC") + named_scope :limit_by, lambda {|l| limit(l) } named_scope :with_authors_at_address, lambda { |address| { :conditions => [ 'authors.author_address_id = ?', address.id ], :joins => 'JOIN authors ON authors.id = posts.author_id' @@ -20,7 +20,7 @@ def greeting has_one :last_comment, :class_name => 'Comment', :order => 'id desc' named_scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} } - named_scope :with_very_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'VerySpecialComment'} } + named_scope :with_very_special_comments, joins(:comments).where(:comments => {:type => 'VerySpecialComment'}) named_scope :with_post, lambda {|post_id| { :joins => :comments, :conditions => {:comments => {:post_id => post_id} } } } From c6850d8361bbf288cf3adefd087cb9a4bc9c97bc Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 00:03:18 +0530 Subject: [PATCH 45/82] Ensure that Scope#proxy_scope is always klass. Rename proxy_scope to klass too. --- activerecord/lib/active_record/named_scope.rb | 38 ++++++++----------- activerecord/lib/active_record/relation.rb | 2 + activerecord/test/cases/named_scope_test.rb | 8 ++-- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 30e75534dd558..9e65fb4ca5c2a 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -121,7 +121,7 @@ def named_scope(name, options = {}, &block) end class Scope - attr_reader :proxy_scope, :proxy_options, :current_scoped_methods_when_defined + attr_reader :klass, :proxy_options, :current_scoped_methods_when_defined NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? many? respond_to?).to_set [].methods.each do |m| unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s) @@ -129,9 +129,10 @@ class Scope end end - delegate :scopes, :with_scope, :scoped_methods, :unscoped, :to => :proxy_scope + delegate :scopes, :with_scope, :with_exclusive_scope, :scoped_methods, :scoped, :to => :klass + delegate :new, :build, :all, :to => :relation - def initialize(proxy_scope, options, &block) + def initialize(klass, options, &block) extend Module.new(&block) if block_given? options ||= {} @@ -142,11 +143,11 @@ def initialize(proxy_scope, options, &block) @proxy_options = options end - unless Scope === proxy_scope - @current_scoped_methods_when_defined = proxy_scope.send(:current_scoped_methods) + unless Scope === klass + @current_scoped_methods_when_defined = klass.send(:current_scoped_methods) end - @proxy_scope = proxy_scope + @klass = klass end def reload @@ -178,7 +179,7 @@ def empty? end def respond_to?(method, include_private = false) - super || @proxy_scope.respond_to?(method, include_private) + super || @klass.respond_to?(method, include_private) end def any? @@ -198,14 +199,12 @@ def many? end end - protected - def relation @relation ||= begin if proxy_options.is_a?(Hash) - unscoped.apply_finder_options(proxy_options) + scoped.apply_finder_options(proxy_options) else - unscoped.merge(proxy_options) + scoped.merge(proxy_options) end end end @@ -217,18 +216,13 @@ def proxy_found private def method_missing(method, *args, &block) - if scopes.include?(method) - scopes[method].call(self, *args) - else - with_scope(relation, :reverse_merge) do - method = :new if method == :build - if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) - with_scope current_scoped_methods_when_defined do - proxy_scope.send(method, *args, &block) - end - else - proxy_scope.send(method, *args, &block) + with_scope(relation, :reverse_merge) do + if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) && !scopes.include?(method) + with_scope current_scoped_methods_when_defined do + klass.send(method, *args, &block) end + else + klass.send(method, *args, &block) end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7c2a080eadbe8..fc429486e46d1 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -20,6 +20,8 @@ def new(*args, &block) with_create_scope { @klass.new(*args, &block) } end + alias build new + def create(*args, &block) with_create_scope { @klass.create(*args, &block) } end diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 6f84e12a49a24..09a657791ecf5 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -345,14 +345,14 @@ def test_chaining_should_use_latest_conditions_when_creating def test_chaining_should_use_latest_conditions_when_searching # Normal hash conditions - assert_equal Topic.where(:approved => true).to_a, Topic.rejected.approved.all.to_a - assert_equal Topic.where(:approved => false).to_a, Topic.approved.rejected.all.to_a + assert_equal Topic.where(:approved => true).to_a, Topic.rejected.approved.all + assert_equal Topic.where(:approved => false).to_a, Topic.approved.rejected.all # Nested hash conditions with same keys - assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all.to_a + assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all # Nested hash conditions with different keys - assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.to_a.uniq + assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.uniq end def test_named_scopes_batch_finders From 88de6b2de2606e141483ff90323c5f3ec0cfb298 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 04:24:24 +0530 Subject: [PATCH 46/82] Inherit named scope class Scope from Relation --- .../associations/association_collection.rb | 2 - activerecord/lib/active_record/named_scope.rb | 118 +++++++----------- activerecord/lib/active_record/relation.rb | 4 +- .../relation/predicate_builder.rb | 2 +- .../active_record/relation/spawn_methods.rb | 6 +- activerecord/test/cases/named_scope_test.rb | 17 +-- 6 files changed, 55 insertions(+), 94 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 64dd5cf629711..e9402d3547b08 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -403,8 +403,6 @@ def method_missing(method, *args) else super end - elsif @reflection.klass.scopes.include?(method) - @reflection.klass.scopes[method].call(self, *args) else with_scope(construct_scope) do if block_given? diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 9e65fb4ca5c2a..16fde1ffb8347 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -24,7 +24,7 @@ module ClassMethods # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope. def scoped(options = {}, &block) if options.present? - Scope.new(self, options, &block) + Scope.init(self, options, &block) else current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.spawn end @@ -105,7 +105,7 @@ def named_scope(name, options = {}, &block) end scopes[name] = lambda do |parent_scope, *args| - Scope.new(parent_scope, case options + Scope.init(parent_scope, case options when Hash, Relation options when Proc @@ -120,117 +120,83 @@ def named_scope(name, options = {}, &block) end end - class Scope - attr_reader :klass, :proxy_options, :current_scoped_methods_when_defined - NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? many? respond_to?).to_set - [].methods.each do |m| - unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s) - delegate m, :to => :proxy_found - end - end + class Scope < Relation + attr_accessor :current_scoped_methods_when_defined delegate :scopes, :with_scope, :with_exclusive_scope, :scoped_methods, :scoped, :to => :klass - delegate :new, :build, :all, :to => :relation - def initialize(klass, options, &block) - extend Module.new(&block) if block_given? + def self.init(klass, options, &block) + relation = new(klass, klass.arel_table) - options ||= {} - if options.is_a?(Hash) - Array.wrap(options[:extend]).each {|extension| extend extension } - @proxy_options = options.except(:extend) + scope = if options.is_a?(Hash) + klass.scoped.apply_finder_options(options.except(:extend)) else - @proxy_options = options + options ? klass.scoped.merge(options) : klass.scoped end - unless Scope === klass - @current_scoped_methods_when_defined = klass.send(:current_scoped_methods) - end + relation = relation.merge(scope) - @klass = klass - end + Array.wrap(options[:extend]).each {|extension| relation.send(:extend, extension) } if options.is_a?(Hash) + relation.send(:extend, Module.new(&block)) if block_given? - def reload - load_found; self + relation.current_scoped_methods_when_defined = klass.send(:current_scoped_methods) + relation end - def first(*args) - if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) - proxy_found.first(*args) - else - find(:first, *args) - end - end + def find(*args) + options = args.extract_options! + relation = options.present? ? apply_finder_options(options) : self - def last(*args) - if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) - proxy_found.last(*args) + case args.first + when :first, :last, :all + relation.send(args.first) else - find(:last, *args) + options.present? ? relation.find(*args) : super end end - def size - @found ? @found.length : count - end - - def empty? - @found ? @found.empty? : count.zero? - end - - def respond_to?(method, include_private = false) - super || @klass.respond_to?(method, include_private) - end - - def any? - if block_given? - proxy_found.any? { |*block_args| yield(*block_args) } + def first(*args) + if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash)) + to_a.first(*args) else - !empty? + args.first.present? ? apply_finder_options(args.first).first : super end end - # Returns true if the named scope has more than 1 matching record. - def many? - if block_given? - proxy_found.many? { |*block_args| yield(*block_args) } + def last(*args) + if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash)) + to_a.last(*args) else - size > 1 + args.first.present? ? apply_finder_options(args.first).last : super end end - def relation - @relation ||= begin - if proxy_options.is_a?(Hash) - scoped.apply_finder_options(proxy_options) - else - scoped.merge(proxy_options) - end - end + def count(*args) + options = args.extract_options! + options.present? ? apply_finder_options(options).count(*args) : super end - def proxy_found - @found || load_found + def ==(other) + to_a == other.to_a end private def method_missing(method, *args, &block) - with_scope(relation, :reverse_merge) do - if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) && !scopes.include?(method) - with_scope current_scoped_methods_when_defined do + if klass.respond_to?(method) + with_scope(self) do + if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) && !scopes.include?(method) + with_scope(current_scoped_methods_when_defined) { klass.send(method, *args, &block) } + else klass.send(method, *args, &block) end - else - klass.send(method, *args, &block) end + else + super end end - def load_found - @found = find(:all) - end - end + end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index fc429486e46d1..1d6fced9527cc 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -7,7 +7,7 @@ class Relation include FinderMethods, CalculationMethods, SpawnMethods, QueryMethods - delegate :length, :collect, :map, :each, :all?, :to => :to_a + delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a attr_reader :table, :klass @@ -175,6 +175,8 @@ def method_missing(method, *args, &block) end end + private + def with_create_scope @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield } end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 6b7d94135016d..03f194c46296f 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -24,7 +24,7 @@ def build_from_hash(attributes, default_table) case value when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope - attribute.in(value) + attribute.in(value.to_a) when Range # TODO : Arel should handle ranges with excluded end. if value.exclude_end? diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 2979f4b82d4db..4ac9e50f5a4c7 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -1,7 +1,7 @@ module ActiveRecord module SpawnMethods def spawn(arel_table = self.table) - relation = Relation.new(@klass, arel_table) + relation = self.class.new(@klass, arel_table) (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |query_method| relation.send(:"#{query_method}_values=", send(:"#{query_method}_values")) @@ -64,7 +64,7 @@ def merge(r) alias :& :merge def except(*skips) - result = Relation.new(@klass, table) + result = self.class.new(@klass, table) (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |method| result.send(:"#{method}_values=", send(:"#{method}_values")) unless skips.include?(method) @@ -78,7 +78,7 @@ def except(*skips) end def only(*onlies) - result = Relation.new(@klass, table) + result = self.class.new(@klass, table) onlies.each do |only| if (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).include?(only) diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 09a657791ecf5..ce1ac845cd079 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -31,7 +31,7 @@ def test_found_items_are_cached def test_reload_expires_cache_of_found_items all_posts = Topic.base - all_posts.inspect + all_posts.all new_post = Topic.create! assert !all_posts.include?(new_post) @@ -48,14 +48,14 @@ def test_delegates_finds_and_calculations_to_the_base_class end def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy - assert Topic.approved.respond_to?(:proxy_found) + assert Topic.approved.respond_to?(:limit) assert Topic.approved.respond_to?(:count) assert Topic.approved.respond_to?(:length) end def test_respond_to_respects_include_private_parameter - assert !Topic.approved.respond_to?(:load_found) - assert Topic.approved.respond_to?(:load_found, true) + assert !Topic.approved.respond_to?(:with_create_scope) + assert Topic.approved.respond_to?(:with_create_scope, true) end def test_subclasses_inherit_scopes @@ -155,7 +155,7 @@ def test_named_scopes_honor_current_scopes_from_when_defined assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5) assert_not_equal Post.top(5), authors(:david).posts.top(5) # Oracle sometimes sorts differently if WHERE condition is changed - assert_equal authors(:david).posts.ranked_by_comments.limit_by(5).sort_by(&:id), authors(:david).posts.top(5).sort_by(&:id) + assert_equal authors(:david).posts.ranked_by_comments.limit_by(5).to_a.sort_by(&:id), authors(:david).posts.top(5).to_a.sort_by(&:id) assert_equal Post.ranked_by_comments.limit_by(5), Post.top(5) end @@ -171,11 +171,6 @@ def test_active_records_have_scope_named__scoped__ assert_equal Topic.find(:all, scope), Topic.scoped(scope) end - def test_proxy_options - expected_proxy_options = { :conditions => { :approved => true } } - assert_equal expected_proxy_options, Topic.approved.proxy_options - end - def test_first_and_last_should_support_find_options assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title') assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title') @@ -297,7 +292,7 @@ def test_should_build_with_proxy_options_chained end def test_find_all_should_behave_like_select - assert_equal Topic.base.select(&:approved), Topic.base.find_all(&:approved) + assert_equal Topic.base.to_a.select(&:approved), Topic.base.to_a.find_all(&:approved) end def test_rand_should_select_a_random_object_from_proxy From e1d507c7fb541d29d57d152f40e3a539e70781d0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 04:28:21 +0530 Subject: [PATCH 47/82] Dont check for class equaity when merging relations --- activerecord/lib/active_record/relation/spawn_methods.rb | 4 ---- activerecord/test/cases/relations_test.rb | 4 ---- 2 files changed, 8 deletions(-) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 4ac9e50f5a4c7..d5b13c6100471 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -15,10 +15,6 @@ def spawn(arel_table = self.table) end def merge(r) - if r.klass != @klass - raise ArgumentError, "Cannot merge a #{r.klass.name}(##{r.klass.object_id}) relation with #{@klass.name}(##{@klass.object_id}) relation" - end - merged_relation = spawn return merged_relation unless r diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index acf247d27b9ec..e31d0ee3e8753 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -420,10 +420,6 @@ def test_relation_merging_with_preload end end - def test_invalid_merge - assert_raises(ArgumentError) { Post.scoped & Developer.scoped } - end - def test_count posts = Post.scoped From d60bb0a9e4be2ac0a9de9a69041a4ddc2e0cc914 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 04:38:19 +0530 Subject: [PATCH 48/82] Rename named_scope to scope --- activerecord/CHANGELOG | 2 ++ activerecord/lib/active_record/named_scope.rb | 19 ++++++++++------ activerecord/test/cases/named_scope_test.rb | 6 ++++- activerecord/test/models/comment.rb | 6 ++--- activerecord/test/models/developer.rb | 4 ++-- activerecord/test/models/organization.rb | 2 +- activerecord/test/models/person.rb | 4 ++-- activerecord/test/models/post.rb | 14 ++++++------ activerecord/test/models/reply.rb | 2 +- activerecord/test/models/topic.rb | 22 +++++++++---------- 10 files changed, 46 insertions(+), 35 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 38bcf0c78796c..f6b2ef553ebea 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Rename named_scope to scope. [Pratik Naik] + * Changed ActiveRecord::Base.store_full_sti_class to be true by default reflecting the previously announced Rails 3 default [DHH] * Add Relation#except. [Pratik Naik] diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 16fde1ffb8347..42fc6c5f28158 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -38,11 +38,11 @@ def scopes # such as :conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions. # # class Shirt < ActiveRecord::Base - # named_scope :red, :conditions => {:color => 'red'} - # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true] + # scope :red, :conditions => {:color => 'red'} + # scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true] # end # - # The above calls to named_scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, + # The above calls to scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, # in effect, represents the query Shirt.find(:all, :conditions => {:color => 'red'}). # # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it resembles the association object @@ -68,7 +68,7 @@ def scopes # Named \scopes can also be procedural: # # class Shirt < ActiveRecord::Base - # named_scope :colored, lambda { |color| + # scope :colored, lambda { |color| # { :conditions => { :color => color } } # } # end @@ -78,7 +78,7 @@ def scopes # Named \scopes can also have extensions, just as with has_many declarations: # # class Shirt < ActiveRecord::Base - # named_scope :red, :conditions => {:color => 'red'} do + # scope :red, :conditions => {:color => 'red'} do # def dom_id # 'red_shirts' # end @@ -90,14 +90,14 @@ def scopes # proxy_options method on the proxy itself. # # class Shirt < ActiveRecord::Base - # named_scope :colored, lambda { |color| + # scope :colored, lambda { |color| # { :conditions => { :color => color } } # } # end # # expected_options = { :conditions => { :colored => 'red' } } # assert_equal expected_options, Shirt.colored('red').proxy_options - def named_scope(name, options = {}, &block) + def scope(name, options = {}, &block) name = name.to_sym if !scopes[name] && respond_to?(name, true) @@ -118,6 +118,11 @@ def named_scope(name, options = {}, &block) end end end + + def named_scope(*args, &block) + ActiveSupport::Deprecation.warn("Base#named_scope has been deprecated, please use Base.scope instead.", caller) + scope(*args, &block) + end end class Scope < Relation diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index ce1ac845cd079..3e2bd58f9a9c7 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -372,9 +372,13 @@ def test_table_names_for_chaining_scopes_with_and_without_table_name_included def test_named_scopes_with_reserved_names [:where, :with_scope].each do |protected_method| - assert_raises(ArgumentError) { Topic.named_scope protected_method } + assert_raises(ArgumentError) { Topic.scope protected_method } end end + + def test_deprecated_named_scope_method + assert_deprecated('named_scope has been deprecated') { Topic.named_scope :deprecated_named_scope } + end end class DynamicScopeMatchTest < ActiveRecord::TestCase diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 399dea9f12554..a8a99f6dcea1e 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -1,7 +1,7 @@ class Comment < ActiveRecord::Base - named_scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'" - named_scope :for_first_post, :conditions => { :post_id => 1 } - named_scope :for_first_author, + scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'" + scope :for_first_post, :conditions => { :post_id => 1 } + scope :for_first_author, :joins => :post, :conditions => { "posts.author_id" => 1 } diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 058970336b2b1..e7a1e110d78e8 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -43,7 +43,7 @@ def find_least_recent has_many :audit_logs - named_scope :jamises, :conditions => {:name => 'Jamis'} + scope :jamises, :conditions => {:name => 'Jamis'} validates_inclusion_of :salary, :in => 50000..200000 validates_length_of :name, :within => 3..20 @@ -81,7 +81,7 @@ def raise_if_projects_empty! class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = 'developers' default_scope :order => 'salary DESC' - named_scope :by_name, :order => 'name DESC' + scope :by_name, :order => 'name DESC' def self.all_ordered_by_name with_scope(:find => { :order => 'name DESC' }) do diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb index c85726169eeab..1da342a0bd50f 100644 --- a/activerecord/test/models/organization.rb +++ b/activerecord/test/models/organization.rb @@ -2,5 +2,5 @@ class Organization < ActiveRecord::Base has_many :member_details has_many :members, :through => :member_details - named_scope :clubs, { :from => 'clubs' } + scope :clubs, { :from => 'clubs' } end \ No newline at end of file diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 57fa6418f1eed..2a73b1ee0119a 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -12,6 +12,6 @@ class Person < ActiveRecord::Base has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id' belongs_to :number1_fan, :class_name => 'Person' - named_scope :males, :conditions => { :gender => 'M' } - named_scope :females, :conditions => { :gender => 'F' } + scope :males, :conditions => { :gender => 'M' } + scope :females, :conditions => { :gender => 'F' } end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index b5f93281394a5..939df7549bb6a 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -1,8 +1,8 @@ class Post < ActiveRecord::Base - named_scope :containing_the_letter_a, where("body LIKE '%a%'") - named_scope :ranked_by_comments, order("comments_count DESC") - named_scope :limit_by, lambda {|l| limit(l) } - named_scope :with_authors_at_address, lambda { |address| { + scope :containing_the_letter_a, where("body LIKE '%a%'") + scope :ranked_by_comments, order("comments_count DESC") + scope :limit_by, lambda {|l| limit(l) } + scope :with_authors_at_address, lambda { |address| { :conditions => [ 'authors.author_address_id = ?', address.id ], :joins => 'JOIN authors ON authors.id = posts.author_id' } @@ -19,9 +19,9 @@ def greeting has_one :last_comment, :class_name => 'Comment', :order => 'id desc' - named_scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} } - named_scope :with_very_special_comments, joins(:comments).where(:comments => {:type => 'VerySpecialComment'}) - named_scope :with_post, lambda {|post_id| + scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} } + scope :with_very_special_comments, joins(:comments).where(:comments => {:type => 'VerySpecialComment'}) + scope :with_post, lambda {|post_id| { :joins => :comments, :conditions => {:comments => {:post_id => post_id} } } } diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb index f1ba45b52807a..264a49b4658ab 100644 --- a/activerecord/test/models/reply.rb +++ b/activerecord/test/models/reply.rb @@ -1,7 +1,7 @@ require 'models/topic' class Reply < Topic - named_scope :base + scope :base belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count" diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index baca4972cb41e..91fc7c9416cc4 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -1,19 +1,19 @@ class Topic < ActiveRecord::Base - named_scope :base - named_scope :written_before, lambda { |time| + scope :base + scope :written_before, lambda { |time| if time { :conditions => ['written_on < ?', time] } end } - named_scope :approved, :conditions => {:approved => true} - named_scope :rejected, :conditions => {:approved => false} + scope :approved, :conditions => {:approved => true} + scope :rejected, :conditions => {:approved => false} - named_scope :by_lifo, :conditions => {:author_name => 'lifo'} + scope :by_lifo, :conditions => {:author_name => 'lifo'} - named_scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}} - named_scope 'approved_as_string', :conditions => {:approved => true} - named_scope :replied, :conditions => ['replies_count > 0'] - named_scope :anonymous_extension do + scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}} + scope 'approved_as_string', :conditions => {:approved => true} + scope :replied, :conditions => ['replies_count > 0'] + scope :anonymous_extension do def one 1 end @@ -33,8 +33,8 @@ def extension_two 2 end end - named_scope :named_extension, :extend => NamedExtension - named_scope :multiple_extensions, :extend => [MultipleExtensionTwo, MultipleExtensionOne] + scope :named_extension, :extend => NamedExtension + scope :multiple_extensions, :extend => [MultipleExtensionTwo, MultipleExtensionOne] has_many :replies, :dependent => :destroy, :foreign_key => "parent_id" has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title" From 6ce538d4850cca0e6ce71da1a5b8d350e57f154f Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 04:42:04 +0530 Subject: [PATCH 49/82] Add missing CHANGELOG entry about relations as scopes --- activerecord/CHANGELOG | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index f6b2ef553ebea..ffff0b7e095d7 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,13 @@ *Edge* +* Allow relations to be used as scope. + + class Item + scope :red, where(:colour => 'red') + end + + Item.red.limit(10) # Ten red items + * Rename named_scope to scope. [Pratik Naik] * Changed ActiveRecord::Base.store_full_sti_class to be true by default reflecting the previously announced Rails 3 default [DHH] From eeba755a11dbdbf90afd4fd815e215bd7e9826e6 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sun, 17 Jan 2010 21:30:38 -0600 Subject: [PATCH 50/82] Accessing nonexistant cookies through the signed jar should not raise an exception --- actionpack/lib/action_dispatch/middleware/cookies.rb | 4 +++- actionpack/test/controller/cookie_test.rb | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 5d2734a15e476..0dc03a1a7e69a 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -176,7 +176,9 @@ def initialize(parent_jar) end def [](name) - @verifier.verify(@parent_jar[name]) + if value = @parent_jar[name] + @verifier.verify(value) + end end def []=(key, options) diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index fd6538b27ae0a..f5ccef8aaf377 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -141,6 +141,11 @@ def test_signed_cookie assert_equal 45, @controller.send(:cookies).signed[:user_id] end + def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature + get :set_signed_cookie + assert_nil @controller.send(:cookies).signed[:non_existant_attribute] + end + def test_permanent_signed_cookie get :set_permanent_signed_cookie assert_match %r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"] From 58fe3295feba782ba0a76b9315b962b1d33e8586 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sun, 17 Jan 2010 21:51:07 -0600 Subject: [PATCH 51/82] Base#action_methods delegates to Base.action_methods --- actionpack/lib/abstract_controller/base.rb | 120 +++++++++++---------- 1 file changed, 62 insertions(+), 58 deletions(-) diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 48725ad82a670..816412d97e80b 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -116,68 +116,72 @@ def controller_path self.class.controller_path end - private - # Returns true if the name can be considered an action. This can - # be overridden in subclasses to modify the semantics of what - # can be considered an action. - # - # ==== Parameters - # name:: The name of an action to be tested - # - # ==== Returns - # TrueClass, FalseClass - def action_method?(name) - self.class.action_methods.include?(name) + def action_methods + self.class.action_methods end - # Call the action. Override this in a subclass to modify the - # behavior around processing an action. This, and not #process, - # is the intended way to override action dispatching. - def process_action(method_name, *args) - send_action(method_name, *args) - end + private + # Returns true if the name can be considered an action. This can + # be overridden in subclasses to modify the semantics of what + # can be considered an action. + # + # ==== Parameters + # name:: The name of an action to be tested + # + # ==== Returns + # TrueClass, FalseClass + def action_method?(name) + self.class.action_methods.include?(name) + end - # Actually call the method associated with the action. Override - # this method if you wish to change how action methods are called, - # not to add additional behavior around it. For example, you would - # override #send_action if you want to inject arguments into the - # method. - alias send_action send - - # If the action name was not found, but a method called "action_missing" - # was found, #method_for_action will return "_handle_action_missing". - # This method calls #action_missing with the current action name. - def _handle_action_missing - action_missing(@_action_name) - end + # Call the action. Override this in a subclass to modify the + # behavior around processing an action. This, and not #process, + # is the intended way to override action dispatching. + def process_action(method_name, *args) + send_action(method_name, *args) + end - # Takes an action name and returns the name of the method that will - # handle the action. In normal cases, this method returns the same - # name as it receives. By default, if #method_for_action receives - # a name that is not an action, it will look for an #action_missing - # method and return "_handle_action_missing" if one is found. - # - # Subclasses may override this method to add additional conditions - # that should be considered an action. For instance, an HTTP controller - # with a template matching the action name is considered to exist. - # - # If you override this method to handle additional cases, you may - # also provide a method (like _handle_method_missing) to handle - # the case. - # - # If none of these conditions are true, and method_for_action - # returns nil, an ActionNotFound exception will be raised. - # - # ==== Parameters - # action_name:: An action name to find a method name for - # - # ==== Returns - # String:: The name of the method that handles the action - # nil:: No method name could be found. Raise ActionNotFound. - def method_for_action(action_name) - if action_method?(action_name) then action_name - elsif respond_to?(:action_missing, true) then "_handle_action_missing" + # Actually call the method associated with the action. Override + # this method if you wish to change how action methods are called, + # not to add additional behavior around it. For example, you would + # override #send_action if you want to inject arguments into the + # method. + alias send_action send + + # If the action name was not found, but a method called "action_missing" + # was found, #method_for_action will return "_handle_action_missing". + # This method calls #action_missing with the current action name. + def _handle_action_missing + action_missing(@_action_name) + end + + # Takes an action name and returns the name of the method that will + # handle the action. In normal cases, this method returns the same + # name as it receives. By default, if #method_for_action receives + # a name that is not an action, it will look for an #action_missing + # method and return "_handle_action_missing" if one is found. + # + # Subclasses may override this method to add additional conditions + # that should be considered an action. For instance, an HTTP controller + # with a template matching the action name is considered to exist. + # + # If you override this method to handle additional cases, you may + # also provide a method (like _handle_method_missing) to handle + # the case. + # + # If none of these conditions are true, and method_for_action + # returns nil, an ActionNotFound exception will be raised. + # + # ==== Parameters + # action_name:: An action name to find a method name for + # + # ==== Returns + # String:: The name of the method that handles the action + # nil:: No method name could be found. Raise ActionNotFound. + def method_for_action(action_name) + if action_method?(action_name) then action_name + elsif respond_to?(:action_missing, true) then "_handle_action_missing" + end end - end end end From c29bb8857e637e77a8cf10f10c049d20fea3228a Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sun, 17 Jan 2010 22:06:28 -0600 Subject: [PATCH 52/82] Clear out AS callback method pollution in AC::Base.action_methods --- actionpack/lib/abstract_controller/base.rb | 20 +++++++------ .../test/controller/new_base/base_test.rb | 28 ++++++++++++++++++- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 816412d97e80b..3119ee498b2e5 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -62,15 +62,19 @@ def hidden_actions # Array[String]:: A list of all methods that should be considered # actions. def action_methods - @action_methods ||= + @action_methods ||= begin # All public instance methods of this class, including ancestors - public_instance_methods(true).map { |m| m.to_s }.to_set - - # Except for public instance methods of Base and its ancestors - internal_methods.map { |m| m.to_s } + - # Be sure to include shadowed public instance methods of this class - public_instance_methods(false).map { |m| m.to_s } - - # And always exclude explicitly hidden actions - hidden_actions + methods = public_instance_methods(true).map { |m| m.to_s }.to_set - + # Except for public instance methods of Base and its ancestors + internal_methods.map { |m| m.to_s } + + # Be sure to include shadowed public instance methods of this class + public_instance_methods(false).map { |m| m.to_s } - + # And always exclude explicitly hidden actions + hidden_actions + + # Clear out AS callback method pollution + methods.reject { |method| method =~ /_one_time_conditions/ } + end end # Returns the full controller name, underscored, without the ending Controller. diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb index 1f9bf7f0fbae6..964780eaf2384 100644 --- a/actionpack/test/controller/new_base/base_test.rb +++ b/actionpack/test/controller/new_base/base_test.rb @@ -3,6 +3,8 @@ # Tests the controller dispatching happy path module Dispatching class SimpleController < ActionController::Base + before_filter :authenticate + def index render :text => "success" end @@ -12,12 +14,20 @@ def modify_response_body end def modify_response_body_twice - ret = (self.response_body = "success") + ret = (self.response_body = "success") self.response_body = "#{ret}!" end def modify_response_headers end + + def show_actions + render :text => "actions: #{action_methods.to_a.join(', ')}" + end + + protected + def authenticate + end end class EmptyController < ActionController::Base ; end @@ -64,5 +74,21 @@ class BaseTest < Rack::TestCase assert_equal 'empty', EmptyController.controller_name assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name end + + test "action methods" do + assert_equal Set.new(%w( + modify_response_headers + modify_response_body_twice + index + modify_response_body + show_actions + )), SimpleController.action_methods + + assert_equal Set.new, EmptyController.action_methods + assert_equal Set.new, Submodule::ContainedEmptyController.action_methods + + get "/dispatching/simple/show_actions" + assert_body "actions: modify_response_headers, modify_response_body_twice, index, modify_response_body, show_actions" + end end end From 38c2e4687f70f67faf29ff4d50b10d8c21349769 Mon Sep 17 00:00:00 2001 From: Damien Mathieu <42@dmathieu.com> Date: Mon, 18 Jan 2010 09:07:42 +0100 Subject: [PATCH 53/82] Missing rename from named_scope to scope [#3735 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionpack/test/fixtures/reply.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/test/fixtures/reply.rb b/actionpack/test/fixtures/reply.rb index 04598437c28d7..19cba9367314e 100644 --- a/actionpack/test/fixtures/reply.rb +++ b/actionpack/test/fixtures/reply.rb @@ -1,5 +1,5 @@ class Reply < ActiveRecord::Base - named_scope :base + scope :base belongs_to :topic, :include => [:replies] belongs_to :developer From 40c4a0036a690b2e0e3157520f1851efc77fbef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Jan 2010 09:56:36 +0100 Subject: [PATCH 54/82] Ensure deprecated validate methods are invoked when they are private [#3214 status:resolved] --- activerecord/lib/active_record/callbacks.rb | 2 +- activerecord/test/cases/validations_test.rb | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index aecde5848c65c..1128286f2bb70 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -291,7 +291,7 @@ def destroy_with_callbacks #:nodoc: end def deprecated_callback_method(symbol) #:nodoc: - if respond_to?(symbol) + if respond_to?(symbol, true) ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead") send(symbol) end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 8314f880be7e5..5aac0229cd455 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -19,7 +19,7 @@ class ProtectedPerson < ActiveRecord::Base class DeprecatedPerson < ActiveRecord::Base set_table_name 'people' - protected + private def validate errors[:name] << "always invalid" @@ -161,4 +161,21 @@ def test_validates_acceptance_of_as_database_column topic = Topic.create("author_name" => "Dan Brown") assert_equal "Dan Brown", topic["author_name"] end + + def test_validate_is_deprecated_on_create + p = DeprecatedPerson.new + assert_deprecated do + assert !p.valid? + end + assert_equal ["always invalid", "invalid on create"], p.errors[:name] + end + + def test_validate_is_deprecated_on_update + p = DeprecatedPerson.new(:first_name => "David") + assert p.save(:validate => false) + assert_deprecated do + assert !p.valid? + end + assert_equal ["always invalid", "invalid on update"], p.errors[:name] + end end From 8ba2902dd4c3e9a3715130c499d78c4fc79fbf16 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 12:32:15 +0530 Subject: [PATCH 55/82] Fix the named_scope deprecation notice --- activerecord/lib/active_record/named_scope.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 42fc6c5f28158..5c0cf10f4cce8 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -120,7 +120,7 @@ def scope(name, options = {}, &block) end def named_scope(*args, &block) - ActiveSupport::Deprecation.warn("Base#named_scope has been deprecated, please use Base.scope instead.", caller) + ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead.", caller) scope(*args, &block) end end From 8bb527464845071ee72bcbcddcf860d4af1eda32 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 12:50:20 +0530 Subject: [PATCH 56/82] Get rid of Relation#order_clauses --- activerecord/lib/active_record/relation.rb | 16 ++++++---------- activerecord/test/cases/method_scoping_test.rb | 6 +++--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 1d6fced9527cc..4ed118b02d4e8 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -50,14 +50,14 @@ def to_a @records = if find_with_associations begin options = { - :select => @select_values.any? ? @select_values.join(", ") : nil, + :select => @select_values.join(", "), :joins => arel.joins(arel), - :group => @group_values.any? ? @group_values.join(", ") : nil, - :order => order_clause, + :group => @group_values.join(", "), + :order => @order_values.join(', '), :conditions => where_clause, - :limit => arel.taken, - :offset => arel.skipped, - :from => (arel.send(:from_clauses) if arel.send(:sources).present?) + :limit => @limit_value, + :offset => @offset_value, + :from => @from_value } including = (@eager_load_values + @includes_values).uniq @@ -185,10 +185,6 @@ def where_clause(join_string = " AND ") arel.send(:where_clauses).join(join_string) end - def order_clause - @order_clause ||= arel.send(:order_clauses).join(', ') - end - def references_eager_loaded_tables? joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.uniq (tables_in_string(to_sql) - joined_tables).any? diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index a502b50c3ef2c..7ca5b5a9884d1 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -608,7 +608,7 @@ def test_default_scope_with_conditions_hash def test_default_scoping_with_threads 2.times do - Thread.new { assert_equal 'salary DESC', DeveloperOrderedBySalary.scoped.send(:order_clause) }.join + Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join end end @@ -618,10 +618,10 @@ def test_default_scoping_with_inheritance klass.send :default_scope, {} # Scopes added on children should append to parent scope - assert klass.scoped.send(:order_clause).blank? + assert klass.scoped.order_values.blank? # Parent should still have the original scope - assert_equal 'salary DESC', DeveloperOrderedBySalary.scoped.send(:order_clause) + assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values end def test_method_scope From 2a2bc8e84aa619089abcc4f9a4c2711a118e5fa9 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 12:55:23 +0530 Subject: [PATCH 57/82] Handle invalid query IN() generated when a blank array is supplied in hash conditions --- activerecord/lib/active_record/relation/predicate_builder.rb | 3 ++- activerecord/test/cases/relations_test.rb | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 03f194c46296f..9e855209f9138 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -24,7 +24,8 @@ def build_from_hash(attributes, default_table) case value when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope - attribute.in(value.to_a) + values = value.to_a + values.any? ? attribute.in(values) : attribute.eq(nil) when Range # TODO : Arel should handle ranges with excluded end. if value.exclude_end? diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index e31d0ee3e8753..d34c9b4895948 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -339,6 +339,11 @@ def test_find_ids assert_raises(ActiveRecord::RecordNotFound) { authors.find(['42', 43]) } end + def test_find_in_empty_array + authors = Author.scoped.where(:id => []) + assert authors.all.blank? + end + def test_exists davids = Author.where(:name => 'David') assert davids.exists? From 4c00c65c58055b341b4ca59a38c3c93082049c84 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 19:16:20 +0530 Subject: [PATCH 58/82] Simplify construct_finder_arel_* methods --- .../lib/active_record/associations.rb | 34 ++++--------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 468a6cd9f833c..ebf1a41e85f7b 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1701,30 +1701,19 @@ def select_all_rows(options, join_dependency) end def construct_finder_arel_with_included_associations(options, join_dependency) - relation = unscoped + relation = scoped for association in join_dependency.join_associations relation = association.join_relation(relation) end - relation = relation.joins(options[:joins]). - select(column_aliases(join_dependency)). - group(options[:group]). - having(options[:having]). - order(options[:order]). - where(options[:conditions]). - from(options[:from]) + relation = relation.apply_finder_options(options).select(column_aliases(join_dependency)) - scoped_relation = current_scoped_methods - scoped_relation_limit = scoped_relation.taken if scoped_relation - - relation = current_scoped_methods.except(:limit).merge(relation) if current_scoped_methods - - if !using_limitable_reflections?(join_dependency.reflections) && ((scoped_relation && scoped_relation.taken) || options[:limit]) + if !using_limitable_reflections?(join_dependency.reflections) && relation.limit_value relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) end - relation = relation.limit(options[:limit] || scoped_relation_limit) if using_limitable_reflections?(join_dependency.reflections) + relation = relation.except(:limit, :offset) unless using_limitable_reflections?(join_dependency.reflections) relation end @@ -1752,23 +1741,14 @@ def select_limited_ids_array(options, join_dependency) end def construct_finder_sql_for_association_limiting(options, join_dependency) - relation = unscoped + relation = scoped for association in join_dependency.join_associations relation = association.join_relation(relation) end - relation = relation.joins(options[:joins]). - where(options[:conditions]). - group(options[:group]). - having(options[:having]). - order(options[:order]). - limit(options[:limit]). - offset(options[:offset]). - from(options[:from]) - - relation = current_scoped_methods.except(:select, :includes, :eager_load).merge(relation) if current_scoped_methods - relation = relation.select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order])) + relation = relation.apply_finder_options(options).except(:select) + relation = relation.select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", relation.order_values.join(", "))) relation.to_sql end From a2e2e73c5ef7c9082810d77c831c50c91011e6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Jan 2010 10:43:10 +0100 Subject: [PATCH 59/82] Ensure generators can be invoked by their shortcut and remove attr_reader tasks. --- railties/lib/rails/generators.rb | 5 +++-- railties/lib/rails/generators/base.rb | 4 ++++ railties/lib/rails/generators/named_base.rb | 8 +++++--- railties/lib/rails/generators/resource_helpers.rb | 10 +++++++--- railties/test/generators/actions_test.rb | 13 +++++++++++++ railties/test/generators_test.rb | 4 ++-- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 3713a38b3335a..ecd0a1c6ea089 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -168,6 +168,8 @@ def self.load_paths # Rails looks for is the first and last parts of the namespace. # def self.find_by_namespace(name, base=nil, context=nil) #:nodoc: + base = "rails" unless base || context || name.to_s.include?(?:) + # Mount regexps to lookup regexps = [] regexps << /^#{base}:[\w:]*#{name}$/ if base @@ -203,8 +205,7 @@ def self.find_by_regexps(regexps, klasses) # commands. def self.invoke(namespace, args=ARGV, config={}) names = namespace.to_s.split(':') - - if klass = find_by_namespace(names.pop, names.shift || "rails") + if klass = find_by_namespace(names.pop, names.shift) args << "--help" if klass.arguments.any? { |a| a.required? } && args.empty? klass.start(args, config) else diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 26abb46644599..9cc342b2022df 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -324,9 +324,13 @@ def self.hooks #:nodoc: # added hook is being used. # def self.prepare_for_invocation(name, value) #:nodoc: + return super unless value.is_a?(String) || value.is_a?(Symbol) + if value && constants = self.hooks[name] value = name if TrueClass === value Rails::Generators.find_by_namespace(value, *constants) + elsif klass = Rails::Generators.find_by_namespace(value) + klass else super end diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 1d4f52286eb95..3e851bf888f2c 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -6,10 +6,12 @@ module Generators class NamedBase < Base argument :name, :type => :string - attr_reader :class_name, :singular_name, :plural_name, :table_name, - :class_path, :file_path, :class_nesting_depth + no_tasks { + attr_reader :class_name, :singular_name, :plural_name, :table_name, + :class_path, :file_path, :class_nesting_depth - alias :file_name :singular_name + alias :file_name :singular_name + } def initialize(args, *options) #:nodoc: # Unfreeze name in case it's given as a frozen string diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index 99954e2292485..7e00a222eda04 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -9,10 +9,14 @@ module ResourceHelpers mattr_accessor :skip_warn def self.included(base) #:nodoc: - base.send :attr_reader, :controller_name, :controller_class_name, :controller_file_name, - :controller_class_path, :controller_file_path + base.class_eval do + class_option :force_plural, :type => :boolean, :desc => "Forces the use of a plural ModelName" - base.send :class_option, :force_plural, :type => :boolean, :desc => "Forces the use of a plural ModelName" + no_tasks { + attr_reader :controller_name, :controller_class_name, :controller_file_name, + :controller_class_path, :controller_file_path + } + end end # Set controller variables on initialization. diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 27b6a49566279..196cec3ce74df 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -1,6 +1,9 @@ require 'generators/generators_test_helper' require 'rails/generators/rails/app/app_generator' +# TODO This line shouldn't be required +require 'rails/generators/rails/model/model_generator' + class ActionsTest < GeneratorsTestCase tests Rails::Generators::AppGenerator arguments [destination_root] @@ -11,6 +14,16 @@ def setup @svn_plugin_uri = 'svn://svnhub.com/technoweenie/restful-authentication/trunk' end + def test_invoke_other_generator_with_shortcut + action :invoke, 'model', ['my_model'] + assert_file 'app/models/my_model.rb', /MyModel/ + end + + def test_invoke_other_generator_with_full_namespace + action :invoke, 'rails:generators:model', ['my_model'] + assert_file 'app/models/my_model.rb', /MyModel/ + end + def test_create_file_should_write_data_to_file_path action :create_file, 'lib/test_file.rb', 'heres test data' assert_file 'lib/test_file.rb', 'heres test data' diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 2df218debcec0..e349040fe2662 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -34,8 +34,8 @@ def test_invoke_with_config_values Rails::Generators.invoke :model, ["Account"], :behavior => :skip end - def test_find_by_namespace_without_base_or_context - assert_nil Rails::Generators.find_by_namespace(:model) + def test_find_by_namespace_without_base_or_context_looks_into_rails_namespace + assert Rails::Generators.find_by_namespace(:model) end def test_find_by_namespace_with_base From e75ea474346e74e36d92febd47985c3571b1472b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Jan 2010 12:28:52 +0100 Subject: [PATCH 60/82] Automatically remove :generators: from namespaces. --- railties/lib/rails/generators.rb | 2 +- railties/lib/rails/generators/base.rb | 2 +- railties/test/generators/actions_test.rb | 2 +- railties/test/generators/app_generator_test.rb | 2 +- railties/test/generators_test.rb | 12 ++++++------ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index ecd0a1c6ea089..e8c7a0993b5d5 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -219,7 +219,7 @@ def self.help builtin.sort! lookup("*") - others = subclasses.map{ |k| k.namespace.gsub(':generators:', ':') } + others = subclasses.map{ |k| k.namespace } others -= Rails::Generators.builtin others.sort! diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 9cc342b2022df..37effb77db126 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -42,7 +42,7 @@ def self.desc(description=nil) # def self.namespace(name=nil) return super if name - @namespace ||= super.sub(/_generator$/, '') + @namespace ||= super.sub(/_generator$/, '').sub(/:generators:/, ':') end # Invoke a generator based on the value supplied by the user to the diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 196cec3ce74df..b59ec524e8583 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -20,7 +20,7 @@ def test_invoke_other_generator_with_shortcut end def test_invoke_other_generator_with_full_namespace - action :invoke, 'rails:generators:model', ['my_model'] + action :invoke, 'rails:model', ['my_model'] assert_file 'app/models/my_model.rb', /MyModel/ end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 5fab233c8dcae..83de50bbb452a 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -158,7 +158,7 @@ def test_default_usage end def test_default_namespace - assert_match "rails:generators:app", Rails::Generators::AppGenerator.namespace + assert_match "rails:app", Rails::Generators::AppGenerator.namespace end def test_file_is_added_for_backwards_compatibility diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index e349040fe2662..65d7cd2746019 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -41,13 +41,13 @@ def test_find_by_namespace_without_base_or_context_looks_into_rails_namespace def test_find_by_namespace_with_base klass = Rails::Generators.find_by_namespace(:model, :rails) assert klass - assert_equal "rails:generators:model", klass.namespace + assert_equal "rails:model", klass.namespace end def test_find_by_namespace_with_context klass = Rails::Generators.find_by_namespace(:test_unit, nil, :model) assert klass - assert_equal "test_unit:generators:model", klass.namespace + assert_equal "test_unit:model", klass.namespace end def test_find_by_namespace_with_duplicated_name @@ -65,13 +65,13 @@ def test_find_by_namespace_lookup_to_the_rails_root_folder def test_find_by_namespace_lookup_to_deep_rails_root_folders klass = Rails::Generators.find_by_namespace(:fixjour, :active_record) assert klass - assert_equal "active_record:generators:fixjour", klass.namespace + assert_equal "active_record:fixjour", klass.namespace end def test_find_by_namespace_lookup_traverse_folders klass = Rails::Generators.find_by_namespace(:javascripts, :rails) assert klass - assert_equal "rails:generators:javascripts", klass.namespace + assert_equal "rails:javascripts", klass.namespace end def test_find_by_namespace_lookup_to_vendor_folders @@ -146,14 +146,14 @@ def test_fallbacks_for_generators_on_find_by_namespace Rails::Generators.fallbacks[:remarkable] = :test_unit klass = Rails::Generators.find_by_namespace(:plugin, :remarkable) assert klass - assert_equal "test_unit:generators:plugin", klass.namespace + assert_equal "test_unit:plugin", klass.namespace end def test_fallbacks_for_generators_on_find_by_namespace_with_context Rails::Generators.fallbacks[:remarkable] = :test_unit klass = Rails::Generators.find_by_namespace(:remarkable, :rails, :plugin) assert klass - assert_equal "test_unit:generators:plugin", klass.namespace + assert_equal "test_unit:plugin", klass.namespace end def test_fallbacks_for_generators_on_invoke From 9fffdc5cdb80b1824473a6d7ae1fedf9e74aa748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Jan 2010 13:44:32 +0100 Subject: [PATCH 61/82] Generators load path now will be Ruby load path. If you want to use rspec:install generator, you need generators/rspec/install_generator in your load path. --- .../lib}/generators/active_record.rb | 8 ++ .../migration/migration_generator.rb | 2 +- .../migration/templates/migration.rb | 0 .../active_record/model/model_generator.rb | 2 +- .../model/templates/migration.rb | 0 .../active_record/model/templates/model.rb | 0 .../observer/observer_generator.rb | 2 +- .../observer/templates/observer.rb | 0 .../session_migration_generator.rb | 2 +- .../session_migration/templates/migration.rb | 0 railties/lib/{rails => }/generators/erb.rb | 0 .../erb/controller/controller_generator.rb | 2 +- .../erb/controller/templates/view.html.erb | 0 .../generators/erb/mailer/mailer_generator.rb | 2 +- .../generators/erb/mailer/templates/view.erb | 0 .../erb/scaffold/scaffold_generator.rb | 2 +- .../erb/scaffold/templates/_form.html.erb | 0 .../erb/scaffold/templates/edit.html.erb | 0 .../erb/scaffold/templates/index.html.erb | 0 .../erb/scaffold/templates/layout.html.erb | 0 .../erb/scaffold/templates/new.html.erb | 0 .../erb/scaffold/templates/show.html.erb | 0 .../{rails => }/generators/rails/app/USAGE | 0 .../generators/rails/app/app_generator.rb | 4 +- .../generators/rails/app/templates/Gemfile | 0 .../generators/rails/app/templates/README | 0 .../generators/rails/app/templates/Rakefile | 0 .../app/controllers/application_controller.rb | 0 .../app/helpers/application_helper.rb | 0 .../app/templates/app/models/.empty_directory | 0 .../app/views/layouts/.empty_directory | 0 .../generators/rails/app/templates/config.ru | 0 .../rails/app/templates/config/application.rb | 0 .../rails/app/templates/config/boot.rb | 0 .../templates/config/databases/frontbase.yml | 0 .../app/templates/config/databases/ibm_db.yml | 0 .../app/templates/config/databases/mysql.yml | 0 .../app/templates/config/databases/oracle.yml | 0 .../templates/config/databases/postgresql.yml | 0 .../templates/config/databases/sqlite3.yml | 0 .../rails/app/templates/config/environment.rb | 0 .../config/environments/development.rb.tt | 0 .../config/environments/production.rb.tt | 0 .../templates/config/environments/test.rb.tt | 0 .../initializers/backtrace_silencers.rb | 0 .../cookie_verification_secret.rb.tt | 0 .../config/initializers/inflections.rb | 0 .../config/initializers/mime_types.rb | 0 .../config/initializers/session_store.rb.tt | 0 .../rails/app/templates/config/locales/en.yml | 0 .../rails/app/templates/config/routes.rb | 0 .../rails/app/templates/db/seeds.rb | 0 .../rails/app/templates/doc/README_FOR_APP | 0 .../generators/rails/app/templates/gitignore | 0 .../rails/app/templates/public/404.html | 0 .../rails/app/templates/public/422.html | 0 .../rails/app/templates/public/500.html | 0 .../rails/app/templates/public/favicon.ico | 0 .../app/templates/public/images/rails.png | Bin .../rails/app/templates/public/index.html | 0 .../public/javascripts/application.js | 0 .../templates/public/javascripts/controls.js | 0 .../templates/public/javascripts/dragdrop.js | 0 .../templates/public/javascripts/effects.js | 0 .../templates/public/javascripts/prototype.js | 0 .../rails/app/templates/public/robots.txt | 0 .../public/stylesheets/.empty_directory | 0 .../rails/app/templates/script/about | 0 .../rails/app/templates/script/console.tt | 0 .../rails/app/templates/script/dbconsole.tt | 0 .../rails/app/templates/script/destroy | 0 .../rails/app/templates/script/generate | 0 .../templates/script/performance/benchmarker | 0 .../app/templates/script/performance/profiler | 0 .../rails/app/templates/script/plugin | 0 .../rails/app/templates/script/runner | 0 .../rails/app/templates/script/server.tt | 0 .../templates/test/fixtures/.empty_directory | 0 .../test/functional/.empty_directory | 0 .../test/integration/.empty_directory | 0 .../test/performance/browsing_test.rb | 0 .../rails/app/templates/test/test_helper.rb | 0 .../app/templates/test/unit/.empty_directory | 0 .../generators/rails/controller/USAGE | 0 .../rails/controller/controller_generator.rb | 0 .../rails/controller/templates/controller.rb | 0 .../generators/rails/generator/USAGE | 0 .../rails/generator/generator_generator.rb | 0 .../templates/%file_name%_generator.rb.tt | 0 .../rails/generator/templates/USAGE.tt | 0 .../templates/templates/.empty_directory | 0 .../{rails => }/generators/rails/helper/USAGE | 0 .../rails/helper/helper_generator.rb | 0 .../rails/helper/templates/helper.rb | 0 .../generators/rails/integration_test/USAGE | 0 .../integration_test_generator.rb | 0 .../{rails => }/generators/rails/mailer/USAGE | 0 .../rails/mailer/mailer_generator.rb | 0 .../rails/mailer/templates/mailer.rb | 0 .../{rails => }/generators/rails/metal/USAGE | 0 .../generators/rails/metal/metal_generator.rb | 0 .../generators/rails/metal/templates/metal.rb | 0 .../generators/rails/migration/USAGE | 0 .../rails/migration/migration_generator.rb | 0 .../{rails => }/generators/rails/model/USAGE | 0 .../generators/rails/model/model_generator.rb | 0 .../model_subclass_generator.rb | 1 + .../generators/rails/observer/USAGE | 0 .../rails/observer/observer_generator.rb | 0 .../generators/rails/performance_test/USAGE | 0 .../performance_test_generator.rb | 0 .../{rails => }/generators/rails/plugin/USAGE | 0 .../rails/plugin/plugin_generator.rb | 2 +- .../rails/plugin/templates/MIT-LICENSE.tt | 0 .../rails/plugin/templates/README.tt | 0 .../rails/plugin/templates/Rakefile.tt | 0 .../generators/rails/plugin/templates/init.rb | 0 .../rails/plugin/templates/install.rb | 0 .../plugin/templates/lib/%file_name%.rb.tt | 0 .../templates/tasks/%file_name%_tasks.rake.tt | 0 .../rails/plugin/templates/uninstall.rb | 0 .../generators/rails/resource/USAGE | 0 .../rails/resource/resource_generator.rb | 2 +- .../generators/rails/scaffold/USAGE | 0 .../rails/scaffold/scaffold_generator.rb | 2 +- .../rails/scaffold_controller/USAGE | 0 .../scaffold_controller_generator.rb | 0 .../templates/controller.rb | 0 .../generators/rails/session_migration/USAGE | 0 .../session_migration_generator.rb | 0 .../generators/rails/stylesheets/USAGE | 0 .../stylesheets/stylesheets_generator.rb | 0 .../rails/stylesheets/templates/scaffold.css | 0 .../lib/{rails => }/generators/test_unit.rb | 0 .../controller/controller_generator.rb | 2 +- .../controller/templates/functional_test.rb | 0 .../test_unit/helper/helper_generator.rb | 2 +- .../test_unit/helper/templates/helper_test.rb | 0 .../integration/integration_generator.rb | 2 +- .../integration/templates/integration_test.rb | 0 .../test_unit/mailer/mailer_generator.rb | 2 +- .../test_unit/mailer/templates/fixture | 0 .../test_unit/mailer/templates/unit_test.rb | 0 .../test_unit/model/model_generator.rb | 2 +- .../test_unit/model/templates/fixtures.yml | 0 .../test_unit/model/templates/unit_test.rb | 0 .../test_unit/observer/observer_generator.rb | 2 +- .../test_unit/observer/templates/unit_test.rb | 0 .../performance/performance_generator.rb | 2 +- .../performance/templates/performance_test.rb | 0 .../test_unit/plugin/plugin_generator.rb | 2 +- .../plugin/templates/%file_name%_test.rb.tt | 0 .../test_unit/plugin/templates/test_helper.rb | 0 .../test_unit/scaffold/scaffold_generator.rb | 2 +- .../scaffold/templates/functional_test.rb | 0 railties/lib/rails/generators.rb | 78 ++++++------------ railties/lib/rails/generators/base.rb | 2 +- railties/test/generators/actions_test.rb | 4 +- .../test/generators/app_generator_test.rb | 2 +- .../generators/controller_generator_test.rb | 2 +- .../generators/generator_generator_test.rb | 2 +- .../test/generators/generators_test_helper.rb | 1 - .../test/generators/helper_generator_test.rb | 2 +- .../integration_test_generator_test.rb | 2 +- .../test/generators/mailer_generator_test.rb | 2 +- .../test/generators/metal_generator_test.rb | 2 +- .../generators/migration_generator_test.rb | 2 +- .../test/generators/model_generator_test.rb | 2 +- railties/test/generators/named_base_test.rb | 2 +- .../generators/observer_generator_test.rb | 2 +- .../performance_test_generator_test.rb | 2 +- .../test/generators/plugin_generator_test.rb | 2 +- .../generators/resource_generator_test.rb | 2 +- .../scaffold_controller_generator_test.rb | 2 +- .../generators/scaffold_generator_test.rb | 2 +- .../session_migration_generator_test.rb | 2 +- .../generators/stylesheets_generator_test.rb | 2 +- 177 files changed, 77 insertions(+), 95 deletions(-) rename {railties/lib/rails => activerecord/lib}/generators/active_record.rb (72%) rename {railties/lib/rails => activerecord/lib}/generators/active_record/migration/migration_generator.rb (93%) rename {railties/lib/rails => activerecord/lib}/generators/active_record/migration/templates/migration.rb (100%) rename {railties/lib/rails => activerecord/lib}/generators/active_record/model/model_generator.rb (95%) rename {railties/lib/rails => activerecord/lib}/generators/active_record/model/templates/migration.rb (100%) rename {railties/lib/rails => activerecord/lib}/generators/active_record/model/templates/model.rb (100%) rename {railties/lib/rails => activerecord/lib}/generators/active_record/observer/observer_generator.rb (88%) rename {railties/lib/rails => activerecord/lib}/generators/active_record/observer/templates/observer.rb (100%) rename {railties/lib/rails => activerecord/lib}/generators/active_record/session_migration/session_migration_generator.rb (94%) rename {railties/lib/rails => activerecord/lib}/generators/active_record/session_migration/templates/migration.rb (100%) rename railties/lib/{rails => }/generators/erb.rb (100%) rename railties/lib/{rails => }/generators/erb/controller/controller_generator.rb (94%) rename railties/lib/{rails => }/generators/erb/controller/templates/view.html.erb (100%) rename railties/lib/{rails => }/generators/erb/mailer/mailer_generator.rb (93%) rename railties/lib/{rails => }/generators/erb/mailer/templates/view.erb (100%) rename railties/lib/{rails => }/generators/erb/scaffold/scaffold_generator.rb (97%) rename railties/lib/{rails => }/generators/erb/scaffold/templates/_form.html.erb (100%) rename railties/lib/{rails => }/generators/erb/scaffold/templates/edit.html.erb (100%) rename railties/lib/{rails => }/generators/erb/scaffold/templates/index.html.erb (100%) rename railties/lib/{rails => }/generators/erb/scaffold/templates/layout.html.erb (100%) rename railties/lib/{rails => }/generators/erb/scaffold/templates/new.html.erb (100%) rename railties/lib/{rails => }/generators/erb/scaffold/templates/show.html.erb (100%) rename railties/lib/{rails => }/generators/rails/app/USAGE (100%) rename railties/lib/{rails => }/generators/rails/app/app_generator.rb (97%) rename railties/lib/{rails => }/generators/rails/app/templates/Gemfile (100%) rename railties/lib/{rails => }/generators/rails/app/templates/README (100%) rename railties/lib/{rails => }/generators/rails/app/templates/Rakefile (100%) rename railties/lib/{rails => }/generators/rails/app/templates/app/controllers/application_controller.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/app/helpers/application_helper.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/app/models/.empty_directory (100%) rename railties/lib/{rails => }/generators/rails/app/templates/app/views/layouts/.empty_directory (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config.ru (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/application.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/boot.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/databases/frontbase.yml (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/databases/ibm_db.yml (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/databases/mysql.yml (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/databases/oracle.yml (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/databases/postgresql.yml (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/databases/sqlite3.yml (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/environment.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/environments/development.rb.tt (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/environments/production.rb.tt (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/environments/test.rb.tt (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/initializers/backtrace_silencers.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/initializers/inflections.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/initializers/mime_types.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/initializers/session_store.rb.tt (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/locales/en.yml (100%) rename railties/lib/{rails => }/generators/rails/app/templates/config/routes.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/db/seeds.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/doc/README_FOR_APP (100%) rename railties/lib/{rails => }/generators/rails/app/templates/gitignore (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/404.html (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/422.html (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/500.html (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/favicon.ico (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/images/rails.png (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/index.html (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/javascripts/application.js (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/javascripts/controls.js (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/javascripts/dragdrop.js (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/javascripts/effects.js (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/javascripts/prototype.js (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/robots.txt (100%) rename railties/lib/{rails => }/generators/rails/app/templates/public/stylesheets/.empty_directory (100%) rename railties/lib/{rails => }/generators/rails/app/templates/script/about (100%) rename railties/lib/{rails => }/generators/rails/app/templates/script/console.tt (100%) rename railties/lib/{rails => }/generators/rails/app/templates/script/dbconsole.tt (100%) rename railties/lib/{rails => }/generators/rails/app/templates/script/destroy (100%) rename railties/lib/{rails => }/generators/rails/app/templates/script/generate (100%) rename railties/lib/{rails => }/generators/rails/app/templates/script/performance/benchmarker (100%) rename railties/lib/{rails => }/generators/rails/app/templates/script/performance/profiler (100%) rename railties/lib/{rails => }/generators/rails/app/templates/script/plugin (100%) rename railties/lib/{rails => }/generators/rails/app/templates/script/runner (100%) rename railties/lib/{rails => }/generators/rails/app/templates/script/server.tt (100%) rename railties/lib/{rails => }/generators/rails/app/templates/test/fixtures/.empty_directory (100%) rename railties/lib/{rails => }/generators/rails/app/templates/test/functional/.empty_directory (100%) rename railties/lib/{rails => }/generators/rails/app/templates/test/integration/.empty_directory (100%) rename railties/lib/{rails => }/generators/rails/app/templates/test/performance/browsing_test.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/test/test_helper.rb (100%) rename railties/lib/{rails => }/generators/rails/app/templates/test/unit/.empty_directory (100%) rename railties/lib/{rails => }/generators/rails/controller/USAGE (100%) rename railties/lib/{rails => }/generators/rails/controller/controller_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/controller/templates/controller.rb (100%) rename railties/lib/{rails => }/generators/rails/generator/USAGE (100%) rename railties/lib/{rails => }/generators/rails/generator/generator_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/generator/templates/%file_name%_generator.rb.tt (100%) rename railties/lib/{rails => }/generators/rails/generator/templates/USAGE.tt (100%) rename railties/lib/{rails => }/generators/rails/generator/templates/templates/.empty_directory (100%) rename railties/lib/{rails => }/generators/rails/helper/USAGE (100%) rename railties/lib/{rails => }/generators/rails/helper/helper_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/helper/templates/helper.rb (100%) rename railties/lib/{rails => }/generators/rails/integration_test/USAGE (100%) rename railties/lib/{rails => }/generators/rails/integration_test/integration_test_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/mailer/USAGE (100%) rename railties/lib/{rails => }/generators/rails/mailer/mailer_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/mailer/templates/mailer.rb (100%) rename railties/lib/{rails => }/generators/rails/metal/USAGE (100%) rename railties/lib/{rails => }/generators/rails/metal/metal_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/metal/templates/metal.rb (100%) rename railties/lib/{rails => }/generators/rails/migration/USAGE (100%) rename railties/lib/{rails => }/generators/rails/migration/migration_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/model/USAGE (100%) rename railties/lib/{rails => }/generators/rails/model/model_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/model_subclass/model_subclass_generator.rb (83%) rename railties/lib/{rails => }/generators/rails/observer/USAGE (100%) rename railties/lib/{rails => }/generators/rails/observer/observer_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/performance_test/USAGE (100%) rename railties/lib/{rails => }/generators/rails/performance_test/performance_test_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/plugin/USAGE (100%) rename railties/lib/{rails => }/generators/rails/plugin/plugin_generator.rb (94%) rename railties/lib/{rails => }/generators/rails/plugin/templates/MIT-LICENSE.tt (100%) rename railties/lib/{rails => }/generators/rails/plugin/templates/README.tt (100%) rename railties/lib/{rails => }/generators/rails/plugin/templates/Rakefile.tt (100%) rename railties/lib/{rails => }/generators/rails/plugin/templates/init.rb (100%) rename railties/lib/{rails => }/generators/rails/plugin/templates/install.rb (100%) rename railties/lib/{rails => }/generators/rails/plugin/templates/lib/%file_name%.rb.tt (100%) rename railties/lib/{rails => }/generators/rails/plugin/templates/tasks/%file_name%_tasks.rake.tt (100%) rename railties/lib/{rails => }/generators/rails/plugin/templates/uninstall.rb (100%) rename railties/lib/{rails => }/generators/rails/resource/USAGE (100%) rename railties/lib/{rails => }/generators/rails/resource/resource_generator.rb (94%) rename railties/lib/{rails => }/generators/rails/scaffold/USAGE (100%) rename railties/lib/{rails => }/generators/rails/scaffold/scaffold_generator.rb (81%) rename railties/lib/{rails => }/generators/rails/scaffold_controller/USAGE (100%) rename railties/lib/{rails => }/generators/rails/scaffold_controller/scaffold_controller_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/scaffold_controller/templates/controller.rb (100%) rename railties/lib/{rails => }/generators/rails/session_migration/USAGE (100%) rename railties/lib/{rails => }/generators/rails/session_migration/session_migration_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/stylesheets/USAGE (100%) rename railties/lib/{rails => }/generators/rails/stylesheets/stylesheets_generator.rb (100%) rename railties/lib/{rails => }/generators/rails/stylesheets/templates/scaffold.css (100%) rename railties/lib/{rails => }/generators/test_unit.rb (100%) rename railties/lib/{rails => }/generators/test_unit/controller/controller_generator.rb (89%) rename railties/lib/{rails => }/generators/test_unit/controller/templates/functional_test.rb (100%) rename railties/lib/{rails => }/generators/test_unit/helper/helper_generator.rb (88%) rename railties/lib/{rails => }/generators/test_unit/helper/templates/helper_test.rb (100%) rename railties/lib/{rails => }/generators/test_unit/integration/integration_generator.rb (88%) rename railties/lib/{rails => }/generators/test_unit/integration/templates/integration_test.rb (100%) rename railties/lib/{rails => }/generators/test_unit/mailer/mailer_generator.rb (93%) rename railties/lib/{rails => }/generators/test_unit/mailer/templates/fixture (100%) rename railties/lib/{rails => }/generators/test_unit/mailer/templates/unit_test.rb (100%) rename railties/lib/{rails => }/generators/test_unit/model/model_generator.rb (94%) rename railties/lib/{rails => }/generators/test_unit/model/templates/fixtures.yml (100%) rename railties/lib/{rails => }/generators/test_unit/model/templates/unit_test.rb (100%) rename railties/lib/{rails => }/generators/test_unit/observer/observer_generator.rb (88%) rename railties/lib/{rails => }/generators/test_unit/observer/templates/unit_test.rb (100%) rename railties/lib/{rails => }/generators/test_unit/performance/performance_generator.rb (88%) rename railties/lib/{rails => }/generators/test_unit/performance/templates/performance_test.rb (100%) rename railties/lib/{rails => }/generators/test_unit/plugin/plugin_generator.rb (84%) rename railties/lib/{rails => }/generators/test_unit/plugin/templates/%file_name%_test.rb.tt (100%) rename railties/lib/{rails => }/generators/test_unit/plugin/templates/test_helper.rb (100%) rename railties/lib/{rails => }/generators/test_unit/scaffold/scaffold_generator.rb (93%) rename railties/lib/{rails => }/generators/test_unit/scaffold/templates/functional_test.rb (100%) diff --git a/railties/lib/rails/generators/active_record.rb b/activerecord/lib/generators/active_record.rb similarity index 72% rename from railties/lib/rails/generators/active_record.rb rename to activerecord/lib/generators/active_record.rb index c62f75c384e16..25b982f296ba1 100644 --- a/railties/lib/rails/generators/active_record.rb +++ b/activerecord/lib/generators/active_record.rb @@ -8,6 +8,14 @@ module Generators class Base < Rails::Generators::NamedBase #:nodoc: include Rails::Generators::Migration + def self.source_root + @_ar_source_root ||= begin + if base_name && generator_name + File.expand_path(File.join(base_name, generator_name, 'templates'), File.dirname(__FILE__)) + end + end + end + protected # Implement the required interface for Rails::Generators::Migration. # diff --git a/railties/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/generators/active_record/migration/migration_generator.rb similarity index 93% rename from railties/lib/rails/generators/active_record/migration/migration_generator.rb rename to activerecord/lib/generators/active_record/migration/migration_generator.rb index f6159deeebb8f..7939977f72332 100644 --- a/railties/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/generators/active_record/migration/migration_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/active_record' +require 'generators/active_record' module ActiveRecord module Generators diff --git a/railties/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/generators/active_record/migration/templates/migration.rb similarity index 100% rename from railties/lib/rails/generators/active_record/migration/templates/migration.rb rename to activerecord/lib/generators/active_record/migration/templates/migration.rb diff --git a/railties/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/generators/active_record/model/model_generator.rb similarity index 95% rename from railties/lib/rails/generators/active_record/model/model_generator.rb rename to activerecord/lib/generators/active_record/model/model_generator.rb index 3e72fbeca8dc1..2641083e0dce7 100644 --- a/railties/lib/rails/generators/active_record/model/model_generator.rb +++ b/activerecord/lib/generators/active_record/model/model_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/active_record' +require 'generators/active_record' module ActiveRecord module Generators diff --git a/railties/lib/rails/generators/active_record/model/templates/migration.rb b/activerecord/lib/generators/active_record/model/templates/migration.rb similarity index 100% rename from railties/lib/rails/generators/active_record/model/templates/migration.rb rename to activerecord/lib/generators/active_record/model/templates/migration.rb diff --git a/railties/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/generators/active_record/model/templates/model.rb similarity index 100% rename from railties/lib/rails/generators/active_record/model/templates/model.rb rename to activerecord/lib/generators/active_record/model/templates/model.rb diff --git a/railties/lib/rails/generators/active_record/observer/observer_generator.rb b/activerecord/lib/generators/active_record/observer/observer_generator.rb similarity index 88% rename from railties/lib/rails/generators/active_record/observer/observer_generator.rb rename to activerecord/lib/generators/active_record/observer/observer_generator.rb index c1c0e3f25b078..a6b57423b8aaf 100644 --- a/railties/lib/rails/generators/active_record/observer/observer_generator.rb +++ b/activerecord/lib/generators/active_record/observer/observer_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/active_record' +require 'generators/active_record' module ActiveRecord module Generators diff --git a/railties/lib/rails/generators/active_record/observer/templates/observer.rb b/activerecord/lib/generators/active_record/observer/templates/observer.rb similarity index 100% rename from railties/lib/rails/generators/active_record/observer/templates/observer.rb rename to activerecord/lib/generators/active_record/observer/templates/observer.rb diff --git a/railties/lib/rails/generators/active_record/session_migration/session_migration_generator.rb b/activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb similarity index 94% rename from railties/lib/rails/generators/active_record/session_migration/session_migration_generator.rb rename to activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb index afcda2a98a494..59c479206673d 100644 --- a/railties/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +++ b/activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/active_record' +require 'generators/active_record' module ActiveRecord module Generators diff --git a/railties/lib/rails/generators/active_record/session_migration/templates/migration.rb b/activerecord/lib/generators/active_record/session_migration/templates/migration.rb similarity index 100% rename from railties/lib/rails/generators/active_record/session_migration/templates/migration.rb rename to activerecord/lib/generators/active_record/session_migration/templates/migration.rb diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/generators/erb.rb similarity index 100% rename from railties/lib/rails/generators/erb.rb rename to railties/lib/generators/erb.rb diff --git a/railties/lib/rails/generators/erb/controller/controller_generator.rb b/railties/lib/generators/erb/controller/controller_generator.rb similarity index 94% rename from railties/lib/rails/generators/erb/controller/controller_generator.rb rename to railties/lib/generators/erb/controller/controller_generator.rb index f8780d9c330a1..ab7b273662433 100644 --- a/railties/lib/rails/generators/erb/controller/controller_generator.rb +++ b/railties/lib/generators/erb/controller/controller_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/erb' +require 'generators/erb' module Erb module Generators diff --git a/railties/lib/rails/generators/erb/controller/templates/view.html.erb b/railties/lib/generators/erb/controller/templates/view.html.erb similarity index 100% rename from railties/lib/rails/generators/erb/controller/templates/view.html.erb rename to railties/lib/generators/erb/controller/templates/view.html.erb diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/generators/erb/mailer/mailer_generator.rb similarity index 93% rename from railties/lib/rails/generators/erb/mailer/mailer_generator.rb rename to railties/lib/generators/erb/mailer/mailer_generator.rb index 5266259e2fcb1..4ec2f4c9f4565 100644 --- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb +++ b/railties/lib/generators/erb/mailer/mailer_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/erb' +require 'generators/erb' module Erb module Generators diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.erb b/railties/lib/generators/erb/mailer/templates/view.erb similarity index 100% rename from railties/lib/rails/generators/erb/mailer/templates/view.erb rename to railties/lib/generators/erb/mailer/templates/view.erb diff --git a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/generators/erb/scaffold/scaffold_generator.rb similarity index 97% rename from railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb rename to railties/lib/generators/erb/scaffold/scaffold_generator.rb index de5b0e9c5d968..846540476f26c 100644 --- a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb +++ b/railties/lib/generators/erb/scaffold/scaffold_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/erb' +require 'generators/erb' require 'rails/generators/resource_helpers' module Erb diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/generators/erb/scaffold/templates/_form.html.erb similarity index 100% rename from railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb rename to railties/lib/generators/erb/scaffold/templates/_form.html.erb diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/generators/erb/scaffold/templates/edit.html.erb similarity index 100% rename from railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb rename to railties/lib/generators/erb/scaffold/templates/edit.html.erb diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/generators/erb/scaffold/templates/index.html.erb similarity index 100% rename from railties/lib/rails/generators/erb/scaffold/templates/index.html.erb rename to railties/lib/generators/erb/scaffold/templates/index.html.erb diff --git a/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb b/railties/lib/generators/erb/scaffold/templates/layout.html.erb similarity index 100% rename from railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb rename to railties/lib/generators/erb/scaffold/templates/layout.html.erb diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/generators/erb/scaffold/templates/new.html.erb similarity index 100% rename from railties/lib/rails/generators/erb/scaffold/templates/new.html.erb rename to railties/lib/generators/erb/scaffold/templates/new.html.erb diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/generators/erb/scaffold/templates/show.html.erb similarity index 100% rename from railties/lib/rails/generators/erb/scaffold/templates/show.html.erb rename to railties/lib/generators/erb/scaffold/templates/show.html.erb diff --git a/railties/lib/rails/generators/rails/app/USAGE b/railties/lib/generators/rails/app/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/app/USAGE rename to railties/lib/generators/rails/app/USAGE diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/generators/rails/app/app_generator.rb similarity index 97% rename from railties/lib/rails/generators/rails/app/app_generator.rb rename to railties/lib/generators/rails/app/app_generator.rb index 4b73576b07d97..2e34992b3be54 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/generators/rails/app/app_generator.rb @@ -5,7 +5,7 @@ module Rails::Generators # We need to store the RAILS_DEV_PATH in a constant, otherwise the path # can change in Ruby 1.8.7 when we FileUtils.cd. - RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__)) + RAILS_DEV_PATH = File.expand_path("../../../../..", File.dirname(__FILE__)) class AppGenerator < Base DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) @@ -35,7 +35,7 @@ class AppGenerator < Base :desc => "Skip Prototype files" class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false, - :desc => "Skip Git ignores and keeps" + :desc => "Skip Git ignores and keeps" # Add bin/rails options class_option :version, :type => :boolean, :aliases => "-v", :group => :rails, diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/generators/rails/app/templates/Gemfile similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/Gemfile rename to railties/lib/generators/rails/app/templates/Gemfile diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/generators/rails/app/templates/README similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/README rename to railties/lib/generators/rails/app/templates/README diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/generators/rails/app/templates/Rakefile similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/Rakefile rename to railties/lib/generators/rails/app/templates/Rakefile diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb b/railties/lib/generators/rails/app/templates/app/controllers/application_controller.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb rename to railties/lib/generators/rails/app/templates/app/controllers/application_controller.rb diff --git a/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb b/railties/lib/generators/rails/app/templates/app/helpers/application_helper.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb rename to railties/lib/generators/rails/app/templates/app/helpers/application_helper.rb diff --git a/railties/lib/rails/generators/rails/app/templates/app/models/.empty_directory b/railties/lib/generators/rails/app/templates/app/models/.empty_directory similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/app/models/.empty_directory rename to railties/lib/generators/rails/app/templates/app/models/.empty_directory diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/.empty_directory b/railties/lib/generators/rails/app/templates/app/views/layouts/.empty_directory similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/app/views/layouts/.empty_directory rename to railties/lib/generators/rails/app/templates/app/views/layouts/.empty_directory diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/generators/rails/app/templates/config.ru similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config.ru rename to railties/lib/generators/rails/app/templates/config.ru diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/generators/rails/app/templates/config/application.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/application.rb rename to railties/lib/generators/rails/app/templates/config/application.rb diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/generators/rails/app/templates/config/boot.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/boot.rb rename to railties/lib/generators/rails/app/templates/config/boot.rb diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/generators/rails/app/templates/config/databases/frontbase.yml similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml rename to railties/lib/generators/rails/app/templates/config/databases/frontbase.yml diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/generators/rails/app/templates/config/databases/ibm_db.yml similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml rename to railties/lib/generators/rails/app/templates/config/databases/ibm_db.yml diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/generators/rails/app/templates/config/databases/mysql.yml similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml rename to railties/lib/generators/rails/app/templates/config/databases/mysql.yml diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/generators/rails/app/templates/config/databases/oracle.yml similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml rename to railties/lib/generators/rails/app/templates/config/databases/oracle.yml diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/generators/rails/app/templates/config/databases/postgresql.yml similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml rename to railties/lib/generators/rails/app/templates/config/databases/postgresql.yml diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/generators/rails/app/templates/config/databases/sqlite3.yml similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml rename to railties/lib/generators/rails/app/templates/config/databases/sqlite3.yml diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/generators/rails/app/templates/config/environment.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/environment.rb rename to railties/lib/generators/rails/app/templates/config/environment.rb diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/generators/rails/app/templates/config/environments/development.rb.tt similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt rename to railties/lib/generators/rails/app/templates/config/environments/development.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/generators/rails/app/templates/config/environments/production.rb.tt similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt rename to railties/lib/generators/rails/app/templates/config/environments/production.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/generators/rails/app/templates/config/environments/test.rb.tt similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt rename to railties/lib/generators/rails/app/templates/config/environments/test.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb b/railties/lib/generators/rails/app/templates/config/initializers/backtrace_silencers.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb rename to railties/lib/generators/rails/app/templates/config/initializers/backtrace_silencers.rb diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt b/railties/lib/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt rename to railties/lib/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb b/railties/lib/generators/rails/app/templates/config/initializers/inflections.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb rename to railties/lib/generators/rails/app/templates/config/initializers/inflections.rb diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb b/railties/lib/generators/rails/app/templates/config/initializers/mime_types.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb rename to railties/lib/generators/rails/app/templates/config/initializers/mime_types.rb diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/generators/rails/app/templates/config/initializers/session_store.rb.tt similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt rename to railties/lib/generators/rails/app/templates/config/initializers/session_store.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml b/railties/lib/generators/rails/app/templates/config/locales/en.yml similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/locales/en.yml rename to railties/lib/generators/rails/app/templates/config/locales/en.yml diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/generators/rails/app/templates/config/routes.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/config/routes.rb rename to railties/lib/generators/rails/app/templates/config/routes.rb diff --git a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb b/railties/lib/generators/rails/app/templates/db/seeds.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/db/seeds.rb rename to railties/lib/generators/rails/app/templates/db/seeds.rb diff --git a/railties/lib/rails/generators/rails/app/templates/doc/README_FOR_APP b/railties/lib/generators/rails/app/templates/doc/README_FOR_APP similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/doc/README_FOR_APP rename to railties/lib/generators/rails/app/templates/doc/README_FOR_APP diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/generators/rails/app/templates/gitignore similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/gitignore rename to railties/lib/generators/rails/app/templates/gitignore diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/generators/rails/app/templates/public/404.html similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/404.html rename to railties/lib/generators/rails/app/templates/public/404.html diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/generators/rails/app/templates/public/422.html similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/422.html rename to railties/lib/generators/rails/app/templates/public/422.html diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/generators/rails/app/templates/public/500.html similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/500.html rename to railties/lib/generators/rails/app/templates/public/500.html diff --git a/railties/lib/rails/generators/rails/app/templates/public/favicon.ico b/railties/lib/generators/rails/app/templates/public/favicon.ico similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/favicon.ico rename to railties/lib/generators/rails/app/templates/public/favicon.ico diff --git a/railties/lib/rails/generators/rails/app/templates/public/images/rails.png b/railties/lib/generators/rails/app/templates/public/images/rails.png similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/images/rails.png rename to railties/lib/generators/rails/app/templates/public/images/rails.png diff --git a/railties/lib/rails/generators/rails/app/templates/public/index.html b/railties/lib/generators/rails/app/templates/public/index.html similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/index.html rename to railties/lib/generators/rails/app/templates/public/index.html diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js b/railties/lib/generators/rails/app/templates/public/javascripts/application.js similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js rename to railties/lib/generators/rails/app/templates/public/javascripts/application.js diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/controls.js b/railties/lib/generators/rails/app/templates/public/javascripts/controls.js similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/javascripts/controls.js rename to railties/lib/generators/rails/app/templates/public/javascripts/controls.js diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/dragdrop.js b/railties/lib/generators/rails/app/templates/public/javascripts/dragdrop.js similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/javascripts/dragdrop.js rename to railties/lib/generators/rails/app/templates/public/javascripts/dragdrop.js diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/effects.js b/railties/lib/generators/rails/app/templates/public/javascripts/effects.js similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/javascripts/effects.js rename to railties/lib/generators/rails/app/templates/public/javascripts/effects.js diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js b/railties/lib/generators/rails/app/templates/public/javascripts/prototype.js similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js rename to railties/lib/generators/rails/app/templates/public/javascripts/prototype.js diff --git a/railties/lib/rails/generators/rails/app/templates/public/robots.txt b/railties/lib/generators/rails/app/templates/public/robots.txt similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/robots.txt rename to railties/lib/generators/rails/app/templates/public/robots.txt diff --git a/railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory b/railties/lib/generators/rails/app/templates/public/stylesheets/.empty_directory similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory rename to railties/lib/generators/rails/app/templates/public/stylesheets/.empty_directory diff --git a/railties/lib/rails/generators/rails/app/templates/script/about b/railties/lib/generators/rails/app/templates/script/about similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/script/about rename to railties/lib/generators/rails/app/templates/script/about diff --git a/railties/lib/rails/generators/rails/app/templates/script/console.tt b/railties/lib/generators/rails/app/templates/script/console.tt similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/script/console.tt rename to railties/lib/generators/rails/app/templates/script/console.tt diff --git a/railties/lib/rails/generators/rails/app/templates/script/dbconsole.tt b/railties/lib/generators/rails/app/templates/script/dbconsole.tt similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/script/dbconsole.tt rename to railties/lib/generators/rails/app/templates/script/dbconsole.tt diff --git a/railties/lib/rails/generators/rails/app/templates/script/destroy b/railties/lib/generators/rails/app/templates/script/destroy similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/script/destroy rename to railties/lib/generators/rails/app/templates/script/destroy diff --git a/railties/lib/rails/generators/rails/app/templates/script/generate b/railties/lib/generators/rails/app/templates/script/generate similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/script/generate rename to railties/lib/generators/rails/app/templates/script/generate diff --git a/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker b/railties/lib/generators/rails/app/templates/script/performance/benchmarker similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker rename to railties/lib/generators/rails/app/templates/script/performance/benchmarker diff --git a/railties/lib/rails/generators/rails/app/templates/script/performance/profiler b/railties/lib/generators/rails/app/templates/script/performance/profiler similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/script/performance/profiler rename to railties/lib/generators/rails/app/templates/script/performance/profiler diff --git a/railties/lib/rails/generators/rails/app/templates/script/plugin b/railties/lib/generators/rails/app/templates/script/plugin similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/script/plugin rename to railties/lib/generators/rails/app/templates/script/plugin diff --git a/railties/lib/rails/generators/rails/app/templates/script/runner b/railties/lib/generators/rails/app/templates/script/runner similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/script/runner rename to railties/lib/generators/rails/app/templates/script/runner diff --git a/railties/lib/rails/generators/rails/app/templates/script/server.tt b/railties/lib/generators/rails/app/templates/script/server.tt similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/script/server.tt rename to railties/lib/generators/rails/app/templates/script/server.tt diff --git a/railties/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory b/railties/lib/generators/rails/app/templates/test/fixtures/.empty_directory similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory rename to railties/lib/generators/rails/app/templates/test/fixtures/.empty_directory diff --git a/railties/lib/rails/generators/rails/app/templates/test/functional/.empty_directory b/railties/lib/generators/rails/app/templates/test/functional/.empty_directory similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/test/functional/.empty_directory rename to railties/lib/generators/rails/app/templates/test/functional/.empty_directory diff --git a/railties/lib/rails/generators/rails/app/templates/test/integration/.empty_directory b/railties/lib/generators/rails/app/templates/test/integration/.empty_directory similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/test/integration/.empty_directory rename to railties/lib/generators/rails/app/templates/test/integration/.empty_directory diff --git a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb b/railties/lib/generators/rails/app/templates/test/performance/browsing_test.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb rename to railties/lib/generators/rails/app/templates/test/performance/browsing_test.rb diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/generators/rails/app/templates/test/test_helper.rb similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/test/test_helper.rb rename to railties/lib/generators/rails/app/templates/test/test_helper.rb diff --git a/railties/lib/rails/generators/rails/app/templates/test/unit/.empty_directory b/railties/lib/generators/rails/app/templates/test/unit/.empty_directory similarity index 100% rename from railties/lib/rails/generators/rails/app/templates/test/unit/.empty_directory rename to railties/lib/generators/rails/app/templates/test/unit/.empty_directory diff --git a/railties/lib/rails/generators/rails/controller/USAGE b/railties/lib/generators/rails/controller/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/controller/USAGE rename to railties/lib/generators/rails/controller/USAGE diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/generators/rails/controller/controller_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/controller/controller_generator.rb rename to railties/lib/generators/rails/controller/controller_generator.rb diff --git a/railties/lib/rails/generators/rails/controller/templates/controller.rb b/railties/lib/generators/rails/controller/templates/controller.rb similarity index 100% rename from railties/lib/rails/generators/rails/controller/templates/controller.rb rename to railties/lib/generators/rails/controller/templates/controller.rb diff --git a/railties/lib/rails/generators/rails/generator/USAGE b/railties/lib/generators/rails/generator/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/generator/USAGE rename to railties/lib/generators/rails/generator/USAGE diff --git a/railties/lib/rails/generators/rails/generator/generator_generator.rb b/railties/lib/generators/rails/generator/generator_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/generator/generator_generator.rb rename to railties/lib/generators/rails/generator/generator_generator.rb diff --git a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt b/railties/lib/generators/rails/generator/templates/%file_name%_generator.rb.tt similarity index 100% rename from railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt rename to railties/lib/generators/rails/generator/templates/%file_name%_generator.rb.tt diff --git a/railties/lib/rails/generators/rails/generator/templates/USAGE.tt b/railties/lib/generators/rails/generator/templates/USAGE.tt similarity index 100% rename from railties/lib/rails/generators/rails/generator/templates/USAGE.tt rename to railties/lib/generators/rails/generator/templates/USAGE.tt diff --git a/railties/lib/rails/generators/rails/generator/templates/templates/.empty_directory b/railties/lib/generators/rails/generator/templates/templates/.empty_directory similarity index 100% rename from railties/lib/rails/generators/rails/generator/templates/templates/.empty_directory rename to railties/lib/generators/rails/generator/templates/templates/.empty_directory diff --git a/railties/lib/rails/generators/rails/helper/USAGE b/railties/lib/generators/rails/helper/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/helper/USAGE rename to railties/lib/generators/rails/helper/USAGE diff --git a/railties/lib/rails/generators/rails/helper/helper_generator.rb b/railties/lib/generators/rails/helper/helper_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/helper/helper_generator.rb rename to railties/lib/generators/rails/helper/helper_generator.rb diff --git a/railties/lib/rails/generators/rails/helper/templates/helper.rb b/railties/lib/generators/rails/helper/templates/helper.rb similarity index 100% rename from railties/lib/rails/generators/rails/helper/templates/helper.rb rename to railties/lib/generators/rails/helper/templates/helper.rb diff --git a/railties/lib/rails/generators/rails/integration_test/USAGE b/railties/lib/generators/rails/integration_test/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/integration_test/USAGE rename to railties/lib/generators/rails/integration_test/USAGE diff --git a/railties/lib/rails/generators/rails/integration_test/integration_test_generator.rb b/railties/lib/generators/rails/integration_test/integration_test_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/integration_test/integration_test_generator.rb rename to railties/lib/generators/rails/integration_test/integration_test_generator.rb diff --git a/railties/lib/rails/generators/rails/mailer/USAGE b/railties/lib/generators/rails/mailer/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/mailer/USAGE rename to railties/lib/generators/rails/mailer/USAGE diff --git a/railties/lib/rails/generators/rails/mailer/mailer_generator.rb b/railties/lib/generators/rails/mailer/mailer_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/mailer/mailer_generator.rb rename to railties/lib/generators/rails/mailer/mailer_generator.rb diff --git a/railties/lib/rails/generators/rails/mailer/templates/mailer.rb b/railties/lib/generators/rails/mailer/templates/mailer.rb similarity index 100% rename from railties/lib/rails/generators/rails/mailer/templates/mailer.rb rename to railties/lib/generators/rails/mailer/templates/mailer.rb diff --git a/railties/lib/rails/generators/rails/metal/USAGE b/railties/lib/generators/rails/metal/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/metal/USAGE rename to railties/lib/generators/rails/metal/USAGE diff --git a/railties/lib/rails/generators/rails/metal/metal_generator.rb b/railties/lib/generators/rails/metal/metal_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/metal/metal_generator.rb rename to railties/lib/generators/rails/metal/metal_generator.rb diff --git a/railties/lib/rails/generators/rails/metal/templates/metal.rb b/railties/lib/generators/rails/metal/templates/metal.rb similarity index 100% rename from railties/lib/rails/generators/rails/metal/templates/metal.rb rename to railties/lib/generators/rails/metal/templates/metal.rb diff --git a/railties/lib/rails/generators/rails/migration/USAGE b/railties/lib/generators/rails/migration/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/migration/USAGE rename to railties/lib/generators/rails/migration/USAGE diff --git a/railties/lib/rails/generators/rails/migration/migration_generator.rb b/railties/lib/generators/rails/migration/migration_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/migration/migration_generator.rb rename to railties/lib/generators/rails/migration/migration_generator.rb diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/generators/rails/model/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/model/USAGE rename to railties/lib/generators/rails/model/USAGE diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/generators/rails/model/model_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/model/model_generator.rb rename to railties/lib/generators/rails/model/model_generator.rb diff --git a/railties/lib/rails/generators/rails/model_subclass/model_subclass_generator.rb b/railties/lib/generators/rails/model_subclass/model_subclass_generator.rb similarity index 83% rename from railties/lib/rails/generators/rails/model_subclass/model_subclass_generator.rb rename to railties/lib/generators/rails/model_subclass/model_subclass_generator.rb index 46497097804bb..99fd2f45bcab5 100644 --- a/railties/lib/rails/generators/rails/model_subclass/model_subclass_generator.rb +++ b/railties/lib/generators/rails/model_subclass/model_subclass_generator.rb @@ -1,5 +1,6 @@ module Rails module Generators + # TODO Deprecate me in a release > Rails 3.0 class ModelSubclassGenerator < Base desc "model_subclass is deprecated. Invoke model with --parent option instead." diff --git a/railties/lib/rails/generators/rails/observer/USAGE b/railties/lib/generators/rails/observer/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/observer/USAGE rename to railties/lib/generators/rails/observer/USAGE diff --git a/railties/lib/rails/generators/rails/observer/observer_generator.rb b/railties/lib/generators/rails/observer/observer_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/observer/observer_generator.rb rename to railties/lib/generators/rails/observer/observer_generator.rb diff --git a/railties/lib/rails/generators/rails/performance_test/USAGE b/railties/lib/generators/rails/performance_test/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/performance_test/USAGE rename to railties/lib/generators/rails/performance_test/USAGE diff --git a/railties/lib/rails/generators/rails/performance_test/performance_test_generator.rb b/railties/lib/generators/rails/performance_test/performance_test_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/performance_test/performance_test_generator.rb rename to railties/lib/generators/rails/performance_test/performance_test_generator.rb diff --git a/railties/lib/rails/generators/rails/plugin/USAGE b/railties/lib/generators/rails/plugin/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/plugin/USAGE rename to railties/lib/generators/rails/plugin/USAGE diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/generators/rails/plugin/plugin_generator.rb similarity index 94% rename from railties/lib/rails/generators/rails/plugin/plugin_generator.rb rename to railties/lib/generators/rails/plugin/plugin_generator.rb index bc614bc5d3296..ee785caf7d5c6 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/generators/rails/plugin/plugin_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/rails/generator/generator_generator' +require 'generators/rails/generator/generator_generator' module Rails module Generators diff --git a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt b/railties/lib/generators/rails/plugin/templates/MIT-LICENSE.tt similarity index 100% rename from railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt rename to railties/lib/generators/rails/plugin/templates/MIT-LICENSE.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/README.tt b/railties/lib/generators/rails/plugin/templates/README.tt similarity index 100% rename from railties/lib/rails/generators/rails/plugin/templates/README.tt rename to railties/lib/generators/rails/plugin/templates/README.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt b/railties/lib/generators/rails/plugin/templates/Rakefile.tt similarity index 100% rename from railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt rename to railties/lib/generators/rails/plugin/templates/Rakefile.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/init.rb b/railties/lib/generators/rails/plugin/templates/init.rb similarity index 100% rename from railties/lib/rails/generators/rails/plugin/templates/init.rb rename to railties/lib/generators/rails/plugin/templates/init.rb diff --git a/railties/lib/rails/generators/rails/plugin/templates/install.rb b/railties/lib/generators/rails/plugin/templates/install.rb similarity index 100% rename from railties/lib/rails/generators/rails/plugin/templates/install.rb rename to railties/lib/generators/rails/plugin/templates/install.rb diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%file_name%.rb.tt b/railties/lib/generators/rails/plugin/templates/lib/%file_name%.rb.tt similarity index 100% rename from railties/lib/rails/generators/rails/plugin/templates/lib/%file_name%.rb.tt rename to railties/lib/generators/rails/plugin/templates/lib/%file_name%.rb.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/tasks/%file_name%_tasks.rake.tt b/railties/lib/generators/rails/plugin/templates/tasks/%file_name%_tasks.rake.tt similarity index 100% rename from railties/lib/rails/generators/rails/plugin/templates/tasks/%file_name%_tasks.rake.tt rename to railties/lib/generators/rails/plugin/templates/tasks/%file_name%_tasks.rake.tt diff --git a/railties/lib/rails/generators/rails/plugin/templates/uninstall.rb b/railties/lib/generators/rails/plugin/templates/uninstall.rb similarity index 100% rename from railties/lib/rails/generators/rails/plugin/templates/uninstall.rb rename to railties/lib/generators/rails/plugin/templates/uninstall.rb diff --git a/railties/lib/rails/generators/rails/resource/USAGE b/railties/lib/generators/rails/resource/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/resource/USAGE rename to railties/lib/generators/rails/resource/USAGE diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/generators/rails/resource/resource_generator.rb similarity index 94% rename from railties/lib/rails/generators/rails/resource/resource_generator.rb rename to railties/lib/generators/rails/resource/resource_generator.rb index a89ce7faed084..43c7cc85f4d94 100644 --- a/railties/lib/rails/generators/rails/resource/resource_generator.rb +++ b/railties/lib/generators/rails/resource/resource_generator.rb @@ -1,5 +1,5 @@ -require 'rails/generators/rails/model/model_generator' require 'rails/generators/resource_helpers' +require 'generators/rails/model/model_generator' module Rails module Generators diff --git a/railties/lib/rails/generators/rails/scaffold/USAGE b/railties/lib/generators/rails/scaffold/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/scaffold/USAGE rename to railties/lib/generators/rails/scaffold/USAGE diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/generators/rails/scaffold/scaffold_generator.rb similarity index 81% rename from railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb rename to railties/lib/generators/rails/scaffold/scaffold_generator.rb index 779f9337853e8..fdea5bf52ba53 100644 --- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb +++ b/railties/lib/generators/rails/scaffold/scaffold_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/rails/resource/resource_generator' +require 'generators/rails/resource/resource_generator' module Rails module Generators diff --git a/railties/lib/rails/generators/rails/scaffold_controller/USAGE b/railties/lib/generators/rails/scaffold_controller/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/scaffold_controller/USAGE rename to railties/lib/generators/rails/scaffold_controller/USAGE diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/generators/rails/scaffold_controller/scaffold_controller_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb rename to railties/lib/generators/rails/scaffold_controller/scaffold_controller_generator.rb diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/generators/rails/scaffold_controller/templates/controller.rb similarity index 100% rename from railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb rename to railties/lib/generators/rails/scaffold_controller/templates/controller.rb diff --git a/railties/lib/rails/generators/rails/session_migration/USAGE b/railties/lib/generators/rails/session_migration/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/session_migration/USAGE rename to railties/lib/generators/rails/session_migration/USAGE diff --git a/railties/lib/rails/generators/rails/session_migration/session_migration_generator.rb b/railties/lib/generators/rails/session_migration/session_migration_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/session_migration/session_migration_generator.rb rename to railties/lib/generators/rails/session_migration/session_migration_generator.rb diff --git a/railties/lib/rails/generators/rails/stylesheets/USAGE b/railties/lib/generators/rails/stylesheets/USAGE similarity index 100% rename from railties/lib/rails/generators/rails/stylesheets/USAGE rename to railties/lib/generators/rails/stylesheets/USAGE diff --git a/railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb b/railties/lib/generators/rails/stylesheets/stylesheets_generator.rb similarity index 100% rename from railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb rename to railties/lib/generators/rails/stylesheets/stylesheets_generator.rb diff --git a/railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css b/railties/lib/generators/rails/stylesheets/templates/scaffold.css similarity index 100% rename from railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css rename to railties/lib/generators/rails/stylesheets/templates/scaffold.css diff --git a/railties/lib/rails/generators/test_unit.rb b/railties/lib/generators/test_unit.rb similarity index 100% rename from railties/lib/rails/generators/test_unit.rb rename to railties/lib/generators/test_unit.rb diff --git a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb b/railties/lib/generators/test_unit/controller/controller_generator.rb similarity index 89% rename from railties/lib/rails/generators/test_unit/controller/controller_generator.rb rename to railties/lib/generators/test_unit/controller/controller_generator.rb index 39816d999058d..b57a6e794fabe 100644 --- a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb +++ b/railties/lib/generators/test_unit/controller/controller_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require 'generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/generators/test_unit/controller/templates/functional_test.rb similarity index 100% rename from railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb rename to railties/lib/generators/test_unit/controller/templates/functional_test.rb diff --git a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb b/railties/lib/generators/test_unit/helper/helper_generator.rb similarity index 88% rename from railties/lib/rails/generators/test_unit/helper/helper_generator.rb rename to railties/lib/generators/test_unit/helper/helper_generator.rb index 4ea80bf7be29e..9ecfaa45ab5b5 100644 --- a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb +++ b/railties/lib/generators/test_unit/helper/helper_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require 'generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/rails/generators/test_unit/helper/templates/helper_test.rb b/railties/lib/generators/test_unit/helper/templates/helper_test.rb similarity index 100% rename from railties/lib/rails/generators/test_unit/helper/templates/helper_test.rb rename to railties/lib/generators/test_unit/helper/templates/helper_test.rb diff --git a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb b/railties/lib/generators/test_unit/integration/integration_generator.rb similarity index 88% rename from railties/lib/rails/generators/test_unit/integration/integration_generator.rb rename to railties/lib/generators/test_unit/integration/integration_generator.rb index 32d0fac0294d6..d9d9b3bf1df56 100644 --- a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb +++ b/railties/lib/generators/test_unit/integration/integration_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require 'generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/generators/test_unit/integration/templates/integration_test.rb similarity index 100% rename from railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb rename to railties/lib/generators/test_unit/integration/templates/integration_test.rb diff --git a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/generators/test_unit/mailer/mailer_generator.rb similarity index 93% rename from railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb rename to railties/lib/generators/test_unit/mailer/mailer_generator.rb index 7353e5d61ab0f..ef350a622451a 100644 --- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb +++ b/railties/lib/generators/test_unit/mailer/mailer_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require 'generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/fixture b/railties/lib/generators/test_unit/mailer/templates/fixture similarity index 100% rename from railties/lib/rails/generators/test_unit/mailer/templates/fixture rename to railties/lib/generators/test_unit/mailer/templates/fixture diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/unit_test.rb b/railties/lib/generators/test_unit/mailer/templates/unit_test.rb similarity index 100% rename from railties/lib/rails/generators/test_unit/mailer/templates/unit_test.rb rename to railties/lib/generators/test_unit/mailer/templates/unit_test.rb diff --git a/railties/lib/rails/generators/test_unit/model/model_generator.rb b/railties/lib/generators/test_unit/model/model_generator.rb similarity index 94% rename from railties/lib/rails/generators/test_unit/model/model_generator.rb rename to railties/lib/generators/test_unit/model/model_generator.rb index 609b81568319b..469306e6c53c1 100644 --- a/railties/lib/rails/generators/test_unit/model/model_generator.rb +++ b/railties/lib/generators/test_unit/model/model_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require 'generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/generators/test_unit/model/templates/fixtures.yml similarity index 100% rename from railties/lib/rails/generators/test_unit/model/templates/fixtures.yml rename to railties/lib/generators/test_unit/model/templates/fixtures.yml diff --git a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb b/railties/lib/generators/test_unit/model/templates/unit_test.rb similarity index 100% rename from railties/lib/rails/generators/test_unit/model/templates/unit_test.rb rename to railties/lib/generators/test_unit/model/templates/unit_test.rb diff --git a/railties/lib/rails/generators/test_unit/observer/observer_generator.rb b/railties/lib/generators/test_unit/observer/observer_generator.rb similarity index 88% rename from railties/lib/rails/generators/test_unit/observer/observer_generator.rb rename to railties/lib/generators/test_unit/observer/observer_generator.rb index 6cc1158c21a28..14181f4e49498 100644 --- a/railties/lib/rails/generators/test_unit/observer/observer_generator.rb +++ b/railties/lib/generators/test_unit/observer/observer_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require 'generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb b/railties/lib/generators/test_unit/observer/templates/unit_test.rb similarity index 100% rename from railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb rename to railties/lib/generators/test_unit/observer/templates/unit_test.rb diff --git a/railties/lib/rails/generators/test_unit/performance/performance_generator.rb b/railties/lib/generators/test_unit/performance/performance_generator.rb similarity index 88% rename from railties/lib/rails/generators/test_unit/performance/performance_generator.rb rename to railties/lib/generators/test_unit/performance/performance_generator.rb index 99edda5461173..0d9c646b268ab 100644 --- a/railties/lib/rails/generators/test_unit/performance/performance_generator.rb +++ b/railties/lib/generators/test_unit/performance/performance_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require 'generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb b/railties/lib/generators/test_unit/performance/templates/performance_test.rb similarity index 100% rename from railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb rename to railties/lib/generators/test_unit/performance/templates/performance_test.rb diff --git a/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb b/railties/lib/generators/test_unit/plugin/plugin_generator.rb similarity index 84% rename from railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb rename to railties/lib/generators/test_unit/plugin/plugin_generator.rb index 4d65cd7d89341..05adf58c4f0a5 100644 --- a/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb +++ b/railties/lib/generators/test_unit/plugin/plugin_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require 'generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt b/railties/lib/generators/test_unit/plugin/templates/%file_name%_test.rb.tt similarity index 100% rename from railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt rename to railties/lib/generators/test_unit/plugin/templates/%file_name%_test.rb.tt diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb b/railties/lib/generators/test_unit/plugin/templates/test_helper.rb similarity index 100% rename from railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb rename to railties/lib/generators/test_unit/plugin/templates/test_helper.rb diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/generators/test_unit/scaffold/scaffold_generator.rb similarity index 93% rename from railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb rename to railties/lib/generators/test_unit/scaffold/scaffold_generator.rb index c0315c7fe6f76..a95916ae1397e 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/generators/test_unit/scaffold/scaffold_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/test_unit' +require 'generators/test_unit' require 'rails/generators/resource_helpers' module TestUnit diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/generators/test_unit/scaffold/templates/functional_test.rb similarity index 100% rename from railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb rename to railties/lib/generators/test_unit/scaffold/templates/functional_test.rb diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index e8c7a0993b5d5..45ae1fd30ab09 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -87,18 +87,6 @@ def self.options #:nodoc: @options ||= DEFAULT_OPTIONS.dup end - def self.gems_generators_paths #:nodoc: - return [] unless defined?(Gem) && Gem.respond_to?(:loaded_specs) - Gem.loaded_specs.inject([]) do |paths, (name, spec)| - paths += Dir[File.join(spec.full_gem_path, "lib/{generators,rails_generators}")] - end - end - - def self.plugins_generators_paths #:nodoc: - return [] unless defined?(Rails.root) && Rails.root - Dir[File.join(Rails.root, "vendor", "plugins", "*", "lib", "{generators,rails_generators}")] - end - # Hold configured generators fallbacks. If a plugin developer wants a # generator group to fallback to another group in case of missing generators, # they can add a fallback. @@ -126,31 +114,6 @@ def self.subclasses @subclasses ||= [] end - # Generators load paths used on lookup. The lookup happens as: - # - # 1) lib generators - # 2) vendor/plugin generators - # 3) vendor/gems generators - # 4) ~/rails/generators - # 5) rubygems generators - # 6) builtin generators - # - # TODO Remove hardcoded paths for all, except (6). - # - def self.load_paths - @load_paths ||= begin - paths = [] - paths += Dir[File.join(Rails.root, "lib", "{generators,rails_generators}")] if defined?(Rails.root) && Rails.root - paths += Dir[File.join(Thor::Util.user_home, ".rails", "{generators,rails_generators}")] - paths += self.plugins_generators_paths - paths += self.gems_generators_paths - paths << File.expand_path(File.join(File.dirname(__FILE__), "generators")) - paths.uniq! - paths - end - end - load_paths # Cache load paths. Needed to avoid __FILE__ pointing to wrong paths. - # Rails finds namespaces similar to thor, it only adds one rule: # # Generators names must end with "_generator.rb". This is required because Rails @@ -170,6 +133,12 @@ def self.load_paths def self.find_by_namespace(name, base=nil, context=nil) #:nodoc: base = "rails" unless base || context || name.to_s.include?(?:) + lookups = [] + lookups << "#{base}:#{name}" if base + lookups << "#{name}:#{context}" if context + lookups << "#{name}" + lookups << "rails:#{name}" unless base || context || name.to_s.include?(?:) + # Mount regexps to lookup regexps = [] regexps << /^#{base}:[\w:]*#{name}$/ if base @@ -183,7 +152,7 @@ def self.find_by_namespace(name, base=nil, context=nil) #:nodoc: return klass if klass # Try to require other generators by looking in load_paths - lookup(name, context) + lookup(lookups) unchecked = subclasses - checked klass = find_by_regexps(regexps, unchecked) return klass if klass @@ -255,22 +224,27 @@ def self.invoke_fallbacks_for(name, base) #:nodoc: # Receives namespaces in an array and tries to find matching generators # in the load path. - def self.lookup(*attempts) #:nodoc: + def self.lookup(attempts) #:nodoc: attempts.compact! attempts.uniq! - attempts = "{#{attempts.join(',')}}_generator.rb" - - self.load_paths.each do |path| - Dir[File.join(path, '**', attempts)].each do |file| - begin - require file - rescue NameError => e - raise unless e.message =~ /Rails::Generator/ - warn "[WARNING] Could not load generator at #{file.inspect} because it's a Rails 2.x generator, which is not supported anymore" - rescue Exception => e - warn "[WARNING] Could not load generator at #{file.inspect}. Error: #{e.message}" - end - end + + attempts.each do |attempt| + last = attempt.split(':').last + + # TODO Support rails_generators + require_generator "generators/#{attempt.gsub(':', '/')}/#{last}_generator" + require_generator "generators/#{attempt.gsub(':', '/')}_generator" + end + end + + def self.require_generator(path) + begin + require path + rescue LoadError => e + raise unless e.message =~ /#{Regexp.escape(path)}$/ + rescue NameError => e + raise unless e.message =~ /Rails::Generator/ + warn "[WARNING] Could not load generator at #{path.inspect} because it's a Rails 2.x generator, which is not supported anymore. Error: #{e.message}" end end diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 37effb77db126..f4b2bd1ae90c4 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -17,7 +17,7 @@ class Base < Thor::Group def self.source_root @_rails_source_root ||= begin if base_name && generator_name - File.expand_path(File.join(File.dirname(__FILE__), base_name, generator_name, 'templates')) + File.expand_path(File.join("../../generators", base_name, generator_name, 'templates'), File.dirname(__FILE__)) end end end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index b59ec524e8583..c5651b37fa025 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -1,8 +1,8 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/app/app_generator' +require 'generators/rails/app/app_generator' # TODO This line shouldn't be required -require 'rails/generators/rails/model/model_generator' +require 'generators/rails/model/model_generator' class ActionsTest < GeneratorsTestCase tests Rails::Generators::AppGenerator diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 83de50bbb452a..f961a95203a9e 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' require 'generators/generators_test_helper' -require 'rails/generators/rails/app/app_generator' +require 'generators/rails/app/app_generator' class AppGeneratorTest < GeneratorsTestCase arguments [destination_root] diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index 8e2fd3b9edb32..501858c0485d4 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/controller/controller_generator' +require 'generators/rails/controller/controller_generator' class ControllerGeneratorTest < GeneratorsTestCase arguments %w(Account foo bar) diff --git a/railties/test/generators/generator_generator_test.rb b/railties/test/generators/generator_generator_test.rb index 28377f23b0655..1a4c80fafb453 100644 --- a/railties/test/generators/generator_generator_test.rb +++ b/railties/test/generators/generator_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/generator/generator_generator' +require 'generators/rails/generator/generator_generator' class GeneratorGeneratorTest < GeneratorsTestCase arguments %w(awesome) diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 54953b76c8b5b..1fbd60d39dc69 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -10,7 +10,6 @@ def self.root require 'rails/generators' require 'rails/generators/test_case' -require 'rubygems' require 'active_record' require 'action_dispatch' diff --git a/railties/test/generators/helper_generator_test.rb b/railties/test/generators/helper_generator_test.rb index cf18782986836..268814f885898 100644 --- a/railties/test/generators/helper_generator_test.rb +++ b/railties/test/generators/helper_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/helper/helper_generator' +require 'generators/rails/helper/helper_generator' ObjectHelper = Class.new AnotherObjectHelperTest = Class.new diff --git a/railties/test/generators/integration_test_generator_test.rb b/railties/test/generators/integration_test_generator_test.rb index 88e18be5b2a5b..668fe13ffa1b4 100644 --- a/railties/test/generators/integration_test_generator_test.rb +++ b/railties/test/generators/integration_test_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/integration_test/integration_test_generator' +require 'generators/rails/integration_test/integration_test_generator' class IntegrationTestGeneratorTest < GeneratorsTestCase arguments %w(integration) diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index ee4346eb715ac..50f5c7ec86c2d 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/mailer/mailer_generator' +require 'generators/rails/mailer/mailer_generator' class MailerGeneratorTest < GeneratorsTestCase arguments %w(notifier foo bar) diff --git a/railties/test/generators/metal_generator_test.rb b/railties/test/generators/metal_generator_test.rb index 5d6a277561e57..9f61a50c9ad31 100644 --- a/railties/test/generators/metal_generator_test.rb +++ b/railties/test/generators/metal_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/metal/metal_generator' +require 'generators/rails/metal/metal_generator' class MetalGeneratorTest < GeneratorsTestCase arguments %w(foo) diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 2fd3e5c0561d9..6d61250655cd4 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/migration/migration_generator' +require 'generators/rails/migration/migration_generator' class MigrationGeneratorTest < GeneratorsTestCase def test_migration diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 051a43706b55c..fb1c41e6af914 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/model/model_generator' +require 'generators/rails/model/model_generator' class ModelGeneratorTest < GeneratorsTestCase arguments %w(Account name:string age:integer) diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb index 8c1df3b99259c..01bb85a352743 100644 --- a/railties/test/generators/named_base_test.rb +++ b/railties/test/generators/named_base_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' +require 'generators/rails/scaffold_controller/scaffold_controller_generator' # Mock out what we need from AR::Base. module ActiveRecord diff --git a/railties/test/generators/observer_generator_test.rb b/railties/test/generators/observer_generator_test.rb index 44d9e4a9f3cfb..859c3e682ae39 100644 --- a/railties/test/generators/observer_generator_test.rb +++ b/railties/test/generators/observer_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/observer/observer_generator' +require 'generators/rails/observer/observer_generator' class ObserverGeneratorTest < GeneratorsTestCase arguments %w(account) diff --git a/railties/test/generators/performance_test_generator_test.rb b/railties/test/generators/performance_test_generator_test.rb index 099575ea1d187..b5ca1ae5f61c5 100644 --- a/railties/test/generators/performance_test_generator_test.rb +++ b/railties/test/generators/performance_test_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/performance_test/performance_test_generator' +require 'generators/rails/performance_test/performance_test_generator' class PerformanceTestGeneratorTest < GeneratorsTestCase arguments %w(performance) diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 2989cdb67de1e..94c3b56ebc4ae 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/plugin/plugin_generator' +require 'generators/rails/plugin/plugin_generator' class PluginGeneratorTest < GeneratorsTestCase arguments %w(plugin_fu) diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index 15c0ca0f01548..4c969e9d82771 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/resource/resource_generator' +require 'generators/rails/resource/resource_generator' class ResourceGeneratorTest < GeneratorsTestCase arguments %w(account) diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 7593c14dd9d92..1188b32e76cbf 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' +require 'generators/rails/scaffold_controller/scaffold_controller_generator' module Unknown module Generators diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 4ddc7b1c894f9..d3ff3e7c088d2 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/scaffold/scaffold_generator' +require 'generators/rails/scaffold/scaffold_generator' class ScaffoldGeneratorTest < GeneratorsTestCase arguments %w(product_line title:string price:integer) diff --git a/railties/test/generators/session_migration_generator_test.rb b/railties/test/generators/session_migration_generator_test.rb index 251ffb19ed946..3ccda075e86d9 100644 --- a/railties/test/generators/session_migration_generator_test.rb +++ b/railties/test/generators/session_migration_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/session_migration/session_migration_generator' +require 'generators/rails/session_migration/session_migration_generator' class SessionMigrationGeneratorTest < GeneratorsTestCase def test_session_migration_with_default_name diff --git a/railties/test/generators/stylesheets_generator_test.rb b/railties/test/generators/stylesheets_generator_test.rb index d9079327ba2dc..bc2eacbe6842e 100644 --- a/railties/test/generators/stylesheets_generator_test.rb +++ b/railties/test/generators/stylesheets_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/stylesheets/stylesheets_generator' +require 'generators/rails/stylesheets/stylesheets_generator' class StylesheetsGeneratorTest < GeneratorsTestCase def test_copy_stylesheets From 61f77b1dcd1d5fb18ada2c9a739557ef6c49a910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Jan 2010 15:04:39 +0100 Subject: [PATCH 62/82] More cleaning up on rails generators load path. --- railties/bin/rails | 2 +- railties/lib/rails/generators.rb | 79 ++++++------- .../{fixjour => }/fixjour_generator.rb | 2 +- .../generators/rails/javascripts_generator.rb | 4 - .../lib/generators/wrong_generator.rb | 0 .../xspec/lib/generators/xspec_generator.rb | 2 - .../lib/rails_generators/mspec_generator.rb | 2 - railties/test/generators_test.rb | 107 ++++++++---------- 8 files changed, 84 insertions(+), 114 deletions(-) rename railties/test/fixtures/lib/generators/active_record/{fixjour => }/fixjour_generator.rb (69%) delete mode 100644 railties/test/fixtures/lib/generators/rails/javascripts_generator.rb rename railties/test/fixtures/{vendor/gems/gems/wrong => }/lib/generators/wrong_generator.rb (100%) delete mode 100644 railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb delete mode 100644 railties/test/fixtures/vendor/plugins/mspec/lib/rails_generators/mspec_generator.rb diff --git a/railties/bin/rails b/railties/bin/rails index b8b2d6188fe2b..afcd9fd0be36c 100755 --- a/railties/bin/rails +++ b/railties/bin/rails @@ -22,6 +22,6 @@ ARGV << "--help" if ARGV.empty? require 'rails/generators' -require 'rails/generators/rails/app/app_generator' +require 'generators/rails/app/app_generator' Rails::Generators::AppGenerator.start diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 45ae1fd30ab09..34fe39f47ecfc 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -131,51 +131,35 @@ def self.subclasses # Rails looks for is the first and last parts of the namespace. # def self.find_by_namespace(name, base=nil, context=nil) #:nodoc: - base = "rails" unless base || context || name.to_s.include?(?:) - lookups = [] lookups << "#{base}:#{name}" if base lookups << "#{name}:#{context}" if context + lookups << "#{name}:#{name}" unless name.to_s.include?(?:) lookups << "#{name}" lookups << "rails:#{name}" unless base || context || name.to_s.include?(?:) - # Mount regexps to lookup - regexps = [] - regexps << /^#{base}:[\w:]*#{name}$/ if base - regexps << /^#{name}:[\w:]*#{context}$/ if context - regexps << /^[(#{name}):]+$/ - regexps.uniq! - # Check if generator happens to be loaded - checked = subclasses.dup - klass = find_by_regexps(regexps, checked) + klass = subclasses.find { |k| lookups.include?(k.namespace) } return klass if klass - # Try to require other generators by looking in load_paths + # Try to load generator from $LOAD_PATH + checked = subclasses.dup lookup(lookups) + unchecked = subclasses - checked - klass = find_by_regexps(regexps, unchecked) + klass = unchecked.find { |k| lookups.include?(k.namespace) } return klass if klass - # Invoke fallbacks invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) end - # Tries to find a generator which the namespace match the regexp. - def self.find_by_regexps(regexps, klasses) - klasses.find do |klass| - namespace = klass.namespace - regexps.find { |r| namespace =~ r } - end - end - # Receives a namespace, arguments and the behavior to invoke the generator. # It's used as the default entry point for generate, destroy and update # commands. def self.invoke(namespace, args=ARGV, config={}) names = namespace.to_s.split(':') if klass = find_by_namespace(names.pop, names.shift) - args << "--help" if klass.arguments.any? { |a| a.required? } && args.empty? + args << "--help" if args.empty? && klass.arguments.any? { |a| a.required? } klass.start(args, config) else puts "Could not find generator #{namespace}." @@ -187,7 +171,8 @@ def self.help builtin = Rails::Generators.builtin.each { |n| n.sub!(/^rails:/, '') } builtin.sort! - lookup("*") + # TODO Fix me + # lookup("*") others = subclasses.map{ |k| k.namespace } others -= Rails::Generators.builtin others.sort! @@ -224,28 +209,38 @@ def self.invoke_fallbacks_for(name, base) #:nodoc: # Receives namespaces in an array and tries to find matching generators # in the load path. - def self.lookup(attempts) #:nodoc: - attempts.compact! - attempts.uniq! - - attempts.each do |attempt| - last = attempt.split(':').last - - # TODO Support rails_generators - require_generator "generators/#{attempt.gsub(':', '/')}/#{last}_generator" - require_generator "generators/#{attempt.gsub(':', '/')}_generator" + def self.lookup(namespaces) #:nodoc: + paths = namespaces_to_paths(namespaces) + + paths.each do |path| + ["generators", "rails_generators"].each do |base| + path = "#{base}/#{path}_generator" + + begin + require path + return + rescue LoadError => e + raise unless e.message =~ /#{Regexp.escape(path)}$/ + rescue NameError => e + raise unless e.message =~ /Rails::Generator([\s(::)]|$)/ + warn "[WARNING] Could not load generator #{path.inspect} because it's a Rails 2.x generator, which is not supported anymore. Error: #{e.message}" + end + end end end - def self.require_generator(path) - begin - require path - rescue LoadError => e - raise unless e.message =~ /#{Regexp.escape(path)}$/ - rescue NameError => e - raise unless e.message =~ /Rails::Generator/ - warn "[WARNING] Could not load generator at #{path.inspect} because it's a Rails 2.x generator, which is not supported anymore. Error: #{e.message}" + # Convert namespaces to paths by replacing ":" for "/" and adding + # an extra lookup. For example, "rails:model" should be searched + # in both: "rails/model/model_generator" and "rails/model_generator". + def self.namespaces_to_paths(namespaces) #:nodoc: + paths = [] + namespaces.each do |namespace| + pieces = namespace.split(":") + paths << pieces.dup.push(pieces.last).join("/") + paths << pieces.join("/") end + paths.uniq! + paths end end diff --git a/railties/test/fixtures/lib/generators/active_record/fixjour/fixjour_generator.rb b/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb similarity index 69% rename from railties/test/fixtures/lib/generators/active_record/fixjour/fixjour_generator.rb rename to railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb index a7d079a1bc1e8..7a4edb8bcbc35 100644 --- a/railties/test/fixtures/lib/generators/active_record/fixjour/fixjour_generator.rb +++ b/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb @@ -1,4 +1,4 @@ -require 'rails/generators/active_record' +require 'generators/active_record' module ActiveRecord module Generators diff --git a/railties/test/fixtures/lib/generators/rails/javascripts_generator.rb b/railties/test/fixtures/lib/generators/rails/javascripts_generator.rb deleted file mode 100644 index cad5e9678483c..0000000000000 --- a/railties/test/fixtures/lib/generators/rails/javascripts_generator.rb +++ /dev/null @@ -1,4 +0,0 @@ -module Rails::Generators - class JavascriptsGenerator < Rails::Generators::NamedBase - end -end diff --git a/railties/test/fixtures/vendor/gems/gems/wrong/lib/generators/wrong_generator.rb b/railties/test/fixtures/lib/generators/wrong_generator.rb similarity index 100% rename from railties/test/fixtures/vendor/gems/gems/wrong/lib/generators/wrong_generator.rb rename to railties/test/fixtures/lib/generators/wrong_generator.rb diff --git a/railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb b/railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb deleted file mode 100644 index cd477eb4c9946..0000000000000 --- a/railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb +++ /dev/null @@ -1,2 +0,0 @@ -class XspecGenerator < Rails::Generators::NamedBase -end diff --git a/railties/test/fixtures/vendor/plugins/mspec/lib/rails_generators/mspec_generator.rb b/railties/test/fixtures/vendor/plugins/mspec/lib/rails_generators/mspec_generator.rb deleted file mode 100644 index 191bdbf2fc818..0000000000000 --- a/railties/test/fixtures/vendor/plugins/mspec/lib/rails_generators/mspec_generator.rb +++ /dev/null @@ -1,2 +0,0 @@ -class MspecGenerator < Rails::Generators::NamedBase -end diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 65d7cd2746019..fdd72d6361778 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -1,15 +1,20 @@ require 'generators/generators_test_helper' -require 'rails/generators/rails/model/model_generator' -require 'rails/generators/test_unit/model/model_generator' +require 'generators/rails/model/model_generator' +require 'generators/test_unit/model/model_generator' require 'mocha' class GeneratorsTest < GeneratorsTestCase + def setup - Rails::Generators.instance_variable_set(:@load_paths, nil) - Gem.stubs(:respond_to?).with(:loaded_specs).returns(false) + @path = File.expand_path("lib", Rails.root) + $LOAD_PATH.unshift(@path) + end + + def teardown + $LOAD_PATH.delete(@path) end - def test_invoke_add_generators_to_raw_lookups + def test_simple_invoke TestUnit::Generators::ModelGenerator.expects(:start).with(["Account"], {}) Rails::Generators.invoke("test_unit:model", ["Account"]) end @@ -34,8 +39,10 @@ def test_invoke_with_config_values Rails::Generators.invoke :model, ["Account"], :behavior => :skip end - def test_find_by_namespace_without_base_or_context_looks_into_rails_namespace - assert Rails::Generators.find_by_namespace(:model) + def test_find_by_namespace + klass = Rails::Generators.find_by_namespace("rails:model") + assert klass + assert_equal "rails:model", klass.namespace end def test_find_by_namespace_with_base @@ -50,48 +57,32 @@ def test_find_by_namespace_with_context assert_equal "test_unit:model", klass.namespace end - def test_find_by_namespace_with_duplicated_name - klass = Rails::Generators.find_by_namespace(:foobar) - assert klass - assert_equal "foobar:foobar", klass.namespace - end - - def test_find_by_namespace_lookup_to_the_rails_root_folder + def test_find_by_namespace_with_generator_on_root klass = Rails::Generators.find_by_namespace(:fixjour) assert klass assert_equal "fixjour", klass.namespace end - def test_find_by_namespace_lookup_to_deep_rails_root_folders + def test_find_by_namespace_in_subfolder klass = Rails::Generators.find_by_namespace(:fixjour, :active_record) assert klass assert_equal "active_record:fixjour", klass.namespace end - def test_find_by_namespace_lookup_traverse_folders - klass = Rails::Generators.find_by_namespace(:javascripts, :rails) + def test_find_by_namespace_with_duplicated_name + klass = Rails::Generators.find_by_namespace(:foobar) assert klass - assert_equal "rails:javascripts", klass.namespace + assert_equal "foobar:foobar", klass.namespace end - def test_find_by_namespace_lookup_to_vendor_folders - klass = Rails::Generators.find_by_namespace(:mspec) - assert klass - assert_equal "mspec", klass.namespace + def test_find_by_namespace_without_base_or_context_looks_into_rails_namespace + assert Rails::Generators.find_by_namespace(:model) end - def test_find_by_namespace_lookup_with_gem_specification - assert_nil Rails::Generators.find_by_namespace(:xspec) - Rails::Generators.instance_variable_set(:@load_paths, nil) - - spec = Gem::Specification.new - spec.expects(:full_gem_path).returns(File.join(Rails.root, 'vendor', 'another_gem_path', 'xspec')) - Gem.expects(:respond_to?).with(:loaded_specs).returns(true) - Gem.expects(:loaded_specs).returns(:spec => spec) - - klass = Rails::Generators.find_by_namespace(:xspec) - assert klass - assert_equal "xspec", klass.namespace + def test_find_by_namespace_show_warning_if_generator_cant_be_loaded + output = capture(:stderr) { Rails::Generators.find_by_namespace(:wrong) } + assert_match /\[WARNING\] Could not load generator/, output + assert_match /Rails 2\.x generator/, output end def test_builtin_generators @@ -109,14 +100,6 @@ def test_rails_generators_with_others_information assert_equal "Others: active_record:fixjour, fixjour, foobar:foobar, mspec, rails:javascripts, xspec.", output end - def test_warning_is_shown_if_generator_cant_be_loaded - Rails::Generators.load_paths << File.join(Rails.root, "vendor", "gems", "gems", "wrong") - output = capture(:stderr){ Rails::Generators.find_by_namespace(:wrong) } - - assert_match /\[WARNING\] Could not load generator at/, output - assert_match /Rails 2\.x generator/, output - end - def test_no_color_sets_proper_shell Rails::Generators.no_color! assert_equal Thor::Shell::Basic, Thor::Base.shell @@ -124,24 +107,6 @@ def test_no_color_sets_proper_shell Thor::Base.shell = Thor::Shell::Color end - def test_rails_root_templates - template = File.join(Rails.root, "lib", "templates", "active_record", "model", "model.rb") - - # Create template - mkdir_p(File.dirname(template)) - File.open(template, 'w'){ |f| f.write "empty" } - - output = capture(:stdout) do - Rails::Generators.invoke :model, ["user"], :destination_root => destination_root - end - - assert_file "app/models/user.rb" do |content| - assert_equal "empty", content - end - ensure - rm_rf File.dirname(template) - end - def test_fallbacks_for_generators_on_find_by_namespace Rails::Generators.fallbacks[:remarkable] = :test_unit klass = Rails::Generators.find_by_namespace(:plugin, :remarkable) @@ -181,8 +146,26 @@ def self.name() 'NewGenerator' end Rails::Generators.subclasses.delete(klass) end + def test_rails_root_templates + template = File.join(Rails.root, "lib", "templates", "active_record", "model", "model.rb") + + # Create template + mkdir_p(File.dirname(template)) + File.open(template, 'w'){ |f| f.write "empty" } + + output = capture(:stdout) do + Rails::Generators.invoke :model, ["user"], :destination_root => destination_root + end + + assert_file "app/models/user.rb" do |content| + assert_equal "empty", content + end + ensure + rm_rf File.dirname(template) + end + def test_source_paths_for_not_namespaced_generators - mspec = Rails::Generators.find_by_namespace :mspec - assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "mspec")) + mspec = Rails::Generators.find_by_namespace :fixjour + assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "fixjour")) end end From a2b76d1dde09ad1a93a0e631bd6ba9a00f5cc1d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Jan 2010 16:14:32 +0100 Subject: [PATCH 63/82] Improve generators help. --- railties/lib/rails/generators.rb | 74 +++++++++++++++++++++----------- railties/test/generators_test.rb | 17 ++++---- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 34fe39f47ecfc..0a78b8687c869 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -138,17 +138,17 @@ def self.find_by_namespace(name, base=nil, context=nil) #:nodoc: lookups << "#{name}" lookups << "rails:#{name}" unless base || context || name.to_s.include?(?:) - # Check if generator happens to be loaded - klass = subclasses.find { |k| lookups.include?(k.namespace) } - return klass if klass - - # Try to load generator from $LOAD_PATH - checked = subclasses.dup lookup(lookups) - unchecked = subclasses - checked - klass = unchecked.find { |k| lookups.include?(k.namespace) } - return klass if klass + namespaces = subclasses.inject({}) do |hash, klass| + hash[klass.namespace] = klass + hash + end + + lookups.each do |namespace| + klass = namespaces[namespace] + return klass if klass + end invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) end @@ -168,27 +168,36 @@ def self.invoke(namespace, args=ARGV, config={}) # Show help message with available generators. def self.help - builtin = Rails::Generators.builtin.each { |n| n.sub!(/^rails:/, '') } - builtin.sort! - - # TODO Fix me - # lookup("*") - others = subclasses.map{ |k| k.namespace } - others -= Rails::Generators.builtin - others.sort! - - puts "Please select a generator." - puts "Builtin: #{builtin.join(', ')}." - puts "Others: #{others.join(', ')}." unless others.empty? + traverse_load_paths! + + namespaces = subclasses.map{ |k| k.namespace } + namespaces.sort! + + groups = Hash.new { |h,k| h[k] = [] } + namespaces.each do |namespace| + base = namespace.split(':').first + groups[base] << namespace + end + + puts "Please select a generator:" + puts + + # Print Rails defaults first. + rails = groups.delete("rails") + rails.map! { |n| n.sub(/^rails:/, '') } + print_list("rails", rails) + + groups.sort.each { |b, n| print_list(b, n) } end protected - # Keep builtin generators in an Array. - def self.builtin #:nodoc: - Dir[File.dirname(__FILE__) + '/generators/*/*'].collect do |file| - file.split('/')[-2, 2].join(':') - end + # Prints a list of generators. + def self.print_list(base, namespaces) #:nodoc: + return if namespaces.empty? + puts "#{base.camelize}:" + namespaces.each { |namespace| puts(" #{namespace}") } + puts end # Try fallbacks for the given base. @@ -207,6 +216,19 @@ def self.invoke_fallbacks_for(name, base) #:nodoc: nil end + # This will try to load any generator in the load path to show in help. + def self.traverse_load_paths! #:nodoc: + $LOAD_PATH.each do |base| + Dir[File.join(base, "{generators,rails_generators}", "**", "*_generator.rb")].each do |path| + begin + require path + rescue Exception => e + # No problem + end + end + end + end + # Receives namespaces in an array and tries to find matching generators # in the load path. def self.lookup(namespaces) #:nodoc: diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index fdd72d6361778..674bdadf41e7d 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -85,19 +85,20 @@ def test_find_by_namespace_show_warning_if_generator_cant_be_loaded assert_match /Rails 2\.x generator/, output end - def test_builtin_generators - assert Rails::Generators.builtin.include?("rails:model") - end - def test_rails_generators_help_with_builtin_information output = capture(:stdout){ Rails::Generators.help } - assert_match /model/, output - assert_match /scaffold_controller/, output + assert_match /Rails:/, output + assert_match /^ model$/, output + assert_match /^ scaffold_controller$/, output end def test_rails_generators_with_others_information - output = capture(:stdout){ Rails::Generators.help }.split("\n").last - assert_equal "Others: active_record:fixjour, fixjour, foobar:foobar, mspec, rails:javascripts, xspec.", output + output = capture(:stdout){ Rails::Generators.help } + assert_match /ActiveRecord:/, output + assert_match /Fixjour:/, output + assert_match /^ active_record:model$/, output + assert_match /^ active_record:fixjour$/, output + assert_match /^ fixjour$/, output end def test_no_color_sets_proper_shell From e27bfad6a5f9e9171148226dee7ce51d41731ea7 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 18 Jan 2010 08:45:24 +0100 Subject: [PATCH 64/82] Forgot to change named_scope to scope in an ArgumentError raise exception for duplication scopes related to commit d60bb0a9e4be [#3736 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- activerecord/lib/active_record/named_scope.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 5c0cf10f4cce8..2d88f49f5f5b3 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -101,7 +101,7 @@ def scope(name, options = {}, &block) name = name.to_sym if !scopes[name] && respond_to?(name, true) - raise ArgumentError, "Cannot define named_scope :#{name} because #{self.name}.#{name} method already exists." + raise ArgumentError, "Cannot define scope :#{name} because #{self.name}.#{name} method already exists." end scopes[name] = lambda do |parent_scope, *args| From 68b76a38eb83db8515336ec2e7fd8191798386be Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 18 Jan 2010 09:49:05 -0600 Subject: [PATCH 65/82] Cleanup deprecation notices. --- actionpack/lib/action_controller/deprecated/dispatcher.rb | 2 +- activerecord/lib/active_record/named_scope.rb | 8 ++++---- railties/lib/rails/deprecation.rb | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/actionpack/lib/action_controller/deprecated/dispatcher.rb b/actionpack/lib/action_controller/deprecated/dispatcher.rb index 1a70d2ee4da19..8c21e375ddee9 100644 --- a/actionpack/lib/action_controller/deprecated/dispatcher.rb +++ b/actionpack/lib/action_controller/deprecated/dispatcher.rb @@ -15,7 +15,7 @@ def after_dispatch(*args, &block) def to_prepare(*args, &block) ActiveSupport::Deprecation.warn "ActionController::Dispatcher.to_prepare is deprecated. " << - "Please use ActionDispatch::Callbacks.to_prepare instead.", caller + "Please use config.to_prepare instead", caller ActionDispatch::Callbacks.after(*args, &block) end diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 2d88f49f5f5b3..92030e5bfdc08 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -9,7 +9,7 @@ module NamedScope module ClassMethods # Returns a relation if invoked without any arguments. # - # posts = Post.scoped + # posts = Post.scoped # posts.size # Fires "select count(*) from posts" and returns the count # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects # @@ -41,8 +41,8 @@ def scopes # scope :red, :conditions => {:color => 'red'} # scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true] # end - # - # The above calls to scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, + # + # The above calls to scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, # in effect, represents the query Shirt.find(:all, :conditions => {:color => 'red'}). # # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it resembles the association object @@ -120,7 +120,7 @@ def scope(name, options = {}, &block) end def named_scope(*args, &block) - ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead.", caller) + ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller) scope(*args, &block) end end diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb index 43f08d13df21c..f28da5a6b0eb1 100644 --- a/railties/lib/rails/deprecation.rb +++ b/railties/lib/rails/deprecation.rb @@ -11,7 +11,7 @@ def replace(*args) end def warn(callstack, called, args) - msg = "RAILS_ROOT is deprecated! Use Rails.root instead." + msg = "RAILS_ROOT is deprecated! Use Rails.root instead" ActiveSupport::Deprecation.warn(msg, callstack) end end).new @@ -26,7 +26,7 @@ def replace(*args) end def warn(callstack, called, args) - msg = "RAILS_ENV is deprecated! Use Rails.env instead." + msg = "RAILS_ENV is deprecated! Use Rails.env instead" ActiveSupport::Deprecation.warn(msg, callstack) end end).new @@ -41,7 +41,7 @@ def replace(*args) end def warn(callstack, called, args) - msg = "RAILS_DEFAULT_LOGGER is deprecated! Use Rails.logger instead." + msg = "RAILS_DEFAULT_LOGGER is deprecated! Use Rails.logger instead" ActiveSupport::Deprecation.warn(msg, callstack) end end).new From 728db5a932a1a185e6c366f1671e0b1f81d7b027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Jan 2010 18:22:55 +0100 Subject: [PATCH 66/82] Rake tasks should load generators from new paths. --- activerecord/lib/active_record/railties/databases.rake | 2 +- railties/lib/rails/tasks/framework.rake | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index b39e064e453bc..88974dd7867f1 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -428,7 +428,7 @@ namespace :db do task :create => :environment do raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations? require 'rails/generators' - require 'rails/generators/rails/session_migration/session_migration_generator' + require 'generators/rails/session_migration/session_migration_generator' Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ] end diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 93ba309039caf..5d7c094d8b3cd 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -86,7 +86,7 @@ namespace :rails do template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} require 'rails/generators' - require 'rails/generators/rails/app/app_generator' + require 'generators/rails/app/app_generator' generator = Rails::Generators::AppGenerator.new [ Rails.root ], {}, :destination_root => Rails.root generator.apply template, :verbose => false end @@ -94,7 +94,7 @@ namespace :rails do namespace :update do def invoke_from_app_generator(method) require 'rails/generators' - require 'rails/generators/rails/app/app_generator' + require 'generators/rails/app/app_generator' generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true }, :destination_root => Rails.root From 893524382a54ac9f65c49f08759d47ca5049b381 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 21:38:22 +0530 Subject: [PATCH 67/82] No need to pass current_scoped_methods to construct_calculation_arel everytime --- .../lib/active_record/calculations.rb | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index e4b3caab4e9d6..051f22db85073 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -46,19 +46,19 @@ module ClassMethods def count(*args) case args.size when 0 - construct_calculation_arel({}, current_scoped_methods).count + construct_calculation_arel.count when 1 if args[0].is_a?(Hash) options = args[0] distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false - construct_calculation_arel(options, current_scoped_methods).count(options[:select], :distinct => distinct) + construct_calculation_arel(options).count(options[:select], :distinct => distinct) else - construct_calculation_arel({}, current_scoped_methods).count(args[0]) + construct_calculation_arel.count(args[0]) end when 2 column_name, options = args distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false - construct_calculation_arel(options, current_scoped_methods).count(column_name, :distinct => distinct) + construct_calculation_arel(options).count(column_name, :distinct => distinct) else raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}" end @@ -141,7 +141,7 @@ def sum(column_name, options = {}) # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors # Person.sum("2 * age") def calculate(operation, column_name, options = {}) - construct_calculation_arel(options, current_scoped_methods).calculate(operation, column_name, options.slice(:distinct)) + construct_calculation_arel(options).calculate(operation, column_name, options.slice(:distinct)) rescue ThrowResult 0 end @@ -151,48 +151,48 @@ def validate_calculation_options(options = {}) options.assert_valid_keys(CALCULATIONS_OPTIONS) end - def construct_calculation_arel(options = {}, merge_with_relation = nil) + def construct_calculation_arel(options = {}) validate_calculation_options(options) options = options.except(:distinct) - merge_with_includes = merge_with_relation ? merge_with_relation.includes_values : [] + merge_with_includes = current_scoped_methods ? current_scoped_methods.includes_values : [] includes = (merge_with_includes + Array.wrap(options[:include])).uniq if includes.any? - merge_with_joins = merge_with_relation ? merge_with_relation.joins_values : [] + merge_with_joins = current_scoped_methods ? current_scoped_methods.joins_values : [] joins = (merge_with_joins + Array.wrap(options[:joins])).uniq join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(joins)) - construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation) + construct_calculation_arel_with_included_associations(options, join_dependency) else relation = unscoped.apply_finder_options(options.slice(:joins, :conditions, :order, :limit, :offset, :group, :having)) - if merge_with_relation - relation = merge_with_relation.except(:select, :order, :limit, :offset, :group, :from).merge(relation) + if current_scoped_methods + relation = current_scoped_methods.except(:select, :order, :limit, :offset, :group, :from).merge(relation) end - from = merge_with_relation.from_value if merge_with_relation && merge_with_relation.from_value.present? + from = current_scoped_methods.from_value if current_scoped_methods && current_scoped_methods.from_value.present? from = options[:from] if from.blank? && options[:from].present? relation = relation.from(from) - select = options[:select].presence || (merge_with_relation ? merge_with_relation.select_values.join(", ") : nil) + select = options[:select].presence || (current_scoped_methods ? current_scoped_methods.select_values.join(", ") : nil) relation = relation.select(select) relation end end - def construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation = nil) + def construct_calculation_arel_with_included_associations(options, join_dependency) relation = unscoped for association in join_dependency.join_associations relation = association.join_relation(relation) end - if merge_with_relation - relation.joins_values = (merge_with_relation.joins_values + relation.joins_values).uniq - relation.where_values = merge_with_relation.where_values + if current_scoped_methods + relation.joins_values = (current_scoped_methods.joins_values + relation.joins_values).uniq + relation.where_values = current_scoped_methods.where_values - merge_limit = merge_with_relation.taken + merge_limit = current_scoped_methods.taken end relation = relation.apply_finder_options(options.slice(:joins, :group, :having, :order, :conditions, :from)). From 3c4186b366a14042a6ea6cc3432634d41986d1e2 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 22:05:43 +0530 Subject: [PATCH 68/82] Remove construct_calculation_arel_with_included_associations because it's same as construct_finder_arel_with_included_associations --- .../lib/active_record/calculations.rb | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 051f22db85073..f279bdfc8d2f8 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -162,7 +162,7 @@ def construct_calculation_arel(options = {}) merge_with_joins = current_scoped_methods ? current_scoped_methods.joins_values : [] joins = (merge_with_joins + Array.wrap(options[:joins])).uniq join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(joins)) - construct_calculation_arel_with_included_associations(options, join_dependency) + construct_finder_arel_with_included_associations(options, join_dependency) else relation = unscoped.apply_finder_options(options.slice(:joins, :conditions, :order, :limit, :offset, :group, :having)) @@ -181,32 +181,6 @@ def construct_calculation_arel(options = {}) end end - def construct_calculation_arel_with_included_associations(options, join_dependency) - relation = unscoped - - for association in join_dependency.join_associations - relation = association.join_relation(relation) - end - - if current_scoped_methods - relation.joins_values = (current_scoped_methods.joins_values + relation.joins_values).uniq - relation.where_values = current_scoped_methods.where_values - - merge_limit = current_scoped_methods.taken - end - - relation = relation.apply_finder_options(options.slice(:joins, :group, :having, :order, :conditions, :from)). - select(column_aliases(join_dependency)) - - if !using_limitable_reflections?(join_dependency.reflections) && (merge_limit || options[:limit]) - relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) - end - - relation = relation.limit(options[:limit] || merge_limit) if using_limitable_reflections?(join_dependency.reflections) - - relation - end - end end end From 9e7ec2a9f13aa52cdf07cf9308e8031548dcddc0 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 23:39:19 +0530 Subject: [PATCH 69/82] Simplify calculation scope building. Remove :order from associations as it is troublesome w/ calculation methods using postgresql. --- activerecord/lib/active_record/calculations.rb | 15 +-------------- activerecord/test/models/company.rb | 8 +++----- activerecord/test/models/post.rb | 2 +- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index f279bdfc8d2f8..8a44dc7df180c 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -164,20 +164,7 @@ def construct_calculation_arel(options = {}) join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(joins)) construct_finder_arel_with_included_associations(options, join_dependency) else - relation = unscoped.apply_finder_options(options.slice(:joins, :conditions, :order, :limit, :offset, :group, :having)) - - if current_scoped_methods - relation = current_scoped_methods.except(:select, :order, :limit, :offset, :group, :from).merge(relation) - end - - from = current_scoped_methods.from_value if current_scoped_methods && current_scoped_methods.from_value.present? - from = options[:from] if from.blank? && options[:from].present? - relation = relation.from(from) - - select = options[:select].presence || (current_scoped_methods ? current_scoped_methods.select_values.join(", ") : nil) - relation = relation.select(select) - - relation + scoped.apply_finder_options(options) end end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 7e93fda1ebe5e..df5fd10b6b907 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -45,7 +45,7 @@ class Firm < Company has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all - has_many :limited_clients, :class_name => "Client", :order => "id", :limit => 1 + has_many :limited_clients, :class_name => "Client", :limit => 1 has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id" has_many :clients_with_interpolated_conditions, :class_name => "Client", :conditions => 'rating > #{rating}' has_many :clients_like_ms_with_hash_conditions, :conditions => { :name => 'Microsoft' }, :class_name => "Client", :order => "id" @@ -91,10 +91,8 @@ class Firm < Company end class DependentFirm < Company - # added order by id as in fixtures there are two accounts for Rails Core - # Oracle tests were failing because of that as the second fixture was selected - has_one :account, :foreign_key => "firm_id", :dependent => :nullify, :order => "id" - has_many :companies, :foreign_key => 'client_of', :order => "id", :dependent => :nullify + has_one :account, :foreign_key => "firm_id", :dependent => :nullify + has_many :companies, :foreign_key => 'client_of', :dependent => :nullify end class Client < Company diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 939df7549bb6a..f48b35486cb24 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -25,7 +25,7 @@ def greeting { :joins => :comments, :conditions => {:comments => {:post_id => post_id} } } } - has_many :comments, :order => "body" do + has_many :comments do def find_most_recent find(:first, :order => "id DESC") end From 59cd044310d4bb454d492addffb8bd60dd8a23a9 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 18 Jan 2010 15:37:18 -0600 Subject: [PATCH 70/82] Add all folders in app/* to the load path --- railties/lib/rails/bootstrap.rb | 11 ++++++++--- railties/lib/rails/configuration.rb | 18 +++--------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/railties/lib/rails/bootstrap.rb b/railties/lib/rails/bootstrap.rb index b7cf70747af3c..5db663f9efc4a 100644 --- a/railties/lib/rails/bootstrap.rb +++ b/railties/lib/rails/bootstrap.rb @@ -23,8 +23,8 @@ def initialize(application) # the load_once paths. initializer :set_autoload_paths do require 'active_support/dependencies' - ActiveSupport::Dependencies.load_paths = config.load_paths.uniq - ActiveSupport::Dependencies.load_once_paths = config.load_once_paths.uniq + ActiveSupport::Dependencies.load_paths = expand_load_path(config.load_paths) + ActiveSupport::Dependencies.load_once_paths = expand_load_path(config.load_once_paths) extra = ActiveSupport::Dependencies.load_once_paths - ActiveSupport::Dependencies.load_paths unless extra.empty? @@ -140,7 +140,7 @@ def initialize(application) end end - initializer :initialize_notifications do + initializer :initialize_notifications do require 'active_support/notifications' if config.colorize_logging == false @@ -152,5 +152,10 @@ def initialize(application) Rails::Subscriber.dispatch(args) end end + + private + def expand_load_path(load_paths) + load_paths.map { |path| Dir.glob(path.to_s) }.flatten.uniq + end end end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 45e2e521ef2b7..b76a7ac2d87cd 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -200,12 +200,7 @@ def view_path end def eager_load_paths - @eager_load_paths ||= %w( - app/metal - app/models - app/controllers - app/helpers - ).map { |dir| "#{root}/#{dir}" }.select { |dir| File.directory?(dir) } + @eager_load_paths ||= ["#{root}/app/*"] end def load_paths @@ -215,20 +210,13 @@ def load_paths # Add the old mock paths only if the directories exists paths.concat(Dir["#{root}/test/mocks/#{Rails.env}"]) if File.exists?("#{root}/test/mocks/#{Rails.env}") - # Add the app's controller directory - paths.concat(Dir["#{root}/app/controllers/"]) - # Followed by the standard includes. paths.concat %w( app - app/metal - app/models - app/controllers - app/helpers - app/services + app/* lib vendor - ).map { |dir| "#{root}/#{dir}" }.select { |dir| File.directory?(dir) } + ).map { |dir| "#{root}/#{dir}" } paths.concat builtin_directories end From f2693cda446dc039e52eee43b60346689850bb76 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 18 Jan 2010 16:48:04 -0600 Subject: [PATCH 71/82] Install plugin view paths into AM view load path --- railties/lib/rails/plugin.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index a057b8f701427..0c097309632bd 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -50,7 +50,11 @@ def load_tasks end initializer :add_view_paths, :after => :initialize_framework_views do - ActionController::Base.view_paths.concat ["#{path}/app/views"] if File.directory?("#{path}/app/views") + plugin_views = "#{path}/app/views" + if File.directory?(plugin_views) + ActionController::Base.view_paths.concat([plugin_views]) if defined? ActionController + ActionMailer::Base.view_paths.concat([plugin_views]) if defined? ActionMailer + end end # TODO Isn't it supposed to be :after => "action_controller.initialize_routing" ? @@ -62,4 +66,4 @@ def load_tasks end end end -end \ No newline at end of file +end From e6a68a5cc3b47865abbba59bf693215c50136002 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 19 Jan 2010 04:32:40 +0530 Subject: [PATCH 72/82] Add Relation#find_with_associations to load relation with eager loaded associations --- activerecord/lib/active_record/relation.rb | 29 +++------------ .../active_record/relation/finder_methods.rb | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 4ed118b02d4e8..e37e692a975b1 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -45,33 +45,12 @@ def respond_to?(method, include_private = false) def to_a return @records if loaded? - find_with_associations = @eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?) - - @records = if find_with_associations - begin - options = { - :select => @select_values.join(", "), - :joins => arel.joins(arel), - :group => @group_values.join(", "), - :order => @order_values.join(', '), - :conditions => where_clause, - :limit => @limit_value, - :offset => @offset_value, - :from => @from_value - } - - including = (@eager_load_values + @includes_values).uniq - join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil) - @klass.send(:find_with_associations, options, join_dependency) - rescue ThrowResult - [] - end - else - @klass.find_by_sql(arel.to_sql) - end + eager_loading = @eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?) + + @records = eager_loading ? find_with_associations : @klass.find_by_sql(arel.to_sql) preload = @preload_values - preload += @includes_values unless find_with_associations + preload += @includes_values unless eager_loading preload.each {|associations| @klass.send(:preload_associations, @records, associations) } # @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT. diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 3668b0997f3cc..980c5796f3f5c 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -44,6 +44,42 @@ def last protected + def find_with_associations + including = (@eager_load_values + @includes_values).uniq + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil) + rows = construct_relation_for_association_find(join_dependency).to_a + join_dependency.instantiate(rows) + rescue ThrowResult + [] + end + + def construct_relation_for_association_find(join_dependency) + relation = except(:includes, :eager_load, :preload, :select).select(@klass.send(:column_aliases, join_dependency)) + + for association in join_dependency.join_associations + relation = association.join_relation(relation) + end + + limitable_reflections = @klass.send(:using_limitable_reflections?, join_dependency.reflections) + + if !limitable_reflections && relation.limit_value + limited_id_condition = construct_limited_ids_condition(relation.except(:select)) + relation = relation.where(limited_id_condition) + end + + relation = relation.except(:limit, :offset) unless limitable_reflections + + relation + end + + def construct_limited_ids_condition(relation) + orders = relation.order_values.join(", ") + values = @klass.connection.distinct("#{@klass.connection.quote_table_name @klass.table_name}.#{@klass.primary_key}", orders) + + ids_array = relation.select(values).collect {|row| row[@klass.primary_key]} + ids_array.empty? ? raise(ThrowResult) : primary_key.in(ids_array) + end + def find_by_attributes(match, attributes, *args) conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h} result = where(conditions).send(match.finder) From e1029be1ebe0e44cdad75dc9b1bdfd3957c245bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Jan 2010 23:32:01 +0100 Subject: [PATCH 73/82] Since Rails will require Ruby >= 1.8.7, we can rely on instance_exec. --- .../lib/generators/rails/plugin/plugin_generator.rb | 12 ++++++------ .../lib/rails/vendor/thor-0.12.3/lib/thor/group.rb | 9 ++++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/railties/lib/generators/rails/plugin/plugin_generator.rb b/railties/lib/generators/rails/plugin/plugin_generator.rb index ee785caf7d5c6..b68b8691db893 100644 --- a/railties/lib/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/generators/rails/plugin/plugin_generator.rb @@ -20,15 +20,15 @@ def create_tasks_files directory 'tasks', plugin_dir('tasks') end - hook_for :generator do |instance, generator| - instance.inside instance.send(:plugin_dir), :verbose => true do - instance.invoke generator, [ instance.name ], :namespace => false + hook_for :generator do |generator| + inside plugin_dir, :verbose => true do + invoke generator, [ name ], :namespace => false end end - hook_for :test_framework do |instance, test_framework| - instance.inside instance.send(:plugin_dir), :verbose => true do - instance.invoke test_framework + hook_for :test_framework do |test_framework| + inside plugin_dir, :verbose => true do + invoke test_framework end end diff --git a/railties/lib/rails/vendor/thor-0.12.3/lib/thor/group.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/group.rb index a585b37b73444..eda3b52c4f921 100644 --- a/railties/lib/rails/vendor/thor-0.12.3/lib/thor/group.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/group.rb @@ -252,10 +252,13 @@ def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc: shell.padding += 1 result = if block_given? - if block.arity == 2 - block.call(self, klass) - else + case block.arity + when 3 block.call(self, klass, task) + when 2 + block.call(self, klass) + when 1 + instance_exec(klass, &block) end else invoke klass, task, *args From 93d64dfefc5a8153c22f14d2806236597f60d8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Jan 2010 23:41:18 +0100 Subject: [PATCH 74/82] Improve script/generate help. --- railties/lib/rails/generators.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 0a78b8687c869..736c36c0dc249 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -179,7 +179,17 @@ def self.help groups[base] << namespace end - puts "Please select a generator:" + puts "Usage:" + puts " script/generate GENERATOR [args] [options]" + puts + puts "General options:" + puts " -h, [--help] # Print generators options and usage" + puts " -p, [--pretend] # Run but do not make any changes" + puts " -f, [--force] # Overwrite files that already exist" + puts " -s, [--skip] # Skip files that already exist" + puts " -q, [--quiet] # Supress status output" + puts + puts "Please choose a generator below." puts # Print Rails defaults first. From a18dd52d58aa6a69fb210c1e60c01074333170ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Jan 2010 00:07:11 +0100 Subject: [PATCH 75/82] Get generators tests running on Ruby 1.9.1 --- railties/test/generators/actions_test.rb | 3 ++- .../test/generators/app_generator_test.rb | 3 ++- .../generators/controller_generator_test.rb | 3 ++- .../generators/generator_generator_test.rb | 3 ++- .../test/generators/generators_test_helper.rb | 21 +++++++++---------- .../test/generators/helper_generator_test.rb | 3 ++- .../integration_test_generator_test.rb | 3 ++- .../test/generators/mailer_generator_test.rb | 3 ++- .../test/generators/metal_generator_test.rb | 3 ++- .../generators/migration_generator_test.rb | 4 +++- .../test/generators/model_generator_test.rb | 3 ++- railties/test/generators/named_base_test.rb | 3 ++- .../generators/observer_generator_test.rb | 3 ++- .../performance_test_generator_test.rb | 3 ++- .../test/generators/plugin_generator_test.rb | 3 ++- .../generators/resource_generator_test.rb | 3 ++- .../scaffold_controller_generator_test.rb | 3 ++- .../generators/scaffold_generator_test.rb | 3 ++- .../session_migration_generator_test.rb | 4 +++- .../generators/stylesheets_generator_test.rb | 4 +++- railties/test/generators_test.rb | 3 ++- 21 files changed, 53 insertions(+), 31 deletions(-) diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index c5651b37fa025..cb1fa96e0df1c 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -4,7 +4,8 @@ # TODO This line shouldn't be required require 'generators/rails/model/model_generator' -class ActionsTest < GeneratorsTestCase +class ActionsTest < Rails::Generators::TestCase + include GeneratorsTestHelper tests Rails::Generators::AppGenerator arguments [destination_root] diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index f961a95203a9e..f821f5caf3f9e 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -2,7 +2,8 @@ require 'generators/generators_test_helper' require 'generators/rails/app/app_generator' -class AppGeneratorTest < GeneratorsTestCase +class AppGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments [destination_root] def setup diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index 501858c0485d4..79a4e5bf17a7b 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -1,7 +1,8 @@ require 'generators/generators_test_helper' require 'generators/rails/controller/controller_generator' -class ControllerGeneratorTest < GeneratorsTestCase +class ControllerGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(Account foo bar) def test_help_does_not_show_invoked_generators_options_if_they_already_exist diff --git a/railties/test/generators/generator_generator_test.rb b/railties/test/generators/generator_generator_test.rb index 1a4c80fafb453..f3fd688e4ff74 100644 --- a/railties/test/generators/generator_generator_test.rb +++ b/railties/test/generators/generator_generator_test.rb @@ -1,7 +1,8 @@ require 'generators/generators_test_helper' require 'generators/rails/generator/generator_generator' -class GeneratorGeneratorTest < GeneratorsTestCase +class GeneratorGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(awesome) def test_generator_skeleton_is_created diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 1fbd60d39dc69..3cd16a69f90e1 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -13,17 +13,16 @@ def self.root require 'active_record' require 'action_dispatch' -class GeneratorsTestCase < Rails::Generators::TestCase - destination File.join(Rails.root, "tmp") - setup :prepare_destination +module GeneratorsTestHelper + def self.included(base) + base.class_eval do + destination File.join(Rails.root, "tmp") + setup :prepare_destination - def self.inherited(base) - base.tests Rails::Generators.const_get(base.name.sub(/Test$/, '')) - rescue - # Do nothing. - end - - def test_truth - # Don't cry test/unit + begin + base.tests Rails::Generators.const_get(base.name.sub(/Test$/, '')) + rescue + end + end end end \ No newline at end of file diff --git a/railties/test/generators/helper_generator_test.rb b/railties/test/generators/helper_generator_test.rb index 268814f885898..6d7168738e728 100644 --- a/railties/test/generators/helper_generator_test.rb +++ b/railties/test/generators/helper_generator_test.rb @@ -4,7 +4,8 @@ ObjectHelper = Class.new AnotherObjectHelperTest = Class.new -class HelperGeneratorTest < GeneratorsTestCase +class HelperGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(admin) def test_helper_skeleton_is_created diff --git a/railties/test/generators/integration_test_generator_test.rb b/railties/test/generators/integration_test_generator_test.rb index 668fe13ffa1b4..d7fc324c8809d 100644 --- a/railties/test/generators/integration_test_generator_test.rb +++ b/railties/test/generators/integration_test_generator_test.rb @@ -1,7 +1,8 @@ require 'generators/generators_test_helper' require 'generators/rails/integration_test/integration_test_generator' -class IntegrationTestGeneratorTest < GeneratorsTestCase +class IntegrationTestGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(integration) def test_integration_test_skeleton_is_created diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index 50f5c7ec86c2d..deabfb432cd38 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -1,7 +1,8 @@ require 'generators/generators_test_helper' require 'generators/rails/mailer/mailer_generator' -class MailerGeneratorTest < GeneratorsTestCase +class MailerGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(notifier foo bar) def test_mailer_skeleton_is_created diff --git a/railties/test/generators/metal_generator_test.rb b/railties/test/generators/metal_generator_test.rb index 9f61a50c9ad31..e3a2384885325 100644 --- a/railties/test/generators/metal_generator_test.rb +++ b/railties/test/generators/metal_generator_test.rb @@ -1,7 +1,8 @@ require 'generators/generators_test_helper' require 'generators/rails/metal/metal_generator' -class MetalGeneratorTest < GeneratorsTestCase +class MetalGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(foo) def test_metal_skeleton_is_created diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 6d61250655cd4..811a712fd56ae 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -1,7 +1,9 @@ require 'generators/generators_test_helper' require 'generators/rails/migration/migration_generator' -class MigrationGeneratorTest < GeneratorsTestCase +class MigrationGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + def test_migration migration = "change_title_body_from_posts" run_generator [migration] diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index fb1c41e6af914..79ce9a2a7b7c4 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -1,7 +1,8 @@ require 'generators/generators_test_helper' require 'generators/rails/model/model_generator' -class ModelGeneratorTest < GeneratorsTestCase +class ModelGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(Account name:string age:integer) def test_help_shows_invoked_generators_options diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb index 01bb85a352743..99eb431a490a0 100644 --- a/railties/test/generators/named_base_test.rb +++ b/railties/test/generators/named_base_test.rb @@ -11,7 +11,8 @@ class << self end end -class NamedBaseTest < GeneratorsTestCase +class NamedBaseTest < Rails::Generators::TestCase + include GeneratorsTestHelper tests Rails::Generators::ScaffoldControllerGenerator def test_named_generator_attributes diff --git a/railties/test/generators/observer_generator_test.rb b/railties/test/generators/observer_generator_test.rb index 859c3e682ae39..058a19228de87 100644 --- a/railties/test/generators/observer_generator_test.rb +++ b/railties/test/generators/observer_generator_test.rb @@ -1,7 +1,8 @@ require 'generators/generators_test_helper' require 'generators/rails/observer/observer_generator' -class ObserverGeneratorTest < GeneratorsTestCase +class ObserverGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(account) def test_invokes_default_orm diff --git a/railties/test/generators/performance_test_generator_test.rb b/railties/test/generators/performance_test_generator_test.rb index b5ca1ae5f61c5..c95063a1272d1 100644 --- a/railties/test/generators/performance_test_generator_test.rb +++ b/railties/test/generators/performance_test_generator_test.rb @@ -1,7 +1,8 @@ require 'generators/generators_test_helper' require 'generators/rails/performance_test/performance_test_generator' -class PerformanceTestGeneratorTest < GeneratorsTestCase +class PerformanceTestGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(performance) def test_performance_test_skeleton_is_created diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 94c3b56ebc4ae..4bfe210efb102 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -1,7 +1,8 @@ require 'generators/generators_test_helper' require 'generators/rails/plugin/plugin_generator' -class PluginGeneratorTest < GeneratorsTestCase +class PluginGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(plugin_fu) def test_plugin_skeleton_is_created diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index 4c969e9d82771..959934bd71fc7 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -1,7 +1,8 @@ require 'generators/generators_test_helper' require 'generators/rails/resource/resource_generator' -class ResourceGeneratorTest < GeneratorsTestCase +class ResourceGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(account) def setup diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 1188b32e76cbf..f971598d18621 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -6,7 +6,8 @@ module Generators end end -class ScaffoldControllerGeneratorTest < GeneratorsTestCase +class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(User name:string age:integer) def test_controller_skeleton_is_created diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index d3ff3e7c088d2..a7e9c8a2319e3 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -1,7 +1,8 @@ require 'generators/generators_test_helper' require 'generators/rails/scaffold/scaffold_generator' -class ScaffoldGeneratorTest < GeneratorsTestCase +class ScaffoldGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper arguments %w(product_line title:string price:integer) def setup diff --git a/railties/test/generators/session_migration_generator_test.rb b/railties/test/generators/session_migration_generator_test.rb index 3ccda075e86d9..de28b4c75b7f9 100644 --- a/railties/test/generators/session_migration_generator_test.rb +++ b/railties/test/generators/session_migration_generator_test.rb @@ -1,7 +1,9 @@ require 'generators/generators_test_helper' require 'generators/rails/session_migration/session_migration_generator' -class SessionMigrationGeneratorTest < GeneratorsTestCase +class SessionMigrationGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + def test_session_migration_with_default_name run_generator assert_migration "db/migrate/add_sessions_table.rb", /class AddSessionsTable < ActiveRecord::Migration/ diff --git a/railties/test/generators/stylesheets_generator_test.rb b/railties/test/generators/stylesheets_generator_test.rb index bc2eacbe6842e..718fcb1fa74da 100644 --- a/railties/test/generators/stylesheets_generator_test.rb +++ b/railties/test/generators/stylesheets_generator_test.rb @@ -1,7 +1,9 @@ require 'generators/generators_test_helper' require 'generators/rails/stylesheets/stylesheets_generator' -class StylesheetsGeneratorTest < GeneratorsTestCase +class StylesheetsGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + def test_copy_stylesheets run_generator assert_file "public/stylesheets/scaffold.css" diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 674bdadf41e7d..60c81a813fb81 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -3,7 +3,8 @@ require 'generators/test_unit/model/model_generator' require 'mocha' -class GeneratorsTest < GeneratorsTestCase +class GeneratorsTest < Rails::Generators::TestCase + include GeneratorsTestHelper def setup @path = File.expand_path("lib", Rails.root) From c7255386cd3d6de037a9b08b635f9ac299608ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Jan 2010 00:49:04 +0100 Subject: [PATCH 76/82] `script/generate mailer Notifier` will now create a mailer at app/mailers. --- railties/lib/generators/rails/mailer/USAGE | 4 ++-- railties/lib/generators/rails/mailer/mailer_generator.rb | 2 +- .../lib/generators/test_unit/mailer/mailer_generator.rb | 2 +- .../mailer/templates/{unit_test.rb => functional_test.rb} | 0 railties/test/generators/mailer_generator_test.rb | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) rename railties/lib/generators/test_unit/mailer/templates/{unit_test.rb => functional_test.rb} (100%) diff --git a/railties/lib/generators/rails/mailer/USAGE b/railties/lib/generators/rails/mailer/USAGE index c56095b2c8be0..4b0b8ddc3b4c0 100644 --- a/railties/lib/generators/rails/mailer/USAGE +++ b/railties/lib/generators/rails/mailer/USAGE @@ -9,7 +9,7 @@ Example: `./script/generate mailer Notifications signup forgot_password invoice` creates a Notifications mailer class, views, test, and fixtures: - Mailer: app/models/notifications.rb + Mailer: app/mailers/notifications.rb Views: app/views/notifications/signup.erb [...] - Test: test/unit/test/unit/notifications_test.rb + Test: test/functional/notifications_test.rb Fixtures: test/fixtures/notifications/signup [...] diff --git a/railties/lib/generators/rails/mailer/mailer_generator.rb b/railties/lib/generators/rails/mailer/mailer_generator.rb index 33f1665b8329d..8993181d79ff9 100644 --- a/railties/lib/generators/rails/mailer/mailer_generator.rb +++ b/railties/lib/generators/rails/mailer/mailer_generator.rb @@ -5,7 +5,7 @@ class MailerGenerator < NamedBase check_class_collision def create_mailer_file - template "mailer.rb", File.join('app/models', class_path, "#{file_name}.rb") + template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}.rb") end hook_for :template_engine, :test_framework diff --git a/railties/lib/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/generators/test_unit/mailer/mailer_generator.rb index ef350a622451a..a0d73db1b0372 100644 --- a/railties/lib/generators/test_unit/mailer/mailer_generator.rb +++ b/railties/lib/generators/test_unit/mailer/mailer_generator.rb @@ -7,7 +7,7 @@ class MailerGenerator < Base check_class_collision :suffix => "Test" def create_test_files - template "unit_test.rb", File.join('test/unit', class_path, "#{file_name}_test.rb") + template "functional_test.rb", File.join('test/functional', class_path, "#{file_name}_test.rb") end def create_fixtures_files diff --git a/railties/lib/generators/test_unit/mailer/templates/unit_test.rb b/railties/lib/generators/test_unit/mailer/templates/functional_test.rb similarity index 100% rename from railties/lib/generators/test_unit/mailer/templates/unit_test.rb rename to railties/lib/generators/test_unit/mailer/templates/functional_test.rb diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index deabfb432cd38..dfc3130f77c79 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -7,7 +7,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_mailer_skeleton_is_created run_generator - assert_file "app/models/notifier.rb", /class Notifier < ActionMailer::Base/ + assert_file "app/mailers/notifier.rb", /class Notifier < ActionMailer::Base/ end def test_check_class_collision @@ -17,7 +17,7 @@ def test_check_class_collision def test_invokes_default_test_framework run_generator - assert_file "test/unit/notifier_test.rb", /class NotifierTest < ActionMailer::TestCase/ + assert_file "test/functional/notifier_test.rb", /class NotifierTest < ActionMailer::TestCase/ assert_file "test/fixtures/notifier/foo", /app\/views\/notifier\/foo/ assert_file "test/fixtures/notifier/bar", /app\/views\/notifier\/bar/ end @@ -40,7 +40,7 @@ def test_logs_if_the_template_engine_cannot_be_found def test_actions_are_turned_into_methods run_generator - assert_file "app/models/notifier.rb", /def foo/ - assert_file "app/models/notifier.rb", /def bar/ + assert_file "app/mailers/notifier.rb", /def foo/ + assert_file "app/mailers/notifier.rb", /def bar/ end end From a0374582ff6d403a48b018df666b064f4c261f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Jan 2010 01:15:57 +0100 Subject: [PATCH 77/82] Bring body(Hash) behavior back. --- actionmailer/lib/action_mailer/base.rb | 17 ++++++++- .../lib/action_mailer/deprecated_body.rb | 38 ++++++++----------- actionmailer/test/mail_service_test.rb | 4 +- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 5be1beaedbc50..928fed477773b 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -23,8 +23,7 @@ module ActionMailer #:nodoc: # bcc ["bcc@example.com", "Order Watcher "] # from "system@example.com" # subject "New account information" - # - # @account = recipient + # body :account => recipient # end # end # @@ -523,6 +522,20 @@ def deliver!(mail = @mail) private + # Allow you to set assigns for your template: + # + # body :greetings => "Hi" + # + # Will make @greetings available in the template to be rendered. + def body(object=nil) + super # Run deprecation hooks + + if object.is_a?(Hash) + @assigns_set = true + object.each { |k, v| instance_variable_set(:"@#{k}", v) } + end + end + # Set up the default values for the various instance variables of this # mailer. Subclasses may override this method to provide different # defaults. diff --git a/actionmailer/lib/action_mailer/deprecated_body.rb b/actionmailer/lib/action_mailer/deprecated_body.rb index 20b0989a8506e..5379b33a54b57 100644 --- a/actionmailer/lib/action_mailer/deprecated_body.rb +++ b/actionmailer/lib/action_mailer/deprecated_body.rb @@ -1,15 +1,13 @@ module ActionMailer # TODO Remove this module all together in a next release. Ensure that super - # hooks in ActionMailer::Base are removed as well. + # hooks and @assigns_set in ActionMailer::Base are removed as well. module DeprecatedBody - def self.included(base) - base.class_eval do - # Define the body of the message. 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 text of the message. - adv_attr_accessor :body - end - end + extend ActionMailer::AdvAttrAccessor + + # Define the body of the message. 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 text of the message. + adv_attr_accessor :body def initialize_defaults(method_name) @body ||= {} @@ -18,32 +16,28 @@ def initialize_defaults(method_name) def attachment(params, &block) if params[:body] ActiveSupport::Deprecation.warn('attachment :body => "string" is deprecated. To set the body of an attachment ' << - 'please use :data instead, like attachment :data => "string".', caller[0,10]) + 'please use :data instead, like attachment :data => "string"', caller[0,10]) params[:data] = params.delete(:body) end end def create_parts - if String === @body - ActiveSupport::Deprecation.warn('body is deprecated. To set the body with a text ' << - 'call render(:text => "body").', caller[0,10]) + if String === @body && !defined?(@assigns_set) + ActiveSupport::Deprecation.warn('body(String) is deprecated. To set the body with a text ' << + 'call render(:text => "body")', caller[0,10]) self.response_body = @body - elsif @body.is_a?(Hash) && !@body.empty? - ActiveSupport::Deprecation.warn('body is deprecated. To set assigns simply ' << - 'use instance variables', caller[0,10]) - @body.each { |k, v| instance_variable_set(:"@#{k}", v) } + elsif self.response_body + @body = self.response_body end end def render(*args) options = args.last.is_a?(Hash) ? args.last : {} if options[:body] - ActiveSupport::Deprecation.warn(':body is deprecated. To set assigns simply ' << - 'use instance variables', caller[0,1]) + ActiveSupport::Deprecation.warn(':body in render deprecated. Please call body ' << + 'with a hash instead', caller[0,1]) - options.delete(:body).each do |k, v| - instance_variable_set(:"@#{k}", v) - end + body options.delete(:body) end super diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index 03bd0c238c50c..d1c4f7b5e9bce 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -301,7 +301,6 @@ def return_path render :text => "testing" end - # This tests body calls accepeting a hash, which is deprecated. def body_ivar(recipient) recipients recipient subject "Body as a local variable" @@ -1089,8 +1088,7 @@ def test_return_path_with_deliver end def test_body_is_stored_as_an_ivar - mail = nil - ActiveSupport::Deprecation.silence { mail = TestMailer.create_body_ivar(@recipient) } + mail = TestMailer.create_body_ivar(@recipient) assert_equal "body: foo\nbar: baz", mail.body.to_s end From f00cbf78725caa44ca46150c4708f9a4fad0e023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Jan 2010 01:35:41 +0100 Subject: [PATCH 78/82] Bring render_message back for 2.3 compatibility. --- actionmailer/lib/action_mailer/base.rb | 42 +++++++++++++++++++------- actionmailer/test/mail_service_test.rb | 4 +-- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 928fed477773b..a8233512abee1 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -143,12 +143,13 @@ module ActionMailer #:nodoc: # subject "New account information" # from "system@example.com" # content_type "multipart/alternative" + # body :account => recipient # # part :content_type => "text/html", - # :body => render_message("signup-as-html", :account => recipient) + # :data => render_message("signup-as-html") # # part "text/plain" do |p| - # p.body = render_message("signup-as-plain", :account => recipient) + # p.body = render_message("signup-as-plain") # p.content_transfer_encoding = "base64" # end # end @@ -453,11 +454,13 @@ def matches_dynamic_method?(method_name) #:nodoc: # body, headers, etc.) can be set on it. def part(params) params = {:content_type => params} if String === params + if custom_headers = params.delete(:headers) ActiveSupport::Deprecation.warn('Passing custom headers with :headers => {} is deprecated. ' << 'Please just pass in custom headers directly.', caller[0,10]) params.merge!(custom_headers) end + part = Mail::Part.new(params) yield part if block_given? @parts << part @@ -475,6 +478,20 @@ def attachment(params, &block) part(params, &block) end + # Allow you to set assigns for your template: + # + # body :greetings => "Hi" + # + # Will make @greetings available in the template to be rendered. + def body(object=nil) + returning(super) do # Run deprecation hooks + if object.is_a?(Hash) + @assigns_set = true + object.each { |k, v| instance_variable_set(:"@#{k}", v) } + end + end + end + # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer # will be initialized according to the named method. If not, the mailer will # remain uninitialized (useful when you only need to invoke the "receive" @@ -522,17 +539,20 @@ def deliver!(mail = @mail) private - # Allow you to set assigns for your template: + # Render a message but does not set it as mail body. Useful for rendering + # data for part and attachments. # - # body :greetings => "Hi" + # Examples: # - # Will make @greetings available in the template to be rendered. - def body(object=nil) - super # Run deprecation hooks - - if object.is_a?(Hash) - @assigns_set = true - object.each { |k, v| instance_variable_set(:"@#{k}", v) } + # render_message "special_message" + # render_message :template => "special_message" + # render_message :inline => "<%= 'Hi!' %>" + def render_message(object) + case object + when String + render_to_body(:template => object) + else + render_to_body(object) end end diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index d1c4f7b5e9bce..f87d9b2e5b0fd 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -120,11 +120,11 @@ def multipart_with_mime_version(recipient) content_type "multipart/alternative" part "text/plain" do |p| - p.body = "blah" + p.body = render_message(:text => "blah") end part "text/html" do |p| - p.body = "blah" + p.body = render_message(:inline => "<%= content_tag(:b, 'blah') %>") end end From 5579de5bab2e9eeba26006705d7b18592948d185 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 18 Jan 2010 21:11:48 -0600 Subject: [PATCH 79/82] Show Rack env dump on exception page --- .../middleware/templates/rescues/_request_and_response.erb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb index 5224403dabf91..7465f2c69be46 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb @@ -17,7 +17,10 @@

    Parameters:

    <%=h request_dump %>

    Show session dump

    - + + +

    Show Rack ENV

    +

    Response

    From 71d67fc6bd504956bce66e274e6228dd00a814c1 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Mon, 18 Jan 2010 21:22:09 -0600 Subject: [PATCH 80/82] Prettier hash dump --- .../templates/rescues/_request_and_response.erb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb index 7465f2c69be46..839df509995ad 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb @@ -11,16 +11,20 @@ clean_params.delete("controller") request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n") + + def debug_hash(hash) + hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect}" }.join("\n") + end %>

    Request

    Parameters:

    <%=h request_dump %>

    Show session dump

    - + -

    Show Rack ENV

    - +

    Show env dump

    +

    Response

    From b9599502c9e738a5a1513e75d08f8d40ed408265 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 19 Jan 2010 15:22:09 +0530 Subject: [PATCH 81/82] Add Relation#construct_relation_for_association_calculations for calculations with includes --- .../lib/active_record/calculations.rb | 25 +++---------------- activerecord/lib/active_record/relation.rb | 11 +++++--- .../active_record/relation/finder_methods.rb | 6 +++++ activerecord/test/cases/calculations_test.rb | 17 ------------- 4 files changed, 17 insertions(+), 42 deletions(-) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 8a44dc7df180c..fc6cb793ab909 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -2,8 +2,6 @@ module ActiveRecord module Calculations #:nodoc: extend ActiveSupport::Concern - CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include, :from] - module ClassMethods # Count operates using three different approaches. # @@ -147,26 +145,11 @@ def calculate(operation, column_name, options = {}) end private - def validate_calculation_options(options = {}) - options.assert_valid_keys(CALCULATIONS_OPTIONS) - end - - def construct_calculation_arel(options = {}) - validate_calculation_options(options) - options = options.except(:distinct) - - merge_with_includes = current_scoped_methods ? current_scoped_methods.includes_values : [] - includes = (merge_with_includes + Array.wrap(options[:include])).uniq - if includes.any? - merge_with_joins = current_scoped_methods ? current_scoped_methods.joins_values : [] - joins = (merge_with_joins + Array.wrap(options[:joins])).uniq - join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(joins)) - construct_finder_arel_with_included_associations(options, join_dependency) - else - scoped.apply_finder_options(options) - end - end + def construct_calculation_arel(options = {}) + relation = scoped.apply_finder_options(options.except(:distinct)) + (relation.eager_loading? || relation.includes_values.present?) ? relation.send(:construct_relation_for_association_calculations) : relation + end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index e37e692a975b1..19f91f4278c0f 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -45,12 +45,10 @@ def respond_to?(method, include_private = false) def to_a return @records if loaded? - eager_loading = @eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?) - - @records = eager_loading ? find_with_associations : @klass.find_by_sql(arel.to_sql) + @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql) preload = @preload_values - preload += @includes_values unless eager_loading + preload += @includes_values unless eager_loading? preload.each {|associations| @klass.send(:preload_associations, @records, associations) } # @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT. @@ -112,6 +110,7 @@ def reload def reset @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil + @should_eager_load = @join_dependency = nil @records = [] self end @@ -133,6 +132,10 @@ def scope_for_create end end + def eager_loading? + @should_eager_load ||= (@eager_load_values.any? || (@includes_values.any? && references_eager_loaded_tables?)) + end + protected def method_missing(method, *args, &block) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 980c5796f3f5c..c48c8fe828c38 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -53,6 +53,12 @@ def find_with_associations [] end + def construct_relation_for_association_calculations + including = (@eager_load_values + @includes_values).uniq + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, arel.joins(arel)) + construct_relation_for_association_find(join_dependency) + end + def construct_relation_for_association_find(join_dependency) relation = except(:includes, :eager_load, :preload, :select).select(@klass.send(:column_aliases, join_dependency)) diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index bd2d471fc7f40..c3b2e56387fac 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -246,23 +246,6 @@ def test_should_group_by_summed_field_through_association_and_having assert_equal 8, c['Jadedpixel'] end - def test_should_reject_invalid_options - assert_nothing_raised do - # empty options are valid - Company.send(:validate_calculation_options) - # these options are valid for all calculations - [:select, :conditions, :joins, :order, :group, :having, :distinct].each do |opt| - Company.send(:validate_calculation_options, opt => true) - end - - # :include is only valid on :count - Company.send(:validate_calculation_options, :include => true) - end - - assert_raise(ArgumentError) { Company.send(:validate_calculation_options, :sum, :foo => :bar) } - assert_raise(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) } - end - def test_should_count_selected_field_with_include assert_equal 6, Account.count(:distinct => true, :include => :firm) assert_equal 4, Account.count(:distinct => true, :include => :firm, :select => :credit_limit) From ed8501ef16fb2f5e4bd4d987740f5e5f62978400 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 19 Jan 2010 15:23:56 +0530 Subject: [PATCH 82/82] Fix DoubleRenderError error message --- actionpack/lib/abstract_controller/rendering.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index d57136dbb81e1..0dab4a3cc0f77 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -42,7 +42,7 @@ def view_context # Delegates render_to_body and sticks the result in self.response_body. def render(*args) if response_body - raise AbstractController::DoubleRenderError, "OMG" + raise AbstractController::DoubleRenderError, "Can only render or redirect once per action" end self.response_body = render_to_body(*args)