diff options
370 files changed, 5440 insertions, 4346 deletions
@@ -29,7 +29,7 @@ end gem 'uglifier', '>= 1.0.3', :require => false gem 'rake', '>= 0.8.7' -gem 'mocha', '>= 0.9.8' +gem 'mocha', '>= 0.11.2' group :doc do # The current sdoc cannot generate GitHub links due diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index be7b82eecc..a33d6e072b 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,4 +1,4 @@ -## Rails 3.2.3 (unreleased) ## +## Rails 3.2.3 (March 30, 2012) ## * Upgrade mail version to 2.4.3 *ML* diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index c508512ff6..e24a7f52d5 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -486,7 +486,7 @@ module ActionMailer #:nodoc: self.class.mailer_name end - # Allows you to pass random and unusual headers to the new +Mail::Message+ object + # Allows you to pass random and unusual headers to the new <tt>Mail::Message</tt> object # which will add them to itself. # # headers['X-Special-Domain-Specific-Header'] = "SecretValue" @@ -497,7 +497,7 @@ module ActionMailer #:nodoc: # headers 'X-Special-Domain-Specific-Header' => "SecretValue", # 'In-Reply-To' => incoming.message_id # - # The resulting Mail::Message will have the following in it's header: + # The resulting Mail::Message will have the following in its header: # # X-Special-Domain-Specific-Header: SecretValue def headers(args=nil) diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb index 7040ae6f8d..68ed86e0d4 100644 --- a/actionmailer/test/i18n_with_controller_test.rb +++ b/actionmailer/test/i18n_with_controller_test.rb @@ -24,7 +24,7 @@ end class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new Routes.draw do - match ':controller(/:action(/:id))' + get ':controller(/:action(/:id))' end def app diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb index 0536e83098..2ea1723434 100644 --- a/actionmailer/test/url_test.rb +++ b/actionmailer/test/url_test.rb @@ -57,8 +57,8 @@ class ActionMailerUrlTest < ActionMailer::TestCase UrlTestMailer.delivery_method = :test AppRoutes.draw do - match ':controller(/:action(/:id))' - match '/welcome' => "foo#bar", :as => "welcome" + get ':controller(/:action(/:id))' + get '/welcome' => "foo#bar", :as => "welcome" end expected = new_mail diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 32630e5ded..99602f14d9 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,24 @@ ## Rails 4.0.0 (unreleased) ## +* Replace `include_seconds` boolean argument with `:include_seconds => true` option + in `distance_of_time_in_words` and `time_ago_in_words` signature. *Dmitriy Kiriyenko* + +* Remove `button_to_function` and `link_to_function` helpers. *Rafael Mendonça França* + +* Make current object and counter (when it applies) variables accessible when + rendering templates with :object / :collection. *Carlos Antonio da Silva* + +* JSONP now uses mimetype application/javascript instead of application/json *omjokine* + +* Allow to lazy load `default_form_builder` by passing a `String` instead of a constant. *Piotr Sarnacki* + +* Session arguments passed to `process` calls in functional tests are now merged into + the existing session, whereas previously they would replace the existing session. + This change may break some existing tests if they are asserting the exact contents of + the session but should not break existing tests that only assert individual keys. + + *Andrew White* + * Add `index` method to FormBuilder class. *Jorge Bejar* * Remove the leading \n added by textarea on assert_select. *Santiago Pastorino* @@ -135,7 +154,7 @@ HTML5 `mark` element. *Brian Cardarella* -## Rails 3.2.3 (unreleased) ## +## Rails 3.2.3 (March 30, 2012) ## * Add `config.action_view.embed_authenticity_token_in_remote_forms` (defaults to true) which allows to set if authenticity token will be included by default in remote forms. If you change it to false, you can still force authenticity token by passing `:authenticity_token => true` in form options *Piotr Sarnacki* diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index 369741fb12..374645c9ce 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -8,10 +8,8 @@ module ActionController #:nodoc: include ActionController::Rendering - DEFAULT_SEND_FILE_OPTIONS = { - :type => 'application/octet-stream'.freeze, - :disposition => 'attachment'.freeze, - }.freeze + DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc: + DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc: protected # Sends the file. This uses a server-appropriate method (such as X-Sendfile) @@ -127,7 +125,7 @@ module ActionController #:nodoc: # # See +send_file+ for more information on HTTP Content-* headers and caching. def send_data(data, options = {}) #:doc: - send_file_headers! options.dup + send_file_headers! options render options.slice(:status, :content_type).merge(:text => data) end @@ -135,15 +133,8 @@ module ActionController #:nodoc: def send_file_headers!(options) type_provided = options.has_key?(:type) - options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) - [:type, :disposition].each do |arg| - raise ArgumentError, ":#{arg} option required" if options[arg].nil? - end - - disposition = options[:disposition] - disposition += %(; filename="#{options[:filename]}") if options[:filename] - - content_type = options[:type] + content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE) + raise ArgumentError, ":type option required" if content_type.nil? if content_type.is_a?(Symbol) extension = Mime[content_type] @@ -157,10 +148,13 @@ module ActionController #:nodoc: self.content_type = content_type end - headers.merge!( - 'Content-Disposition' => disposition, - 'Content-Transfer-Encoding' => 'binary' - ) + disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION) + unless disposition.nil? + disposition += %(; filename="#{options[:filename]}") if options[:filename] + headers['Content-Disposition'] = disposition + end + + headers['Content-Transfer-Encoding'] = 'binary' response.sending_file = true diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index fa760f2658..17e2db74d4 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -66,7 +66,7 @@ module ActionController # class Admin::UsersController < ApplicationController # end # - # will try to check if +Admin::User+ or +User+ model exists, and use it to + # will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to # determine the wrapper key respectively. If both models don't exist, # it will then fallback to use +user+ as the key. module ParamsWrapper diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 35ded8a56c..5e7bd44562 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -45,6 +45,16 @@ module ActionController # integer, or a symbol representing the downcased, underscored and symbolized description. # Note that the status code must be a 3xx HTTP code, or redirection will not occur. # + # If you are using XHR requests other than GET or POST and redirecting after the + # request then some browsers will follow the redirect using the original request + # method. This may lead to undesirable behavior such as a double DELETE. To work + # around this you can return a <tt>303 See Other</tt> status code which will be + # followed using a GET request. + # + # Examples: + # redirect_to posts_url, :status => :see_other + # redirect_to :action => 'index', :status => 303 + # # It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names # +alert+ and +notice+ as well as a general purpose +flash+ bucket. # diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 6e9ce450ac..4a0c1c7dd7 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -91,9 +91,14 @@ module ActionController add :json do |json, options| json = json.to_json(options) unless json.kind_of?(String) - json = "#{options[:callback]}(#{json})" unless options[:callback].blank? - self.content_type ||= Mime::JSON - json + + if options[:callback].present? + self.content_type ||= Mime::JS + "#{options[:callback]}(#{json})" + else + self.content_type ||= Mime::JSON + json + end end add :js do |js, options| diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 52aaed0ed4..eeb37db2e7 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -139,17 +139,17 @@ module ActionController #:nodoc: # session or flash after the template starts rendering will not propagate # to the client. # - # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+ + # If you try to modify cookies, session or flash, an <tt>ActionDispatch::ClosedError</tt> # will be raised, showing those objects are closed for modification. # # == Middlewares # # Middlewares that need to manipulate the body won't work with streaming. # You should disable those middlewares whenever streaming in development - # or production. For instance, +Rack::Bug+ won't work when streaming as it + # or production. For instance, <tt>Rack::Bug</tt> won't work when streaming as it # needs to inject contents in the HTML body. # - # Also +Rack::Cache+ won't work with streaming as it does not support + # Also <tt>Rack::Cache</tt> won't work with streaming as it does not support # streaming bodies yet. Whenever streaming Cache-Control is automatically # set to "no-cache". # diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 5c40889f6b..e7af3f5b8d 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -75,12 +75,7 @@ module ActionController def record_key_for_dom_id(record) record = record.to_model if record.respond_to?(:to_model) key = record.to_key - key ? sanitize_dom_id(key.join('_')) : key - end - - # Replaces characters that are invalid in HTML DOM ids with valid ones. - def sanitize_dom_id(candidate_id) - candidate_id # TODO implement conversion to valid DOM id values + key ? key.join('_') : key end end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 55a9819316..21997c4d79 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -27,13 +27,13 @@ module ActionController path = payload[:virtual_path] next unless path partial = path =~ /^.*\/_[^\/]*$/ + if partial @partials[path] += 1 @partials[path.split("/").last] += 1 - @templates[path] += 1 - else - @templates[path] += 1 end + + @templates[path] += 1 end end @@ -147,17 +147,23 @@ module ActionController extra_keys = routes.extra_keys(parameters) non_path_parameters = get? ? query_parameters : request_parameters parameters.each do |key, value| - if value.is_a? Fixnum - value = value.to_s - elsif value.is_a? Array - value = Result.new(value.map { |v| v.is_a?(String) ? v.dup : v }) - elsif value.is_a? String + if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?)) + value = value.map{ |v| v.duplicable? ? v.dup : v } + elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? }) + value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }] + elsif value.frozen? && value.duplicable? value = value.dup end if extra_keys.include?(key.to_sym) non_path_parameters[key] = value else + if value.is_a?(Array) + value = Result.new(value.map(&:to_param)) + else + value = value.to_param + end + path_parameters[key.to_s] = value end end @@ -453,7 +459,7 @@ module ActionController # Ensure that numbers and symbols passed as params are converted to # proper params, as is the case when engaging rack. - parameters = paramify_values(parameters) + parameters = paramify_values(parameters) if html_format?(parameters) @request.recycle! @response.recycle! @@ -466,14 +472,13 @@ module ActionController parameters ||= {} controller_class_name = @controller.class.anonymous? ? - "anonymous_controller" : + "anonymous" : @controller.class.name.underscore.sub(/_controller$/, '') @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters) - @request.session = ActionController::TestSession.new(session) if session + @request.session.update(session) if session @request.session["flash"] = @request.flash.update(flash || {}) - @request.session["flash"].sweep @controller.request = @request build_request_uri(action, parameters) @@ -546,6 +551,12 @@ module ActionController @request.env["QUERY_STRING"] = query_string || "" end end + + def html_format?(parameters) + return true unless parameters.is_a?(Hash) + format = Mime[parameters[:format]] + format.nil? || format.html? + end end # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index e9b50ff8ce..114b0e73c9 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -99,7 +99,7 @@ module HTML self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto feed svn urn aim rsync tag ssh sftp rtsp afs)) - # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept. + # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept. self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse border-color border-left-color border-right-color border-top-color clear color cursor direction display elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 5c48a60469..e31f3b823d 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/module/attribute_accessors' + module ActionDispatch module Http module MimeNegotiation diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 078229efd2..cc46f9983c 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -29,7 +29,7 @@ module ActionDispatch # :nodoc: # class DemoControllerTest < ActionDispatch::IntegrationTest # def test_print_root_path_to_console # get('/') - # puts @response.body + # puts response.body # end # end class Response diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index f9dae5dad7..4266ec042e 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -23,23 +23,6 @@ module ActionDispatch end def url_for(options = {}) - if options[:host].blank? && options[:only_path].blank? - raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true' - end - - rewritten_url = "" - - unless options[:only_path] - unless options[:protocol] == false - rewritten_url << (options[:protocol] || "http") - rewritten_url << ":" unless rewritten_url.match(%r{:|//}) - end - rewritten_url << "//" unless rewritten_url.match("//") - rewritten_url << rewrite_authentication(options) - rewritten_url << host_or_subdomain_and_domain(options) - rewritten_url << ":#{options.delete(:port)}" if options[:port] - end - path = "" path << options.delete(:script_name).to_s.chomp("/") path << options.delete(:path).to_s @@ -47,14 +30,36 @@ module ActionDispatch params = options[:params] || {} params.reject! {|k,v| v.to_param.nil? } - rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) - rewritten_url << "?#{params.to_query}" unless params.empty? - rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor] - rewritten_url + result = build_host_url(options) + + result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) + result << "?#{params.to_query}" unless params.empty? + result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor] + result end private + def build_host_url(options) + if options[:host].blank? && options[:only_path].blank? + raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true' + end + + result = "" + + unless options[:only_path] + unless options[:protocol] == false + result << (options[:protocol] || "http") + result << ":" unless result.match(%r{:|//}) + end + result << "//" unless result.match("//") + result << rewrite_authentication(options) + result << host_or_subdomain_and_domain(options) + result << ":#{options.delete(:port)}" if options[:port] + end + result + end + def named_host?(host) host && IP_HOST_REGEXP !~ host end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 0c717c8503..e1016977ab 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -339,7 +339,6 @@ module ActionDispatch end def call(env) - cookie_jar = nil status, headers, body = @app.call(env) if cookie_jar = env['action_dispatch.cookies'] diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index cff0877030..54f2669f43 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -4,7 +4,7 @@ module ActionDispatch # read a notice you put there or <tt>flash["notice"] = "hello"</tt> # to put a new one. def flash - @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new) + @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new).tap(&:sweep) end end @@ -17,7 +17,7 @@ module ActionDispatch # def create # # save post # flash[:notice] = "Post successfully created" - # redirect_to posts_path(@post) + # redirect_to @post # end # # def show @@ -217,10 +217,6 @@ module ActionDispatch end def call(env) - if (session = env['rack.session']) && (flash = session['flash']) - flash.sweep - end - @app.call(env) ensure session = env['rack.session'] || {} @@ -237,7 +233,8 @@ module ActionDispatch env[KEY] = new_hash end - if session.key?('flash') && session['flash'].empty? + if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) + session.key?('flash') && session['flash'].empty? session.delete('flash') end end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index d924f21fad..ec15a2a715 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -5,11 +5,14 @@ module ActionDispatch # IP addresses that are "trusted proxies" that can be stripped from # the comma-delimited list in the X-Forwarded-For header. See also: # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces + # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses. TRUSTED_PROXIES = %r{ ^127\.0\.0\.1$ | # localhost + ^::1$ | ^(10 | # private IP 10.x.x.x 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255 - 192\.168 # private IP 192.168.x.x + 192\.168 | # private IP 192.168.x.x + fc00:: # private IP fc00 )\. }x @@ -19,13 +22,13 @@ module ActionDispatch @app = app @check_ip = check_ip_spoofing @proxies = case custom_proxies - when Regexp - custom_proxies - when nil - TRUSTED_PROXIES - else - Regexp.union(TRUSTED_PROXIES, custom_proxies) - end + when Regexp + custom_proxies + when nil + TRUSTED_PROXIES + else + Regexp.union(TRUSTED_PROXIES, custom_proxies) + end end def call(env) @@ -34,6 +37,31 @@ module ActionDispatch end class GetIp + + # IP v4 and v6 (with compression) validation regexp + # https://gist.github.com/1289635 + VALID_IP = %r{ + (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4 + (^( + (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated + (([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end + (([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6 + (([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with + (([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon + (([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle + (([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + (::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4 + ([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4 + (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining + (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending + )$) + }x + def initialize(env, middleware) @env = env @middleware = middleware @@ -44,25 +72,31 @@ module ActionDispatch # but will be wrong if the user is behind a proxy. Proxies will set # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those. # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of - # multiple chained proxies. The last address which is not a known proxy - # will be the originating IP. + # multiple chained proxies. The first address which is in this list + # if it's not a known proxy will be the originating IP. + # Format of HTTP_X_FORWARDED_FOR: + # client_ip, proxy_ip1, proxy_ip2... + # http://en.wikipedia.org/wiki/X-Forwarded-For def calculate_ip - client_ip = @env['HTTP_CLIENT_IP'] - forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR') - remote_addrs = ips_from('REMOTE_ADDR') + client_ip = @env['HTTP_CLIENT_IP'] + forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first + remote_addrs = ips_from('REMOTE_ADDR') check_ip = client_ip && @middleware.check_ip - if check_ip && !forwarded_ips.include?(client_ip) + if check_ip && forwarded_ip != client_ip # We don't know which came from the proxy, and which from the user raise IpSpoofAttackError, "IP spoofing attack?!" \ "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \ "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" end - not_proxy = client_ip || forwarded_ips.first || remote_addrs.first - - # Return first REMOTE_ADDR if there are no other options - not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first + client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten + if client_ips.present? + client_ips.first + else + # If there is no client ip we can return first valid proxy ip from REMOTE_ADDR + remote_addrs.find { |ip| valid_ip? ip } + end end def to_s @@ -71,12 +105,24 @@ module ActionDispatch @ip = calculate_ip end - protected + private - def ips_from(header, allow_proxies = false) - ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : [] - allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies } + def ips_from(header) + @env[header] ? @env[header].strip.split(/[,\s]+/) : [] end + + def valid_ip?(ip) + ip =~ VALID_IP + end + + def not_a_proxy?(ip) + ip !~ @middleware.proxies + end + + def remove_proxies(ips) + ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) } + end + end end diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 28e8fbdab8..12bc438be3 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -110,7 +110,7 @@ module ActionDispatch def build(app = nil, &block) app ||= block raise "MiddlewareStack#build requires an app" unless app - middlewares.reverse.inject(app) { |a, e| e.build(a) } + middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) } end protected diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index ba4cfb482d..1a1a054985 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/hash/except' +require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/enumerable' require 'active_support/inflector' @@ -58,6 +59,16 @@ module ActionDispatch @options = (@scope[:options] || {}).merge(options) @path = normalize_path(path) normalize_options! + + via_all = @options.delete(:via) if @options[:via] == :all + + if !via_all && request_method_condition.empty? + msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \ + "If you want to expose your action to GET, use `get` in the router:\n\n" \ + " Instead of: match \"controller#action\"\n" \ + " Do: get \"controller#action\"" + raise msg + end end def to_route @@ -263,7 +274,7 @@ module ActionDispatch # of most Rails applications, this is beneficial. def root(options = {}) options = { :to => options } if options.is_a?(String) - match '/', { :as => :root }.merge(options) + match '/', { :as => :root, :via => :get }.merge(options) end # Matches a url pattern to one or more routes. Any symbols in a pattern @@ -416,7 +427,7 @@ module ActionDispatch options[:as] ||= app_name(app) - match(path, options.merge(:to => app, :anchor => false, :format => false)) + match(path, options.merge(:to => app, :anchor => false, :format => false, :via => :all)) define_generate_prefix(app, options[:as]) self @@ -1294,6 +1305,21 @@ module ActionDispatch parent_resource.instance_of?(Resource) && @scope[:shallow] end + def draw(name) + path = @draw_paths.find do |_path| + _path.join("#{name}.rb").file? + end + + unless path + msg = "Your router tried to #draw the external file #{name}.rb,\n" \ + "but the file was not found in:\n\n" + msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n") + raise msg + end + + instance_eval(path.join("#{name}.rb").read) + end + # match 'path' => 'controller#action' # match 'path', to: 'controller#action' # match 'path', 'otherpath', on: :member, via: :get @@ -1543,6 +1569,7 @@ module ActionDispatch def initialize(set) #:nodoc: @set = set + @draw_paths = set.draw_paths @scope = { :path_names => @set.resources_path_names } end diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index 617b24b46a..444a79c50d 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -1,4 +1,6 @@ require 'action_dispatch/http/request' +require 'active_support/core_ext/uri' +require 'rack/utils' module ActionDispatch module Routing @@ -46,8 +48,17 @@ module ActionDispatch :params => request.query_parameters }.merge options + if !params.empty? && url_options[:path].match(/%\{\w*\}/) + url_options[:path] = (url_options[:path] % escape_path(params)) + end + ActionDispatch::Http::URL.url_for url_options end + + private + def escape_path(params) + Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }] + end end module Redirection @@ -67,10 +78,13 @@ module ActionDispatch # params, depending of how many arguments your block accepts. A string is required as a # return value. # - # match 'jokes/:number', :to => redirect do |params, request| - # path = (params[:number].to_i.even? ? "/wheres-the-beef" : "/i-love-lamp") + # match 'jokes/:number', :to => redirect { |params, request| + # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp") # "http://#{request.host_with_port}/#{path}" - # end + # } + # + # Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass + # the block to +match+ instead of +redirect+. Use <tt>{ ... }</tt> instead. # # The options version of redirect allows you to supply only the parts of the url which need # to change, it also supports interpolation of the path similar to the first example. @@ -93,13 +107,18 @@ module ActionDispatch path = args.shift block = lambda { |params, request| - (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params) + (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % escape(params)) } if String === path block = path if path.respond_to? :call raise ArgumentError, "redirection argument not supported" unless block Redirect.new status, block end + + private + def escape(params) + Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }] + end end end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 45075050eb..7a7810a95c 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -96,7 +96,25 @@ module ActionDispatch def initialize @routes = {} @helpers = [] - @module = Module.new + @module = Module.new do + protected + + def handle_positional_args(args, options, route) + inner_options = args.extract_options! + result = options.dup + + if args.any? + keys = route.segment_keys + if args.size < keys.size - 1 # take format into account + keys -= self.url_options.keys if self.respond_to?(:url_options) + keys -= options.keys + end + result.merge!(Hash[keys.zip(args)]) + end + + result.merge!(inner_options) + end + end end def helper_names @@ -135,40 +153,37 @@ module ActionDispatch end private - def url_helper_name(name, kind = :url) - :"#{name}_#{kind}" + def url_helper_name(name, only_path) + if only_path + :"#{name}_path" + else + :"#{name}_url" + end end - def hash_access_name(name, kind = :url) - :"hash_for_#{name}_#{kind}" + def hash_access_name(name, only_path) + if only_path + :"hash_for_#{name}_path" + else + :"hash_for_#{name}_url" + end end def define_named_route_methods(name, route) - {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts| - hash = route.defaults.merge(:use_route => name).merge(opts) - define_hash_access route, name, kind, hash - define_url_helper route, name, kind, hash + [true, false].each do |only_path| + hash = route.defaults.merge(:use_route => name, :only_path => only_path) + define_hash_access route, name, hash + define_url_helper route, name, hash end end - def define_hash_access(route, name, kind, options) - selector = hash_access_name(name, kind) + def define_hash_access(route, name, options) + selector = hash_access_name(name, options[:only_path]) @module.module_eval do - remove_possible_method selector - - define_method(selector) do |*args| - inner_options = args.extract_options! - result = options.dup - - if args.any? - result[:_positional_args] = args - result[:_positional_keys] = route.segment_keys - end - - result.merge(inner_options) + redefine_method(selector) do |*args| + self.handle_positional_args(args, options, route) end - protected selector end helpers << selector @@ -187,9 +202,9 @@ module ActionDispatch # # foo_url(bar, baz, bang, :sort_by => 'baz') # - def define_url_helper(route, name, kind, options) - selector = url_helper_name(name, kind) - hash_access_method = hash_access_name(name, kind) + def define_url_helper(route, name, options) + selector = url_helper_name(name, options[:only_path]) + hash_access_method = hash_access_name(name, options[:only_path]) if optimize_helper?(route) @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 @@ -240,6 +255,7 @@ module ActionDispatch attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :disable_clear_and_finalize, :resources_path_names attr_accessor :default_url_options, :request_class, :valid_conditions + attr_accessor :draw_paths alias :routes :set @@ -251,6 +267,7 @@ module ActionDispatch self.named_routes = NamedRouteCollection.new self.resources_path_names = self.class.default_resources_path_names.dup self.default_url_options = {} + self.draw_paths = [] self.request_class = request_class @valid_conditions = {} @@ -609,8 +626,6 @@ module ActionDispatch def url_for(options) options = default_url_options.merge(options || {}) - handle_positional_args(options) - user, password = extract_authentication(options) path_segments = options.delete(:_path_segments) script_name = options.delete(:script_name).presence || _generate_prefix(options) @@ -680,16 +695,6 @@ module ActionDispatch end end - def handle_positional_args(options) - return unless args = options.delete(:_positional_args) - - keys = options.delete(:_positional_keys) - keys -= options.keys if args.size < keys.size - 1 # take format into account - - # Tell url_for to skip default_url_options - options.merge!(Hash[args.zip(keys).map { |v, k| [k, v] }]) - end - end end end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index d4d09e2ee9..7985683a72 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -442,7 +442,7 @@ module ActionView # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> # video_tag(["trailer.ogg", "trailer.flv"]) # => # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> - # video_tag(["trailer.ogg", "trailer.flv"] :size => "160x120") # => + # video_tag(["trailer.ogg", "trailer.flv"], :size => "160x120") # => # <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video> def video_tag(*sources) multiple_sources_tag('video', sources) do |options| diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index dd4e9ae4cc..35f91cec18 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -1,5 +1,6 @@ require 'thread' require 'active_support/core_ext/file' +require 'active_support/core_ext/module/attribute_accessors' module ActionView module Helpers diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 1f237cd013..76ffb0657b 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -19,7 +19,7 @@ module ActionView # of \date[month]. module DateHelper # Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds. - # Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs. + # Pass <tt>:include_seconds => true</tt> if you want more detailed approximations when distance < 1 min, 29 secs. # Distances are reported based on the following table: # # 0 <-> 29 secs # => less than a minute @@ -36,7 +36,7 @@ module ActionView # 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years # 2 yrs <-> max time or date # => (same rules as 1 yr) # - # With <tt>include_seconds</tt> = true and the difference < 1 minute 29 seconds: + # With <tt>:include_seconds => true</tt> and the difference < 1 minute 29 seconds: # 0-4 secs # => less than 5 seconds # 5-9 secs # => less than 10 seconds # 10-19 secs # => less than 20 seconds @@ -46,36 +46,45 @@ module ActionView # # ==== Examples # from_time = Time.now - # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour - # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour - # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute - # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds - # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years - # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days - # distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute - # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute - # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute - # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year - # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years + # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour + # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour + # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute + # distance_of_time_in_words(from_time, from_time + 15.seconds, :include_seconds => true) # => less than 20 seconds + # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years + # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days + # distance_of_time_in_words(from_time, from_time + 45.seconds, :include_seconds => true) # => less than a minute + # distance_of_time_in_words(from_time, from_time - 45.seconds, :include_seconds => true) # => less than a minute + # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute + # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year + # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years # distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years # # to_time = Time.now + 6.years + 19.days - # distance_of_time_in_words(from_time, to_time, true) # => about 6 years - # distance_of_time_in_words(to_time, from_time, true) # => about 6 years - # distance_of_time_in_words(Time.now, Time.now) # => less than a minute - # - def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {}) + # distance_of_time_in_words(from_time, to_time, :include_seconds => true) # => about 6 years + # distance_of_time_in_words(to_time, from_time, :include_seconds => true) # => about 6 years + # distance_of_time_in_words(Time.now, Time.now) # => less than a minute + # + def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {}) + unless include_seconds_or_options.is_a?(Hash) + ActiveSupport::Deprecation.warn "distance_of_time_in_words and time_ago_in_words now accept :include_seconds " + + "as a part of options hash, not a boolean argument", caller + options[:include_seconds] ||= !!include_seconds_or_options + else + options = include_seconds_or_options + end + from_time = from_time.to_time if from_time.respond_to?(:to_time) to_time = to_time.to_time if to_time.respond_to?(:to_time) - distance_in_minutes = (((to_time - from_time).abs)/60).round - distance_in_seconds = ((to_time - from_time).abs).round + from_time, to_time = to_time, from_time if from_time > to_time + distance_in_minutes = ((to_time - from_time)/60.0).round + distance_in_seconds = (to_time - from_time).round I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale| case distance_in_minutes when 0..1 return distance_in_minutes == 0 ? locale.t(:less_than_x_minutes, :count => 1) : - locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds + locale.t(:x_minutes, :count => distance_in_minutes) unless options[:include_seconds] case distance_in_seconds when 0..4 then locale.t :less_than_x_seconds, :count => 5 @@ -94,18 +103,22 @@ module ActionView when 43200..86399 then locale.t :about_x_months, :count => 1 when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round else - fyear = from_time.year - fyear += 1 if from_time.month >= 3 - tyear = to_time.year - tyear -= 1 if to_time.month < 3 - leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)} - minute_offset_for_leap_year = leap_years * 1440 - # Discount the leap year days when calculating year distance. - # e.g. if there are 20 leap year days between 2 dates having the same day - # and month then the based on 365 days calculation - # the distance in years will come out to over 80 years when in written - # english it would read better as about 80 years. - minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year + if from_time.acts_like?(:time) && to_time.acts_like?(:time) + fyear = from_time.year + fyear += 1 if from_time.month >= 3 + tyear = to_time.year + tyear -= 1 if to_time.month < 3 + leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)} + minute_offset_for_leap_year = leap_years * 1440 + # Discount the leap year days when calculating year distance. + # e.g. if there are 20 leap year days between 2 dates having the same day + # and month then the based on 365 days calculation + # the distance in years will come out to over 80 years when in written + # english it would read better as about 80 years. + minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year + else + minutes_with_offset = distance_in_minutes + end remainder = (minutes_with_offset % 525600) distance_in_years = (minutes_with_offset / 525600) if remainder < 131400 @@ -122,15 +135,16 @@ module ActionView # Like <tt>distance_of_time_in_words</tt>, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>. # # ==== Examples - # time_ago_in_words(3.minutes.from_now) # => 3 minutes - # time_ago_in_words(Time.now - 15.hours) # => about 15 hours - # time_ago_in_words(Time.now) # => less than a minute + # time_ago_in_words(3.minutes.from_now) # => 3 minutes + # time_ago_in_words(Time.now - 15.hours) # => about 15 hours + # time_ago_in_words(Time.now) # => less than a minute + # time_ago_in_words(Time.now, :include_seconds => true) # => less than 5 seconds # # from_time = Time.now - 3.days - 14.minutes - 25.seconds # time_ago_in_words(from_time) # => 3 days # - def time_ago_in_words(from_time, include_seconds = false) - distance_of_time_in_words(from_time, Time.now, include_seconds) + def time_ago_in_words(from_time, include_seconds_or_options = {}) + distance_of_time_in_words(from_time, Time.now, include_seconds_or_options) end alias_method :distance_of_time_in_words_to_now, :time_ago_in_words diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 3f5829d86a..67f2abe509 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -10,6 +10,7 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/array/extract_options' require 'active_support/deprecation' +require 'active_support/core_ext/string/inflections' module ActionView # = Action View Form Helpers @@ -184,7 +185,7 @@ module ActionView # First name: <%= f.text_field :first_name %> # Last name : <%= f.text_field :last_name %> # Biography : <%= text_area :person, :biography %> - # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %> + # Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %> # <%= f.submit %> # <% end %> # @@ -902,7 +903,7 @@ module ActionView # # Let's say that @post.validated? is 1: # check_box("post", "validated") # # => <input name="post[validated]" type="hidden" value="0" /> - # # <input type="checkbox" id="post_validated" name="post[validated]" value="1" /> + # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" /> # # # Let's say that @puppy.gooddog is "no": # check_box("puppy", "gooddog", {}, "yes", "no") @@ -1039,9 +1040,14 @@ module ActionView object_name = ActiveModel::Naming.param_key(object) end - builder = options[:builder] || ActionView::Base.default_form_builder + builder = options[:builder] || default_form_builder builder.new(object_name, object, self, options) end + + def default_form_builder + builder = ActionView::Base.default_form_builder + builder.respond_to?(:constantize) ? builder.constantize : builder + end end class FormBuilder diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 5a869d6303..cc20518b93 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -15,7 +15,6 @@ module ActionView JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '
' JS_ESCAPE_MAP["\342\200\251".force_encoding('UTF-8').encode!] = '
' - # Escapes carriage returns and single and double quotes for JavaScript segments. # @@ -68,40 +67,6 @@ module ActionView def javascript_cdata_section(content) #:nodoc: "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe end - - # Returns a button whose +onclick+ handler triggers the passed JavaScript. - # - # The helper receives a name, JavaScript code, and an optional hash of HTML options. The - # name is used as button label and the JavaScript code goes into its +onclick+ attribute. - # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. - # - # button_to_function "Greeting", "alert('Hello world!')", :class => "ok" - # # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" /> - # - def button_to_function(name, function=nil, html_options={}) - onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};" - - tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick)) - end - - # Returns a link whose +onclick+ handler triggers the passed JavaScript. - # - # The helper receives a name, JavaScript code, and an optional hash of HTML options. The - # name is used as the link text and the JavaScript code goes into the +onclick+ attribute. - # If +html_options+ has an <tt>:onclick</tt>, that one is put before +function+. Once all - # the JavaScript is set, the helper appends "; return false;". - # - # The +href+ attribute of the tag is set to "#" unless +html_options+ has one. - # - # link_to_function "Greeting", "alert('Hello world!')", :class => "nav_link" - # # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a> - # - def link_to_function(name, function, html_options={}) - onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;" - href = html_options[:href] || '#' - - content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick)) - end end end end diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index d3a7a1bb96..2011351bd2 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -233,7 +233,7 @@ module ActionView parts = number.to_s.to_str.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") - parts.join(options[:separator]).html_safe + safe_join(parts, options[:separator]) end # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision @@ -392,10 +392,10 @@ module ActionView # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, <tt>:billion</tt>, <tt>:trillion</tt>, <tt>:quadrillion</tt> # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, <tt>:pico</tt>, <tt>:femto</tt> # * <tt>:format</tt> - Sets the format of the output string (defaults to "%n %u"). The field types are: - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. - # # %u The quantifier (ex.: 'thousand') # %n The number + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. + # # # ==== Examples # number_to_human(123) # => "123" diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 1a15459406..9b35f076e5 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -94,10 +94,10 @@ module ActionView # for each record. def content_tag_for_single_record(tag_name, record, prefix, options, &block) options = options ? options.dup : {} - options.merge!(:class => "#{dom_class(record, prefix)} #{options[:class]}".rstrip, :id => dom_id(record, prefix)) + options[:class] = "#{dom_class(record, prefix)} #{options[:class]}".rstrip + options[:id] = dom_id(record, prefix) - content = block.arity == 0 ? capture(&block) : capture(record, &block) - content_tag(tag_name, content, options) + content_tag(tag_name, capture(record, &block), options) end end end diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionpack/lib/action_view/helpers/tags/check_box.rb index 1a4aebb936..9d17a1dde3 100644 --- a/actionpack/lib/action_view/helpers/tags/check_box.rb +++ b/actionpack/lib/action_view/helpers/tags/check_box.rb @@ -41,17 +41,15 @@ module ActionView def checked?(value) case value when TrueClass, FalseClass - value + value == !!@checked_value when NilClass false - when Integer - value != 0 when String value == @checked_value when Array value.include?(@checked_value) else - value.to_i != 0 + value.to_i == @checked_value.to_i end end diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index cc74eff53a..fd06bfa2a8 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -45,6 +45,7 @@ module ActionView # you know what kind of output to expect when you call translate in a template. def translate(key, options = {}) options.merge!(:rescue_format => :html) unless options.key?(:rescue_format) + options[:default] = wrap_translate_defaults(options[:default]) if options[:default] if html_safe_translation_key?(key) html_safe_options = options.dup options.except(*I18n::RESERVED_KEYS).each do |name, value| @@ -83,6 +84,21 @@ module ActionView def html_safe_translation_key?(key) key.to_s =~ /(\b|_|\.)html$/ end + + def wrap_translate_defaults(defaults) + new_defaults = [] + defaults = Array(defaults) + while key = defaults.shift + if key.is_a?(Symbol) + new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) } + break + else + new_defautls << key + end + end + + new_defaults + end end end end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 8b53867aea..c5d5540510 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -158,8 +158,8 @@ module ActionView # Name: <%= user.name %> # </div> # - # If a collection is given, the layout will be rendered once for each item in the collection. Just think - # these two snippets have the same output: + # If a collection is given, the layout will be rendered once for each item in + # the collection. Just think these two snippets have the same output: # # <%# app/views/users/_user.html.erb %> # Name: <%= user.name %> @@ -184,7 +184,7 @@ module ActionView # <%= render :partial => "user", :layout => "li_layout", :collection => users %> # </ul> # - # Given two users whose names are Alice and Bob, these snippets return: + # Given two users whose names are Alice and Bob, these snippets return: # # <ul> # <li> @@ -195,6 +195,10 @@ module ActionView # </li> # </ul> # + # The current object being rendered, as well as the object_counter, will be + # available as local variables inside the layout template under the same names + # as available in the partial. + # # You can also apply a layout to a block within any template: # # <%# app/views/users/_chief.html.erb &> @@ -282,14 +286,7 @@ module ActionView spacer = find_template(@options[:spacer_template]).render(@view, @locals) end - if layout = @options[:layout] - layout = find_template(layout) - end - result = @template ? collection_with_template : collection_without_template - - result.map!{|content| layout.render(@view, @locals) { content } } if layout - result.join(spacer).html_safe end @@ -298,7 +295,7 @@ module ActionView object, as = @object, @variable if !block && (layout = @options[:layout]) - layout = find_template(layout) + layout = find_template(layout, @locals.keys + [@variable]) end object ||= locals[as] @@ -384,17 +381,23 @@ module ActionView segments, locals, template = [], @locals, @template as, counter = @variable, @variable_counter + if layout = @options[:layout] + layout = find_template(layout, @locals.keys + [@variable, @variable_counter]) + end + locals[counter] = -1 @collection.each do |object| locals[counter] += 1 locals[as] = object - segments << template.render(@view, locals) + + content = template.render(@view, locals) + content = layout.render(@view, locals) { content } if layout + segments << content end - + segments end - def collection_without_template segments, locals, collection_data = [], @locals, @collection_data @@ -451,7 +454,7 @@ module ActionView end def retrieve_variable(path) - variable = @options[:as].try(:to_sym) || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym + variable = @options.fetch(:as) { path[%r'_?(\w+)(\.\w+)*$', 1] }.try(:to_sym) variable_counter = :"#{variable}_counter" if @collection [variable, variable_counter] end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index b1a5356ddd..22ba047328 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -125,11 +125,11 @@ module ActiveSupport # have been loaded. setup_once do SharedTestRoutes.draw do - match ':controller(/:action)' + get ':controller(/:action)' end ActionDispatch::IntegrationTest.app.routes.draw do - match ':controller(/:action)' + get ':controller(/:action)' end end end diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb index 2fe7959f5a..fceebe1858 100644 --- a/actionpack/test/activerecord/active_record_store_test.rb +++ b/actionpack/test/activerecord/active_record_store_test.rb @@ -259,7 +259,7 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest def with_test_route_set(options = {}) with_routing do |set| set.draw do - match ':action', :to => 'active_record_store_test/test' + get ':action', :to => 'active_record_store_test/test' end @app = self.class.build_app(set) do |middleware| diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb index 8187eb72d5..409370104d 100644 --- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb +++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb @@ -16,7 +16,7 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base end def render_with_has_many_through_association - @developer = Developer.find(:first) + @developer = Developer.first render :partial => @developer.topics end @@ -31,7 +31,7 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base end def render_with_record - @developer = Developer.find(:first) + @developer = Developer.first render :partial => @developer end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 01cafe1aca..b121ca9481 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -162,7 +162,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_string_constraint with_routing do |set| set.draw do - match "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"} + get "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"} end end end @@ -170,9 +170,9 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_assert_redirect_to_named_route_failure with_routing do |set| set.draw do - match 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one - match 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two - match ':controller/:action' + get 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one + get 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two + get ':controller/:action' end process :redirect_to_named_route assert_raise(ActiveSupport::TestCase::Assertion) do @@ -192,8 +192,8 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase with_routing do |set| set.draw do - match 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module - match ':controller/:action' + get 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module + get ':controller/:action' end process :redirect_to_index # redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}> @@ -206,8 +206,8 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase with_routing do |set| set.draw do - match '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level - match ':controller/:action' + get '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level + get ':controller/:action' end process :redirect_to_top_level_named_route # assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return @@ -221,8 +221,8 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase with_routing do |set| set.draw do # this controller exists in the admin namespace as well which is the only difference from previous test - match '/user/:id', :to => 'user#index', :as => :top_level - match ':controller/:action' + get '/user/:id', :to => 'user#index', :as => :top_level + get ':controller/:action' end process :redirect_to_top_level_named_route # assert_redirected_to top_level_url('foo') would pass because of exact match early return diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 2032aca52e..b9513ccff4 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -158,7 +158,7 @@ class UrlOptionsTest < ActionController::TestCase def test_url_for_query_params_included rs = ActionDispatch::Routing::RouteSet.new rs.draw do - match 'home' => 'pages#home' + get 'home' => 'pages#home' end options = { @@ -174,8 +174,8 @@ class UrlOptionsTest < ActionController::TestCase def test_url_options_override with_routing do |set| set.draw do - match 'from_view', :to => 'url_options#from_view', :as => :from_view - match ':controller/:action' + get 'from_view', :to => 'url_options#from_view', :as => :from_view + get ':controller/:action' end get :from_view, :route => "from_view_url" @@ -189,7 +189,7 @@ class UrlOptionsTest < ActionController::TestCase def test_url_helpers_does_not_become_actions with_routing do |set| set.draw do - match "account/overview" + get "account/overview" end assert !@controller.class.action_methods.include?("account_overview_path") @@ -208,8 +208,8 @@ class DefaultUrlOptionsTest < ActionController::TestCase def test_default_url_options_override with_routing do |set| set.draw do - match 'from_view', :to => 'default_url_options#from_view', :as => :from_view - match ':controller/:action' + get 'from_view', :to => 'default_url_options#from_view', :as => :from_view + get ':controller/:action' end get :from_view, :route => "from_view_url" @@ -226,7 +226,7 @@ class DefaultUrlOptionsTest < ActionController::TestCase scope("/:locale") do resources :descriptions end - match ':controller/:action' + get ':controller/:action' end get :from_view, :route => "description_path(1)" diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index a42c68a628..e8546cb154 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -102,8 +102,8 @@ class PageCachingTest < ActionController::TestCase def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route with_routing do |set| set.draw do - match 'posts.:format', :to => 'posts#index', :as => :formatted_posts - match '/', :to => 'posts#index', :as => :main + get 'posts.:format', :to => 'posts#index', :as => :formatted_posts + get '/', :to => 'posts#index', :as => :main end @params[:format] = 'rss' assert_equal '/posts.rss', @routes.url_for(@params) @@ -560,7 +560,7 @@ class ActionCacheTest < ActionController::TestCase def test_xml_version_of_resource_is_treated_as_different_cache with_routing do |set| set.draw do - match ':controller(/:action(.:format))' + get ':controller(/:action(.:format))' end get :index, :format => 'xml' diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 37ccc7a4a5..e4b34125ad 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -277,7 +277,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest def with_test_route_set with_routing do |set| set.draw do - match ':action', :to => FlashIntegrationTest::TestController + get ':action', :to => FlashIntegrationTest::TestController end @app = self.class.build_app(set) do |middleware| diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 44f033119d..877b91b563 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -466,7 +466,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest end set.draw do - match ':action', :to => controller + match ':action', :to => controller, :via => [:get, :post] get 'get/:action', :to => controller end @@ -530,10 +530,10 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest end routes.draw do - match '', :to => 'application_integration_test/test#index', :as => :empty_string + get '', :to => 'application_integration_test/test#index', :as => :empty_string - match 'foo', :to => 'application_integration_test/test#index', :as => :foo - match 'bar', :to => 'application_integration_test/test#index', :as => :bar + get 'foo', :to => 'application_integration_test/test#index', :as => :foo + get 'bar', :to => 'application_integration_test/test#index', :as => :bar end def app diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 0e4099ddc6..ac056319fc 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -1118,7 +1118,7 @@ class RespondWithControllerTest < ActionController::TestCase resources :quiz_stores do resources :customers end - match ":controller/:action" + get ":controller/:action" end yield end diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb index 4b70031c90..9b57641e75 100644 --- a/actionpack/test/controller/new_base/content_type_test.rb +++ b/actionpack/test/controller/new_base/content_type_test.rb @@ -43,7 +43,7 @@ module ContentType test "default response is HTML and UTF8" do with_routing do |set| set.draw do - match ':controller', :action => 'index' + get ':controller', :action => 'index' end get "/content_type/base" diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb index ade204c387..00c7df2af8 100644 --- a/actionpack/test/controller/new_base/render_template_test.rb +++ b/actionpack/test/controller/new_base/render_template_test.rb @@ -164,7 +164,7 @@ module RenderTemplate test "rendering with implicit layout" do with_routing do |set| - set.draw { match ':controller', :action => :index } + set.draw { get ':controller', :action => :index } get "/render_template/with_layout" diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb index 60468bf5c7..cc7f12ac6d 100644 --- a/actionpack/test/controller/new_base/render_test.rb +++ b/actionpack/test/controller/new_base/render_test.rb @@ -57,7 +57,7 @@ module Render test "render with blank" do with_routing do |set| set.draw do - match ":controller", :action => 'index' + get ":controller", :action => 'index' end get "/render/blank_render" @@ -70,7 +70,7 @@ module Render test "rendering more than once raises an exception" do with_routing do |set| set.draw do - match ":controller", :action => 'index' + get ":controller", :action => 'index' end assert_raises(AbstractController::DoubleRenderError) do diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb index 06d500cca7..e0b38b29fa 100644 --- a/actionpack/test/controller/new_base/render_text_test.rb +++ b/actionpack/test/controller/new_base/render_text_test.rb @@ -67,7 +67,7 @@ module RenderText test "rendering text from a action with default options renders the text with the layout" do with_routing do |set| - set.draw { match ':controller', :action => 'index' } + set.draw { get ':controller', :action => 'index' } get "/render_text/simple" assert_body "hello david" @@ -77,7 +77,7 @@ module RenderText test "rendering text from a action with default options renders the text without the layout" do with_routing do |set| - set.draw { match ':controller', :action => 'index' } + set.draw { get ':controller', :action => 'index' } get "/render_text/with_layout" diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 6dab42d75d..4331333b98 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -262,7 +262,7 @@ class RedirectTest < ActionController::TestCase with_routing do |set| set.draw do resources :workshops - match ':controller/:action' + get ':controller/:action' end get :redirect_to_existing_record @@ -296,7 +296,7 @@ class RedirectTest < ActionController::TestCase def test_redirect_to_with_block_and_accepted_options with_routing do |set| set.draw do - match ':controller/:action' + get ':controller/:action' end get :redirect_to_with_block_and_options diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb index 75fed8e933..7c0a6bd67e 100644 --- a/actionpack/test/controller/render_json_test.rb +++ b/actionpack/test/controller/render_json_test.rb @@ -102,7 +102,7 @@ class RenderJsonTest < ActionController::TestCase def test_render_json_with_callback get :render_json_hello_world_with_callback assert_equal 'alert({"hello":"world"})', @response.body - assert_equal 'application/json', @response.content_type + assert_equal 'text/javascript', @response.content_type end def test_render_json_with_custom_content_type diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index fce13d096c..10f62dad65 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -1188,7 +1188,7 @@ class RenderTest < ActionController::TestCase with_routing do |set| set.draw do resources :customers - match ':controller/:action' + get ':controller/:action' end get :head_with_location_object diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb index 8b4f2f5349..4f280c4bec 100644 --- a/actionpack/test/controller/render_xml_test.rb +++ b/actionpack/test/controller/render_xml_test.rb @@ -72,7 +72,7 @@ class RenderXmlTest < ActionController::TestCase with_routing do |set| set.draw do resources :customers - match ':controller/:action' + get ':controller/:action' end get :render_with_object_location diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 9c51db135b..48e2d6491e 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -60,11 +60,6 @@ class RescueController < ActionController::Base render :text => exception.message end - # This is a Dispatcher exception and should be in ApplicationController. - rescue_from ActionController::RoutingError do - render :text => 'no way' - end - rescue_from ActionView::TemplateError do render :text => 'action_view templater error' end @@ -343,9 +338,9 @@ class RescueTest < ActionDispatch::IntegrationTest def with_test_routing with_routing do |set| set.draw do - match 'foo', :to => ::RescueTest::TestController.action(:foo) - match 'invalid', :to => ::RescueTest::TestController.action(:invalid) - match 'b00m', :to => ::RescueTest::TestController.action(:b00m) + get 'foo', :to => ::RescueTest::TestController.action(:foo) + get 'invalid', :to => ::RescueTest::TestController.action(:invalid) + get 'b00m', :to => ::RescueTest::TestController.action(:b00m) end yield end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 3c0a5d36ca..9fc875014c 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -680,7 +680,7 @@ class ResourcesTest < ActionController::TestCase scope '/threads/:thread_id' do resources :messages, :as => 'thread_messages' do get :search, :on => :collection - match :preview, :on => :new + get :preview, :on => :new end end end @@ -698,7 +698,7 @@ class ResourcesTest < ActionController::TestCase scope '/admin' do resource :account, :as => :admin_account do get :login, :on => :member - match :preview, :on => :new + get :preview, :on => :new end end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 272a7da8c5..9441b46d47 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -17,7 +17,7 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase def setup @set = ActionDispatch::Routing::RouteSet.new @set.draw do - match ':controller/:action/:variable/*additional' + get ':controller/:action/:variable/*additional' end safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ]) @@ -85,7 +85,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_symbols_with_dashes rs.draw do - match '/:artist/:song-omg', :to => lambda { |env| + get '/:artist/:song-omg', :to => lambda { |env| resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] [200, {}, [resp]] } @@ -97,7 +97,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_id_with_dash rs.draw do - match '/journey/:id', :to => lambda { |env| + get '/journey/:id', :to => lambda { |env| resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] [200, {}, [resp]] } @@ -109,7 +109,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_dash_with_custom_regexp rs.draw do - match '/:artist/:song-omg', :constraints => { :song => /\d+/ }, :to => lambda { |env| + get '/:artist/:song-omg', :constraints => { :song => /\d+/ }, :to => lambda { |env| resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] [200, {}, [resp]] } @@ -122,7 +122,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_pre_dash rs.draw do - match '/:artist/omg-:song', :to => lambda { |env| + get '/:artist/omg-:song', :to => lambda { |env| resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] [200, {}, [resp]] } @@ -134,7 +134,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_pre_dash_with_custom_regexp rs.draw do - match '/:artist/omg-:song', :constraints => { :song => /\d+/ }, :to => lambda { |env| + get '/:artist/omg-:song', :constraints => { :song => /\d+/ }, :to => lambda { |env| resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] [200, {}, [resp]] } @@ -147,7 +147,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_star_paths_are_greedy rs.draw do - match "/*path", :to => lambda { |env| + get "/*path", :to => lambda { |env| x = env["action_dispatch.request.path_parameters"][:path] [200, {}, [x]] }, :format => false @@ -159,7 +159,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_star_paths_are_greedy_but_not_too_much rs.draw do - match "/*path", :to => lambda { |env| + get "/*path", :to => lambda { |env| x = JSON.dump env["action_dispatch.request.path_parameters"] [200, {}, [x]] } @@ -172,7 +172,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_optional_star_paths_are_greedy rs.draw do - match "/(*filters)", :to => lambda { |env| + get "/(*filters)", :to => lambda { |env| x = env["action_dispatch.request.path_parameters"][:filters] [200, {}, [x]] }, :format => false @@ -184,7 +184,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_optional_star_paths_are_greedy_but_not_too_much rs.draw do - match "/(*filters)", :to => lambda { |env| + get "/(*filters)", :to => lambda { |env| x = JSON.dump env["action_dispatch.request.path_parameters"] [200, {}, [x]] } @@ -198,11 +198,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_regexp_precidence @rs.draw do - match '/whois/:domain', :constraints => { + get '/whois/:domain', :constraints => { :domain => /\w+\.[\w\.]+/ }, :to => lambda { |env| [200, {}, %w{regexp}] } - match '/whois/:id', :to => lambda { |env| [200, {}, %w{id}] } + get '/whois/:id', :to => lambda { |env| [200, {}, %w{id}] } end assert_equal 'regexp', get(URI('http://example.org/whois/example.org')) @@ -217,9 +217,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase } @rs.draw do - match '/', :constraints => subdomain.new, + get '/', :constraints => subdomain.new, :to => lambda { |env| [200, {}, %w{default}] } - match '/', :constraints => { :subdomain => 'clients' }, + get '/', :constraints => { :subdomain => 'clients' }, :to => lambda { |env| [200, {}, %w{clients}] } end @@ -229,11 +229,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_lambda_constraints @rs.draw do - match '/', :constraints => lambda { |req| + get '/', :constraints => lambda { |req| req.subdomain.present? and req.subdomain != "clients" }, :to => lambda { |env| [200, {}, %w{default}] } - match '/', :constraints => lambda { |req| + get '/', :constraints => lambda { |req| req.subdomain.present? && req.subdomain == "clients" }, :to => lambda { |env| [200, {}, %w{clients}] } end @@ -271,7 +271,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase end def test_default_setup - @rs.draw { match '/:controller(/:action(/:id))' } + @rs.draw { get '/:controller(/:action(/:id))' } assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content")) assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list")) assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10")) @@ -289,21 +289,21 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_ignores_leading_slash @rs.clear! - @rs.draw { match '/:controller(/:action(/:id))'} + @rs.draw { get '/:controller(/:action(/:id))'} test_default_setup end def test_route_with_colon_first rs.draw do - match '/:controller/:action/:id', :action => 'index', :id => nil - match ':url', :controller => 'tiny_url', :action => 'translate' + get '/:controller/:action/:id', :action => 'index', :id => nil + get ':url', :controller => 'tiny_url', :action => 'translate' end end def test_route_with_regexp_for_controller rs.draw do - match ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/ - match '/:controller(/:action(/:id))' + get ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/ + get '/:controller(/:action(/:id))' end assert_equal({:controller => "admin/user", :admintoken => "foo", :action => "index"}, @@ -317,7 +317,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_route_with_regexp_and_captures_for_controller rs.draw do - match '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/ + get '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/ end assert_equal({:controller => "admin/accounts", :action => "index"}, rs.recognize_path("/admin/accounts")) assert_equal({:controller => "admin/users", :action => "index"}, rs.recognize_path("/admin/users")) @@ -326,7 +326,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_route_with_regexp_and_dot rs.draw do - match ':controller/:action/:file', + get ':controller/:action/:file', :controller => /admin|user/, :action => /upload|download/, :defaults => {:file => nil}, @@ -356,7 +356,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_with_option rs.draw do - match 'page/:title' => 'content#show_page', :as => 'page' + get 'page/:title' => 'content#show_page', :as => 'page' end assert_equal("http://test.host/page/new%20stuff", @@ -365,7 +365,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_with_default rs.draw do - match 'page/:title' => 'content#show_page', :title => 'AboutPage', :as => 'page' + get 'page/:title' => 'content#show_page', :title => 'AboutPage', :as => 'page' end assert_equal("http://test.host/page/AboutRails", @@ -375,7 +375,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_with_path_prefix rs.draw do scope "my" do - match 'page' => 'content#show_page', :as => 'page' + get 'page' => 'content#show_page', :as => 'page' end end @@ -386,7 +386,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_with_blank_path_prefix rs.draw do scope "" do - match 'page' => 'content#show_page', :as => 'page' + get 'page' => 'content#show_page', :as => 'page' end end @@ -396,7 +396,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_with_nested_controller rs.draw do - match 'admin/user' => 'admin/user#index', :as => "users" + get 'admin/user' => 'admin/user#index', :as => "users" end assert_equal("http://test.host/admin/user", @@ -405,7 +405,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_optimised_named_route_with_host rs.draw do - match 'page' => 'content#show_page', :as => 'pages', :host => 'foo.com' + get 'page' => 'content#show_page', :as => 'pages', :host => 'foo.com' end routes = setup_for_named_route routes.expects(:url_for).with({ @@ -424,7 +424,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_without_hash rs.draw do - match ':controller/:action/:id', :as => 'normal' + get ':controller/:action/:id', :as => 'normal' end end @@ -448,9 +448,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_with_regexps rs.draw do - match 'page/:year/:month/:day/:title' => 'page#show', :as => 'article', + get 'page/:year/:month/:day/:title' => 'page#show', :as => 'article', :year => /\d+/, :month => /\d+/, :day => /\d+/ - match ':controller/:action/:id' + get ':controller/:action/:id' end routes = setup_for_named_route @@ -460,7 +460,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase end def test_changing_controller - @rs.draw { match ':controller/:action/:id' } + @rs.draw { get ':controller/:action/:id' } assert_equal '/admin/stuff/show/10', url_for(rs, {:controller => 'stuff', :action => 'show', :id => 10}, @@ -469,8 +469,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_paths_escaped rs.draw do - match 'file/*path' => 'content#show_file', :as => 'path' - match ':controller/:action/:id' + get 'file/*path' => 'content#show_file', :as => 'path' + get ':controller/:action/:id' end # No + to space in URI escaping, only for query params. @@ -486,7 +486,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_paths_slashes_unescaped_with_ordered_parameters rs.draw do - match '/file/*path' => 'content#index', :as => 'path' + get '/file/*path' => 'content#index', :as => 'path' end # No / to %2F in URI, only for query params. @@ -495,14 +495,14 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_non_controllers_cannot_be_matched rs.draw do - match ':controller/:action/:id' + get ':controller/:action/:id' end assert_raise(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") } end def test_should_list_options_diff_when_routing_constraints_dont_match rs.draw do - match 'post/:id' => 'post#show', :constraints => { :id => /\d+/ }, :as => 'post' + get 'post/:id' => 'post#show', :constraints => { :id => /\d+/ }, :as => 'post' end assert_raise(ActionController::RoutingError) do url_for(rs, { :controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post" }) @@ -511,7 +511,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_dynamic_path_allowed rs.draw do - match '*path' => 'content#show_file' + get '*path' => 'content#show_file' end assert_equal '/pages/boo', @@ -520,7 +520,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_dynamic_recall_paths_allowed rs.draw do - match '*path' => 'content#show_file' + get '*path' => 'content#show_file' end assert_equal '/pages/boo', @@ -529,8 +529,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_backwards rs.draw do - match 'page/:id(/:action)' => 'pages#show' - match ':controller(/:action(/:id))' + get 'page/:id(/:action)' => 'pages#show' + get ':controller(/:action(/:id))' end assert_equal '/page/20', url_for(rs, { :id => 20 }, { :controller => 'pages', :action => 'show' }) @@ -540,8 +540,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_route_with_fixnum_default rs.draw do - match 'page(/:id)' => 'content#show_page', :id => 1 - match ':controller/:action/:id' + get 'page(/:id)' => 'content#show_page', :id => 1 + get ':controller/:action/:id' end assert_equal '/page', url_for(rs, { :controller => 'content', :action => 'show_page' }) @@ -557,8 +557,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase # For newer revision def test_route_with_text_default rs.draw do - match 'page/:id' => 'content#show_page', :id => 1 - match ':controller/:action/:id' + get 'page/:id' => 'content#show_page', :id => 1 + get ':controller/:action/:id' end assert_equal '/page/foo', url_for(rs, { :controller => 'content', :action => 'show_page', :id => 'foo' }) @@ -573,13 +573,13 @@ class LegacyRouteSetTests < ActiveSupport::TestCase end def test_action_expiry - @rs.draw { match ':controller(/:action(/:id))' } + @rs.draw { get ':controller(/:action(/:id))' } assert_equal '/content', url_for(rs, { :controller => 'content' }, { :controller => 'content', :action => 'show' }) end def test_requirement_should_prevent_optional_id rs.draw do - match 'post/:id' => 'post#show', :constraints => {:id => /\d+/}, :as => 'post' + get 'post/:id' => 'post#show', :constraints => {:id => /\d+/}, :as => 'post' end assert_equal '/post/10', url_for(rs, { :controller => 'post', :action => 'show', :id => 10 }) @@ -591,11 +591,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_both_requirement_and_optional rs.draw do - match('test(/:year)' => 'post#show', :as => 'blog', + get('test(/:year)' => 'post#show', :as => 'blog', :defaults => { :year => nil }, :constraints => { :year => /\d{4}/ } ) - match ':controller/:action/:id' + get ':controller/:action/:id' end assert_equal '/test', url_for(rs, { :controller => 'post', :action => 'show' }) @@ -606,8 +606,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_set_to_nil_forgets rs.draw do - match 'pages(/:year(/:month(/:day)))' => 'content#list_pages', :month => nil, :day => nil - match ':controller/:action/:id' + get 'pages(/:year(/:month(/:day)))' => 'content#list_pages', :month => nil, :day => nil + get ':controller/:action/:id' end assert_equal '/pages/2005', @@ -649,8 +649,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_named_route_method rs.draw do - match 'categories' => 'content#categories', :as => 'categories' - match ':controller(/:action(/:id))' + get 'categories' => 'content#categories', :as => 'categories' + get ':controller(/:action(/:id))' end assert_equal '/categories', url_for(rs, { :controller => 'content', :action => 'categories' }) @@ -664,9 +664,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_nil_defaults rs.draw do - match 'journal' => 'content#list_journal', + get 'journal' => 'content#list_journal', :date => nil, :user_id => nil - match ':controller/:action/:id' + get ':controller/:action/:id' end assert_equal '/journal', url_for(rs, { @@ -698,7 +698,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_recognize_array_of_methods rs.draw do match '/match' => 'books#get_or_post', :via => [:get, :post] - match '/match' => 'books#not_get_or_post' + put '/match' => 'books#not_get_or_post' end params = rs.recognize_path("/match", :method => :post) @@ -710,10 +710,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_subpath_recognized rs.draw do - match '/books/:id/edit' => 'subpath_books#edit' - match '/items/:id/:action' => 'subpath_books' - match '/posts/new/:action' => 'subpath_books' - match '/posts/:id' => 'subpath_books#show' + get '/books/:id/edit' => 'subpath_books#edit' + get '/items/:id/:action' => 'subpath_books' + get '/posts/new/:action' => 'subpath_books' + get '/posts/:id' => 'subpath_books#show' end hash = rs.recognize_path "/books/17/edit" @@ -735,9 +735,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_subpath_generated rs.draw do - match '/books/:id/edit' => 'subpath_books#edit' - match '/items/:id/:action' => 'subpath_books' - match '/posts/new/:action' => 'subpath_books' + get '/books/:id/edit' => 'subpath_books#edit' + get '/items/:id/:action' => 'subpath_books' + get '/posts/new/:action' => 'subpath_books' end assert_equal "/books/7/edit", url_for(rs, { :controller => "subpath_books", :id => 7, :action => "edit" }) @@ -747,7 +747,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_failed_constraints_raises_exception_with_violated_constraints rs.draw do - match 'foos/:id' => 'foos#show', :as => 'foo_with_requirement', :constraints => { :id => /\d+/ } + get 'foos/:id' => 'foos#show', :as => 'foo_with_requirement', :constraints => { :id => /\d+/ } end assert_raise(ActionController::RoutingError) do @@ -758,11 +758,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_routes_changed_correctly_after_clear rs = ::ActionDispatch::Routing::RouteSet.new rs.draw do - match 'ca' => 'ca#aa' - match 'cb' => 'cb#ab' - match 'cc' => 'cc#ac' - match ':controller/:action/:id' - match ':controller/:action/:id.:format' + get 'ca' => 'ca#aa' + get 'cb' => 'cb#ab' + get 'cc' => 'cc#ac' + get ':controller/:action/:id' + get ':controller/:action/:id.:format' end hash = rs.recognize_path "/cc" @@ -771,10 +771,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal %w(cc ac), [hash[:controller], hash[:action]] rs.draw do - match 'cb' => 'cb#ab' - match 'cc' => 'cc#ac' - match ':controller/:action/:id' - match ':controller/:action/:id.:format' + get 'cb' => 'cb#ab' + get 'cc' => 'cc#ac' + get ':controller/:action/:id' + get ':controller/:action/:id.:format' end hash = rs.recognize_path "/cc" @@ -799,29 +799,29 @@ class RouteSetTest < ActiveSupport::TestCase @default_route_set ||= begin set = ROUTING::RouteSet.new set.draw do - match '/:controller(/:action(/:id))' + get '/:controller(/:action(/:id))' end set end end def test_generate_extras - set.draw { match ':controller/(:action(/:id))' } + set.draw { get ':controller/(:action(/:id))' } path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") assert_equal "/foo/bar/15", path assert_equal %w(that this), extras.map { |e| e.to_s }.sort end def test_extra_keys - set.draw { match ':controller/:action/:id' } + set.draw { get ':controller/:action/:id' } extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") assert_equal %w(that this), extras.map { |e| e.to_s }.sort end def test_generate_extras_not_first set.draw do - match ':controller/:action/:id.:format' - match ':controller/:action/:id' + get ':controller/:action/:id.:format' + get ':controller/:action/:id' end path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") assert_equal "/foo/bar/15", path @@ -830,8 +830,8 @@ class RouteSetTest < ActiveSupport::TestCase def test_generate_not_first set.draw do - match ':controller/:action/:id.:format' - match ':controller/:action/:id' + get ':controller/:action/:id.:format' + get ':controller/:action/:id' end assert_equal "/foo/bar/15?this=hello", url_for(set, { :controller => "foo", :action => "bar", :id => 15, :this => "hello" }) @@ -839,8 +839,8 @@ class RouteSetTest < ActiveSupport::TestCase def test_extra_keys_not_first set.draw do - match ':controller/:action/:id.:format' - match ':controller/:action/:id' + get ':controller/:action/:id.:format' + get ':controller/:action/:id' end extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") assert_equal %w(that this), extras.map { |e| e.to_s }.sort @@ -849,7 +849,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_draw assert_equal 0, set.routes.size set.draw do - match '/hello/world' => 'a#b' + get '/hello/world' => 'a#b' end assert_equal 1, set.routes.size end @@ -857,7 +857,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_draw_symbol_controller_name assert_equal 0, set.routes.size set.draw do - match '/users/index' => 'users#index' + get '/users/index' => 'users#index' end set.recognize_path('/users/index', :method => :get) assert_equal 1, set.routes.size @@ -866,7 +866,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_named_draw assert_equal 0, set.routes.size set.draw do - match '/hello/world' => 'a#b', :as => 'hello' + get '/hello/world' => 'a#b', :as => 'hello' end assert_equal 1, set.routes.size assert_equal set.routes.first, set.named_routes[:hello] @@ -874,18 +874,18 @@ class RouteSetTest < ActiveSupport::TestCase def test_earlier_named_routes_take_precedence set.draw do - match '/hello/world' => 'a#b', :as => 'hello' - match '/hello' => 'a#b', :as => 'hello' + get '/hello/world' => 'a#b', :as => 'hello' + get '/hello' => 'a#b', :as => 'hello' end assert_equal set.routes.first, set.named_routes[:hello] end def setup_named_route_test set.draw do - match '/people(/:id)' => 'people#show', :as => 'show' - match '/people' => 'people#index', :as => 'index' - match '/people/go/:foo/:bar/joe(/:id)' => 'people#multi', :as => 'multi' - match '/admin/users' => 'admin/users#index', :as => "users" + get '/people(/:id)' => 'people#show', :as => 'show' + get '/people' => 'people#index', :as => 'index' + get '/people/go/:foo/:bar/joe(/:id)' => 'people#multi', :as => 'multi' + get '/admin/users' => 'admin/users#index', :as => "users" end MockController.build(set.url_helpers).new @@ -985,7 +985,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_draw_default_route set.draw do - match '/:controller/:action/:id' + get '/:controller/:action/:id' end assert_equal 1, set.routes.size @@ -999,8 +999,8 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_with_parameter_shell set.draw do - match 'page/:id' => 'pages#show', :id => /\d+/ - match '/:controller(/:action(/:id))' + get 'page/:id' => 'pages#show', :id => /\d+/ + get '/:controller(/:action(/:id))' end assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages')) @@ -1014,7 +1014,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_constraints_on_request_object_with_anchors_are_valid assert_nothing_raised do set.draw do - match 'page/:id' => 'pages#show', :constraints => { :host => /^foo$/ } + get 'page/:id' => 'pages#show', :constraints => { :host => /^foo$/ } end end end @@ -1022,27 +1022,27 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_constraints_with_anchor_chars_are_invalid assert_raise ArgumentError do set.draw do - match 'page/:id' => 'pages#show', :id => /^\d+/ + get 'page/:id' => 'pages#show', :id => /^\d+/ end end assert_raise ArgumentError do set.draw do - match 'page/:id' => 'pages#show', :id => /\A\d+/ + get 'page/:id' => 'pages#show', :id => /\A\d+/ end end assert_raise ArgumentError do set.draw do - match 'page/:id' => 'pages#show', :id => /\d+$/ + get 'page/:id' => 'pages#show', :id => /\d+$/ end end assert_raise ArgumentError do set.draw do - match 'page/:id' => 'pages#show', :id => /\d+\Z/ + get 'page/:id' => 'pages#show', :id => /\d+\Z/ end end assert_raise ArgumentError do set.draw do - match 'page/:id' => 'pages#show', :id => /\d+\z/ + get 'page/:id' => 'pages#show', :id => /\d+\z/ end end end @@ -1057,7 +1057,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_recognize_with_encoded_id_and_regex set.draw do - match 'page/:id' => 'pages#show', :id => /[a-zA-Z0-9\+]+/ + get 'page/:id' => 'pages#show', :id => /[a-zA-Z0-9\+]+/ end assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) @@ -1128,7 +1128,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_typo_recognition set.draw do - match 'articles/:year/:month/:day/:title' => 'articles#permalink', + get 'articles/:year/:month/:day/:title' => 'articles#permalink', :year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/ end @@ -1143,7 +1143,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_routing_traversal_does_not_load_extra_classes assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded" set.draw do - match '/profile' => 'profile#index' + get '/profile' => 'profile#index' end set.recognize_path("/profile") rescue nil @@ -1177,8 +1177,8 @@ class RouteSetTest < ActiveSupport::TestCase def test_generate_with_default_action set.draw do - match "/people", :controller => "people", :action => "index" - match "/people/list", :controller => "people", :action => "list" + get "/people", :controller => "people", :action => "index" + get "/people/list", :controller => "people", :action => "list" end url = url_for(set, { :controller => "people", :action => "list" }) @@ -1197,7 +1197,7 @@ class RouteSetTest < ActiveSupport::TestCase set.draw do namespace 'api' do - match 'inventory' => 'products#inventory' + get 'inventory' => 'products#inventory' end end @@ -1222,7 +1222,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_namespace_with_path_prefix set.draw do scope :module => "api", :path => "prefix" do - match 'inventory' => 'products#inventory' + get 'inventory' => 'products#inventory' end end @@ -1234,7 +1234,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_namespace_with_blank_path_prefix set.draw do scope :module => "api", :path => "" do - match 'inventory' => 'products#inventory' + get 'inventory' => 'products#inventory' end end @@ -1244,7 +1244,7 @@ class RouteSetTest < ActiveSupport::TestCase end def test_generate_changes_controller_module - set.draw { match ':controller/:action/:id' } + set.draw { get ':controller/:action/:id' } current = { :controller => "bling/bloop", :action => "bap", :id => 9 } assert_equal "/foo/bar/baz/7", @@ -1253,7 +1253,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_id_is_sticky_when_it_ought_to_be set.draw do - match ':controller/:id/:action' + get ':controller/:id/:action' end url = url_for(set, { :action => "destroy" }, { :controller => "people", :action => "show", :id => "7" }) @@ -1262,8 +1262,8 @@ class RouteSetTest < ActiveSupport::TestCase def test_use_static_path_when_possible set.draw do - match 'about' => "welcome#about" - match ':controller/:action/:id' + get 'about' => "welcome#about" + get ':controller/:action/:id' end url = url_for(set, { :controller => "welcome", :action => "about" }, @@ -1273,7 +1273,7 @@ class RouteSetTest < ActiveSupport::TestCase end def test_generate - set.draw { match ':controller/:action/:id' } + set.draw { get ':controller/:action/:id' } args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" } assert_equal "/foo/bar/7?x=y", url_for(set, args) @@ -1284,7 +1284,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_generate_with_path_prefix set.draw do scope "my" do - match ':controller(/:action(/:id))' + get ':controller(/:action(/:id))' end end @@ -1295,7 +1295,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_generate_with_blank_path_prefix set.draw do scope "" do - match ':controller(/:action(/:id))' + get ':controller(/:action(/:id))' end end @@ -1305,9 +1305,9 @@ class RouteSetTest < ActiveSupport::TestCase def test_named_routes_are_never_relative_to_modules set.draw do - match "/connection/manage(/:action)" => 'connection/manage#index' - match "/connection/connection" => "connection/connection#index" - match '/connection' => 'connection#index', :as => 'family_connection' + get "/connection/manage(/:action)" => 'connection/manage#index' + get "/connection/connection" => "connection/connection#index" + get '/connection' => 'connection#index', :as => 'family_connection' end url = url_for(set, { :controller => "connection" }, { :controller => 'connection/manage' }) @@ -1319,7 +1319,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_action_left_off_when_id_is_recalled set.draw do - match ':controller(/:action(/:id))' + get ':controller(/:action(/:id))' end assert_equal '/books', url_for(set, {:controller => 'books', :action => 'index'}, @@ -1329,8 +1329,8 @@ class RouteSetTest < ActiveSupport::TestCase def test_query_params_will_be_shown_when_recalled set.draw do - match 'show_weblog/:parameter' => 'weblog#show' - match ':controller(/:action(/:id))' + get 'show_weblog/:parameter' => 'weblog#show' + get ':controller(/:action(/:id))' end assert_equal '/weblog/edit?parameter=1', url_for(set, {:action => 'edit', :parameter => 1}, @@ -1340,7 +1340,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_format_is_not_inherit set.draw do - match '/posts(.:format)' => 'posts#index' + get '/posts(.:format)' => 'posts#index' end assert_equal '/posts', url_for(set, @@ -1355,7 +1355,7 @@ class RouteSetTest < ActiveSupport::TestCase end def test_expiry_determination_should_consider_values_with_to_param - set.draw { match 'projects/:project_id/:controller/:action' } + set.draw { get 'projects/:project_id/:controller/:action' } assert_equal '/projects/1/weblog/show', url_for(set, { :action => 'show', :project_id => 1 }, { :controller => 'weblog', :action => 'show', :project_id => '1' }) @@ -1365,7 +1365,7 @@ class RouteSetTest < ActiveSupport::TestCase set.draw do resources :projects do member do - match 'milestones' => 'milestones#index', :as => 'milestones' + get 'milestones' => 'milestones#index', :as => 'milestones' end end end @@ -1398,7 +1398,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_constraints_with_unsupported_regexp_options_must_error assert_raise ArgumentError do set.draw do - match 'page/:name' => 'pages#show', + get 'page/:name' => 'pages#show', :constraints => { :name => /(david|jamis)/m } end end @@ -1407,13 +1407,13 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_constraints_with_supported_options_must_not_error assert_nothing_raised do set.draw do - match 'page/:name' => 'pages#show', + get 'page/:name' => 'pages#show', :constraints => { :name => /(david|jamis)/i } end end assert_nothing_raised do set.draw do - match 'page/:name' => 'pages#show', + get 'page/:name' => 'pages#show', :constraints => { :name => / # Desperately overcommented regexp ( #Either david #The Creator @@ -1427,7 +1427,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_with_subdomain_and_constraints_must_receive_params name_param = nil set.draw do - match 'page/:name' => 'pages#show', :constraints => lambda {|request| + get 'page/:name' => 'pages#show', :constraints => lambda {|request| name_param = request.params[:name] return true } @@ -1439,7 +1439,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_requirement_recognize_with_ignore_case set.draw do - match 'page/:name' => 'pages#show', + get 'page/:name' => 'pages#show', :constraints => {:name => /(david|jamis)/i} end assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis')) @@ -1451,7 +1451,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_requirement_generate_with_ignore_case set.draw do - match 'page/:name' => 'pages#show', + get 'page/:name' => 'pages#show', :constraints => {:name => /(david|jamis)/i} end @@ -1466,7 +1466,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_requirement_recognize_with_extended_syntax set.draw do - match 'page/:name' => 'pages#show', + get 'page/:name' => 'pages#show', :constraints => {:name => / # Desperately overcommented regexp ( #Either david #The Creator @@ -1486,7 +1486,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_route_requirement_with_xi_modifiers set.draw do - match 'page/:name' => 'pages#show', + get 'page/:name' => 'pages#show', :constraints => {:name => / # Desperately overcommented regexp ( #Either david #The Creator @@ -1504,8 +1504,8 @@ class RouteSetTest < ActiveSupport::TestCase def test_routes_with_symbols set.draw do - match 'unnamed', :controller => :pages, :action => :show, :name => :as_symbol - match 'named' , :controller => :pages, :action => :show, :name => :as_symbol, :as => :named + get 'unnamed', :controller => :pages, :action => :show, :name => :as_symbol + get 'named' , :controller => :pages, :action => :show, :name => :as_symbol, :as => :named end assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/unnamed')) assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/named')) @@ -1513,8 +1513,8 @@ class RouteSetTest < ActiveSupport::TestCase def test_regexp_chunk_should_add_question_mark_for_optionals set.draw do - match '/' => 'foo#index' - match '/hello' => 'bar#index' + get '/' => 'foo#index' + get '/hello' => 'bar#index' end assert_equal '/', url_for(set, { :controller => 'foo' }) @@ -1526,7 +1526,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_assign_route_options_with_anchor_chars set.draw do - match '/cars/:action/:person/:car/', :controller => 'cars' + get '/cars/:action/:person/:car/', :controller => 'cars' end assert_equal '/cars/buy/1/2', url_for(set, { :controller => 'cars', :action => 'buy', :person => '1', :car => '2' }) @@ -1536,7 +1536,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_segmentation_of_dot_path set.draw do - match '/books/:action.rss', :controller => 'books' + get '/books/:action.rss', :controller => 'books' end assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list' }) @@ -1546,7 +1546,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_segmentation_of_dynamic_dot_path set.draw do - match '/books(/:action(.:format))', :controller => 'books' + get '/books(/:action(.:format))', :controller => 'books' end assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list', :format => 'rss' }) @@ -1562,7 +1562,7 @@ class RouteSetTest < ActiveSupport::TestCase def test_slashes_are_implied @set = nil - set.draw { match("/:controller(/:action(/:id))") } + set.draw { get("/:controller(/:action(/:id))") } assert_equal '/content', url_for(set, { :controller => 'content', :action => 'index' }) assert_equal '/content/list', url_for(set, { :controller => 'content', :action => 'list' }) @@ -1647,13 +1647,13 @@ class RouteSetTest < ActiveSupport::TestCase def test_generate_with_default_params set.draw do - match 'dummy/page/:page' => 'dummy#show' - match 'dummy/dots/page.:page' => 'dummy#dots' - match 'ibocorp(/:page)' => 'ibocorp#show', + get 'dummy/page/:page' => 'dummy#show' + get 'dummy/dots/page.:page' => 'dummy#dots' + get 'ibocorp(/:page)' => 'ibocorp#show', :constraints => { :page => /\d+/ }, :defaults => { :page => 1 } - match ':controller/:action/:id' + get ':controller/:action/:id' end assert_equal '/ibocorp', url_for(set, { :controller => 'ibocorp', :action => "show", :page => 1 }) @@ -1661,17 +1661,17 @@ class RouteSetTest < ActiveSupport::TestCase def test_generate_with_optional_params_recalls_last_request set.draw do - match "blog/", :controller => "blog", :action => "index" + get "blog/", :controller => "blog", :action => "index" - match "blog(/:year(/:month(/:day)))", + get "blog(/:year(/:month(/:day)))", :controller => "blog", :action => "show_date", :constraints => { :year => /(19|20)\d\d/, :month => /[01]?\d/, :day => /[0-3]?\d/ }, :day => nil, :month => nil - match "blog/show/:id", :controller => "blog", :action => "show", :id => /\d+/ - match "blog/:controller/:action(/:id)" - match "*anything", :controller => "blog", :action => "unknown_request" + get "blog/show/:id", :controller => "blog", :action => "show", :id => /\d+/ + get "blog/:controller/:action(/:id)" + get "*anything", :controller => "blog", :action => "unknown_request" end assert_equal({:controller => "blog", :action => "index"}, set.recognize_path("/blog")) @@ -1719,7 +1719,7 @@ class RackMountIntegrationTests < ActiveSupport::TestCase root :to => 'users#index' end - match '/blog(/:year(/:month(/:day)))' => 'posts#show_date', + get '/blog(/:year(/:month(/:day)))' => 'posts#show_date', :constraints => { :year => /(19|20)\d\d/, :month => /[01]?\d/, @@ -1728,37 +1728,37 @@ class RackMountIntegrationTests < ActiveSupport::TestCase :day => nil, :month => nil - match 'archive/:year', :controller => 'archive', :action => 'index', + get 'archive/:year', :controller => 'archive', :action => 'index', :defaults => { :year => nil }, :constraints => { :year => /\d{4}/ }, :as => "blog" resources :people - match 'legacy/people' => "people#index", :legacy => "true" + get 'legacy/people' => "people#index", :legacy => "true" - match 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol - match 'id_default(/:id)' => "foo#id_default", :id => 1 + get 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol + get 'id_default(/:id)' => "foo#id_default", :id => 1 match 'get_or_post' => "foo#get_or_post", :via => [:get, :post] - match 'optional/:optional' => "posts#index" - match 'projects/:project_id' => "project#index", :as => "project" - match 'clients' => "projects#index" + get 'optional/:optional' => "posts#index" + get 'projects/:project_id' => "project#index", :as => "project" + get 'clients' => "projects#index" - match 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i - match 'extended/geocode/:postalcode' => 'geocode#show',:constraints => { + get 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i + get 'extended/geocode/:postalcode' => 'geocode#show',:constraints => { :postalcode => /# Postcode format \d{5} #Prefix (-\d{4})? #Suffix /x }, :as => "geocode" - match 'news(.:format)' => "news#index" + get 'news(.:format)' => "news#index" - match 'comment/:id(/:action)' => "comments#show" - match 'ws/:controller(/:action(/:id))', :ws => true - match 'account(/:action)' => "account#subscription" - match 'pages/:page_id/:controller(/:action(/:id))' - match ':controller/ping', :action => 'ping' - match ':controller(/:action(/:id))(.:format)' + get 'comment/:id(/:action)' => "comments#show" + get 'ws/:controller(/:action(/:id))', :ws => true + get 'account(/:action)' => "account#subscription" + get 'pages/:page_id/:controller(/:action(/:id))' + get ':controller/ping', :action => 'ping' + match ':controller(/:action(/:id))(.:format)', :via => :all root :to => "news#index" } diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index 3af17f495c..6fc3556e31 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -154,6 +154,17 @@ class SendFileTest < ActionController::TestCase end end + def test_send_file_with_default_content_disposition_header + process('data') + assert_equal 'attachment', @controller.headers['Content-Disposition'] + end + + def test_send_file_without_content_disposition_header + @controller.options = {:disposition => nil} + process('data') + assert_nil @controller.headers['Content-Disposition'] + end + %w(file data).each do |method| define_method "test_send_#{method}_status" do @controller.options = { :stream => false, :status => 500 } diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index ecba9fed22..0d6d303b51 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -45,6 +45,10 @@ class TestCaseTest < ActionController::TestCase render :text => request.fullpath end + def test_format + render :text => request.format + end + def test_query_string render :text => request.query_string end @@ -138,7 +142,7 @@ XML @request.env['PATH_INFO'] = nil @routes = ActionDispatch::Routing::RouteSet.new.tap do |r| r.draw do - match ':controller(/:action(/:id))' + get ':controller(/:action(/:id))' end end end @@ -224,6 +228,26 @@ XML assert_equal 'value2', session[:symbol] end + def test_process_merges_session_arg + session[:foo] = 'bar' + get :no_op, nil, { :bar => 'baz' } + assert_equal 'bar', session[:foo] + assert_equal 'baz', session[:bar] + end + + def test_merged_session_arg_is_retained_across_requests + get :no_op, nil, { :foo => 'bar' } + assert_equal 'bar', session[:foo] + get :no_op + assert_equal 'bar', session[:foo] + end + + def test_process_overwrites_existing_session_arg + session[:foo] = 'bar' + get :no_op, nil, { :foo => 'baz' } + assert_equal 'baz', session[:foo] + end + def test_session_is_cleared_from_controller_after_reset_session process :set_session process :reset_the_session @@ -524,7 +548,7 @@ XML with_routing do |set| set.draw do namespace :admin do - match 'user' => 'user#index' + get 'user' => 'user#index' end end @@ -534,7 +558,7 @@ XML def test_assert_routing_with_glob with_routing do |set| - set.draw { match('*path' => "pages#show") } + set.draw { get('*path' => "pages#show") } assert_routing('/company/about', { :controller => 'pages', :action => 'show', :path => 'company/about' }) end end @@ -559,14 +583,34 @@ XML ) end + def test_params_passing_with_fixnums_when_not_html_request + get :test_params, :format => 'json', :count => 999 + parsed_params = eval(@response.body) + assert_equal( + {'controller' => 'test_case_test/test', 'action' => 'test_params', + 'format' => 'json', 'count' => 999 }, + parsed_params + ) + end + + def test_params_passing_path_parameter_is_string_when_not_html_request + get :test_params, :format => 'json', :id => 1 + parsed_params = eval(@response.body) + assert_equal( + {'controller' => 'test_case_test/test', 'action' => 'test_params', + 'format' => 'json', 'id' => '1' }, + parsed_params + ) + end + def test_params_passing_with_frozen_values assert_nothing_raised do - get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze + get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze, :deepfreeze => { :frozen => 'icy'.freeze }.freeze end parsed_params = eval(@response.body) assert_equal( {'controller' => 'test_case_test/test', 'action' => 'test_params', - 'frozen' => 'icy', 'frozens' => ['icy']}, + 'frozen' => 'icy', 'frozens' => ['icy'], 'deepfreeze' => { 'frozen' => 'icy' }}, parsed_params ) end @@ -585,8 +629,8 @@ XML def test_array_path_parameter_handled_properly with_routing do |set| set.draw do - match 'file/*path', :to => 'test_case_test/test#test_params' - match ':controller/:action' + get 'file/*path', :to => 'test_case_test/test#test_params' + get ':controller/:action' end get :test_params, :path => ['hello', 'world'] @@ -667,6 +711,20 @@ XML assert_equal "http://", @response.body end + def test_request_format + get :test_format, :format => 'html' + assert_equal 'text/html', @response.body + + get :test_format, :format => 'json' + assert_equal 'application/json', @response.body + + get :test_format, :format => 'xml' + assert_equal 'application/xml', @response.body + + get :test_format + assert_equal 'text/html', @response.body + end + def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set cookies['foo'] = 'bar' get :no_op @@ -828,3 +886,24 @@ class NamedRoutesControllerTest < ActionController::TestCase end end end + +class AnonymousControllerTest < ActionController::TestCase + def setup + @controller = Class.new(ActionController::Base) do + def index + render :text => params[:controller] + end + end.new + + @routes = ActionDispatch::Routing::RouteSet.new.tap do |r| + r.draw do + get ':controller(/:action(/:id))' + end + end + end + + def test_controller_name + get :index + assert_equal 'anonymous', @response.body + end +end
\ No newline at end of file diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb index 451ea6027d..6c2311e7a5 100644 --- a/actionpack/test/controller/url_for_integration_test.rb +++ b/actionpack/test/controller/url_for_integration_test.rb @@ -18,7 +18,7 @@ module ActionPack root :to => 'users#index' end - match '/blog(/:year(/:month(/:day)))' => 'posts#show_date', + get '/blog(/:year(/:month(/:day)))' => 'posts#show_date', :constraints => { :year => /(19|20)\d\d/, :month => /[01]?\d/, @@ -27,7 +27,7 @@ module ActionPack :day => nil, :month => nil - match 'archive/:year', :controller => 'archive', :action => 'index', + get 'archive/:year', :controller => 'archive', :action => 'index', :defaults => { :year => nil }, :constraints => { :year => /\d{4}/ }, :as => "blog" @@ -35,29 +35,29 @@ module ActionPack resources :people #match 'legacy/people' => "people#index", :legacy => "true" - match 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol - match 'id_default(/:id)' => "foo#id_default", :id => 1 + get 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol + get 'id_default(/:id)' => "foo#id_default", :id => 1 match 'get_or_post' => "foo#get_or_post", :via => [:get, :post] - match 'optional/:optional' => "posts#index" - match 'projects/:project_id' => "project#index", :as => "project" - match 'clients' => "projects#index" + get 'optional/:optional' => "posts#index" + get 'projects/:project_id' => "project#index", :as => "project" + get 'clients' => "projects#index" - match 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i - match 'extended/geocode/:postalcode' => 'geocode#show',:constraints => { + get 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i + get 'extended/geocode/:postalcode' => 'geocode#show',:constraints => { :postalcode => /# Postcode format \d{5} #Prefix (-\d{4})? #Suffix /x }, :as => "geocode" - match 'news(.:format)' => "news#index" + get 'news(.:format)' => "news#index" - match 'comment/:id(/:action)' => "comments#show" - match 'ws/:controller(/:action(/:id))', :ws => true - match 'account(/:action)' => "account#subscription" - match 'pages/:page_id/:controller(/:action(/:id))' - match ':controller/ping', :action => 'ping' - match ':controller(/:action(/:id))(.:format)' + get 'comment/:id(/:action)' => "comments#show" + get 'ws/:controller(/:action(/:id))', :ws => true + get 'account(/:action)' => "account#subscription" + get 'pages/:page_id/:controller(/:action(/:id))' + get ':controller/ping', :action => 'ping' + get ':controller(/:action(/:id))(.:format)' root :to => "news#index" } diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index aa233d6135..b2cb5f80d5 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -5,7 +5,7 @@ module AbstractController class UrlForTests < ActionController::TestCase class W - include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { match ':controller(/:action(/:id(.:format)))' } }.url_helpers + include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { get ':controller(/:action(/:id(.:format)))' } }.url_helpers end def teardown @@ -210,8 +210,8 @@ module AbstractController def test_named_routes with_routing do |set| set.draw do - match 'this/is/verbose', :to => 'home#index', :as => :no_args - match 'home/sweet/home/:user', :to => 'home#index', :as => :home + get 'this/is/verbose', :to => 'home#index', :as => :no_args + get 'home/sweet/home/:user', :to => 'home#index', :as => :home end # We need to create a new class in order to install the new named route. @@ -231,7 +231,7 @@ module AbstractController def test_relative_url_root_is_respected_for_named_routes with_routing do |set| set.draw do - match '/home/sweet/home/:user', :to => 'home#index', :as => :home + get '/home/sweet/home/:user', :to => 'home#index', :as => :home end kls = Class.new { include set.url_helpers } @@ -245,8 +245,8 @@ module AbstractController def test_only_path with_routing do |set| set.draw do - match 'home/sweet/home/:user', :to => 'home#index', :as => :home - match ':controller/:action/:id' + get 'home/sweet/home/:user', :to => 'home#index', :as => :home + get ':controller/:action/:id' end # We need to create a new class in order to install the new named route. @@ -313,8 +313,8 @@ module AbstractController def test_named_routes_with_nil_keys with_routing do |set| set.draw do - match 'posts.:format', :to => 'posts#index', :as => :posts - match '/', :to => 'posts#index', :as => :main + get 'posts.:format', :to => 'posts#index', :as => :posts + get '/', :to => 'posts#index', :as => :main end # We need to create a new class in order to install the new named route. diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index f88903b10e..cc3706aeee 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -21,7 +21,7 @@ class UrlRewriterTests < ActionController::TestCase @rewriter = Rewriter.new(@request) #.new(@request, @params) @routes = ActionDispatch::Routing::RouteSet.new.tap do |r| r.draw do - match ':controller(/:action(/:id))' + get ':controller(/:action(/:id))' end end end diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 351e61eeae..c0b9833603 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -254,7 +254,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest def with_test_route_set with_routing do |set| set.draw do - match '/', :to => 'web_service_test/test#assign_parameters' + match '/', :to => 'web_service_test/test#assign_parameters', :via => :all end yield end diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb index d3465589c1..bd078d2b21 100644 --- a/actionpack/test/dispatch/mapper_test.rb +++ b/actionpack/test/dispatch/mapper_test.rb @@ -4,11 +4,12 @@ module ActionDispatch module Routing class MapperTest < ActiveSupport::TestCase class FakeSet - attr_reader :routes + attr_reader :routes, :draw_paths alias :set :routes def initialize @routes = [] + @draw_paths = [] end def resources_path_names @@ -37,7 +38,7 @@ module ActionDispatch end def test_mapping_requirements - options = { :controller => 'foo', :action => 'bar' } + options = { :controller => 'foo', :action => 'bar', :via => :get } m = Mapper::Mapping.new FakeSet.new, {}, '/store/:name(*rest)', options _, _, requirements, _ = m.to_route assert_equal(/.+?/, requirements[:rest]) @@ -46,7 +47,7 @@ module ActionDispatch def test_map_slash fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.match '/', :to => 'posts#index', :as => :main + mapper.get '/', :to => 'posts#index', :as => :main assert_equal '/', fakeset.conditions.first[:path_info] end @@ -55,14 +56,14 @@ module ActionDispatch mapper = Mapper.new fakeset # FIXME: is this a desired behavior? - mapper.match '/one/two/', :to => 'posts#index', :as => :main + mapper.get '/one/two/', :to => 'posts#index', :as => :main assert_equal '/one/two(.:format)', fakeset.conditions.first[:path_info] end def test_map_wildcard fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.match '/*path', :to => 'pages#show' + mapper.get '/*path', :to => 'pages#show' assert_equal '/*path(.:format)', fakeset.conditions.first[:path_info] assert_equal(/.+?/, fakeset.requirements.first[:path]) end @@ -70,7 +71,7 @@ module ActionDispatch def test_map_wildcard_with_other_element fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.match '/*path/foo/:bar', :to => 'pages#show' + mapper.get '/*path/foo/:bar', :to => 'pages#show' assert_equal '/*path/foo/:bar(.:format)', fakeset.conditions.first[:path_info] assert_nil fakeset.requirements.first[:path] end @@ -78,7 +79,7 @@ module ActionDispatch def test_map_wildcard_with_multiple_wildcard fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.match '/*foo/*bar', :to => 'pages#show' + mapper.get '/*foo/*bar', :to => 'pages#show' assert_equal '/*foo/*bar(.:format)', fakeset.conditions.first[:path_info] assert_nil fakeset.requirements.first[:foo] assert_equal(/.+?/, fakeset.requirements.first[:bar]) @@ -87,7 +88,7 @@ module ActionDispatch def test_map_wildcard_with_format_false fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.match '/*path', :to => 'pages#show', :format => false + mapper.get '/*path', :to => 'pages#show', :format => false assert_equal '/*path', fakeset.conditions.first[:path_info] assert_nil fakeset.requirements.first[:path] end @@ -95,7 +96,7 @@ module ActionDispatch def test_map_wildcard_with_format_true fakeset = FakeSet.new mapper = Mapper.new fakeset - mapper.match '/*path', :to => 'pages#show', :format => true + mapper.get '/*path', :to => 'pages#show', :format => true assert_equal '/*path.:format', fakeset.conditions.first[:path_info] end end diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index bd5b5edab0..ab2f7612ce 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -25,12 +25,12 @@ module TestGenerationPrefix @routes ||= begin routes = ActionDispatch::Routing::RouteSet.new routes.draw do - match "/posts/:id", :to => "inside_engine_generating#show", :as => :post - match "/posts", :to => "inside_engine_generating#index", :as => :posts - match "/url_to_application", :to => "inside_engine_generating#url_to_application" - match "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine" - match "/conflicting_url", :to => "inside_engine_generating#conflicting" - match "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test + get "/posts/:id", :to => "inside_engine_generating#show", :as => :post + get "/posts", :to => "inside_engine_generating#index", :as => :posts + get "/url_to_application", :to => "inside_engine_generating#url_to_application" + get "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine" + get "/conflicting_url", :to => "inside_engine_generating#conflicting" + get "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test end routes @@ -51,12 +51,12 @@ module TestGenerationPrefix scope "/:omg", :omg => "awesome" do mount BlogEngine => "/blog", :as => "blog_engine" end - match "/posts/:id", :to => "outside_engine_generating#post", :as => :post - match "/generate", :to => "outside_engine_generating#index" - match "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app" - match "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine" - match "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for" - match "/conflicting_url", :to => "outside_engine_generating#conflicting" + get "/posts/:id", :to => "outside_engine_generating#post", :as => :post + get "/generate", :to => "outside_engine_generating#index" + get "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app" + get "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine" + get "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for" + get "/conflicting_url", :to => "outside_engine_generating#conflicting" root :to => "outside_engine_generating#index" end @@ -282,7 +282,7 @@ module TestGenerationPrefix @routes ||= begin routes = ActionDispatch::Routing::RouteSet.new routes.draw do - match "/posts/:id", :to => "posts#show", :as => :post + get "/posts/:id", :to => "posts#show", :as => :post end routes diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index ae425dd406..302bff0696 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -65,7 +65,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest def with_test_routing with_routing do |set| set.draw do - match ':action', :to => ::JsonParamsParsingTest::TestController + post ':action', :to => ::JsonParamsParsingTest::TestController end yield end @@ -118,7 +118,7 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest def with_test_routing(controller) with_routing do |set| set.draw do - match ':action', :to => controller + post ':action', :to => controller end yield end diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index d144f013f5..63c5ea26a6 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -144,7 +144,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest def with_test_routing with_routing do |set| set.draw do - match ':action', :to => 'multipart_params_parsing_test/test' + post ':action', :to => 'multipart_params_parsing_test/test' end yield end diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb index f6a1475d04..d14f188e30 100644 --- a/actionpack/test/dispatch/request/query_string_parsing_test.rb +++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb @@ -109,7 +109,7 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest def assert_parses(expected, actual) with_routing do |set| set.draw do - match ':action', :to => ::QueryStringParsingTest::TestController + get ':action', :to => ::QueryStringParsingTest::TestController end get "/parse", actual diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb index 05569561d2..568e220b15 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -130,7 +130,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest def with_test_routing with_routing do |set| set.draw do - match ':action', :to => ::UrlEncodedParamsParsingTest::TestController + post ':action', :to => ::UrlEncodedParamsParsingTest::TestController end yield end diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb index afd400c2a9..84823e2896 100644 --- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb @@ -106,7 +106,7 @@ class XmlParamsParsingTest < ActionDispatch::IntegrationTest def with_test_routing with_routing do |set| set.draw do - match ':action', :to => ::XmlParamsParsingTest::TestController + post ':action', :to => ::XmlParamsParsingTest::TestController end yield end @@ -155,7 +155,7 @@ class RootLessXmlParamsParsingTest < ActionDispatch::IntegrationTest def with_test_routing with_routing do |set| set.draw do - match ':action', :to => ::RootLessXmlParamsParsingTest::TestController + post ':action', :to => ::RootLessXmlParamsParsingTest::TestController end yield end diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb index 4b98cd32f2..a8050b4fab 100644 --- a/actionpack/test/dispatch/request_id_test.rb +++ b/actionpack/test/dispatch/request_id_test.rb @@ -52,7 +52,7 @@ class RequestIdResponseTest < ActionDispatch::IntegrationTest def with_test_route_set with_routing do |set| set.draw do - match '/', :to => ::RequestIdResponseTest::TestController.action(:index) + get '/', :to => ::RequestIdResponseTest::TestController.action(:index) end @app = self.class.build_app(set) do |middleware| @@ -62,4 +62,4 @@ class RequestIdResponseTest < ActionDispatch::IntegrationTest yield end end -end
\ No newline at end of file +end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 6c8b22c47f..94d0e09842 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -35,37 +35,40 @@ class RequestTest < ActiveSupport::TestCase assert_equal '1.2.3.4', request.remote_ip request = stub_request 'REMOTE_ADDR' => '1.2.3.4', - 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' assert_equal '3.4.5.6', request.remote_ip request = stub_request 'REMOTE_ADDR' => '127.0.0.1', - 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' assert_equal '3.4.5.6', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,unknown' assert_equal '3.4.5.6', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.16.0.1,3.4.5.6' - assert_equal '3.4.5.6', request.remote_ip + assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6' - assert_equal '3.4.5.6', request.remote_ip + assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6' - assert_equal '3.4.5.6', request.remote_ip + assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6' - assert_equal '3.4.5.6', request.remote_ip + assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '127.0.0.1,3.4.5.6' - assert_equal '3.4.5.6', request.remote_ip + assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1' - assert_equal 'unknown', request.remote_ip + assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 172.31.4.4' assert_equal '3.4.5.6', request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address' + assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', 'HTTP_CLIENT_IP' => '2.2.2.2' e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { @@ -89,6 +92,68 @@ class RequestTest < ActiveSupport::TestCase assert_equal '9.9.9.9', request.remote_ip end + test "remote ip v6" do + request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334' + assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + + request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + + request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + + request = stub_request 'REMOTE_ADDR' => '::1', + 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal nil, request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal nil, request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal nil, request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal nil, request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1, ::1, fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal nil, request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,::1' + assert_equal nil, request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::' + assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address' + assert_equal nil, request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', + 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334' + e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { + request.remote_ip + } + assert_match(/IP spoofing attack/, e.message) + assert_match(/HTTP_X_FORWARDED_FOR="fe80:0000:0000:0000:0202:b3ff:fe1e:8329"/, e.message) + assert_match(/HTTP_CLIENT_IP="2001:0db8:85a3:0000:0000:8a2e:0370:7334"/, e.message) + + # Turn IP Spoofing detection off. + # This is useful for sites that are aimed at non-IP clients. The typical + # example is WAP. Since the cellular network is not IP based, it's a + # leap of faith to assume that their proxies are ever going to set the + # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly. + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', + 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + :ip_spoofing_check => false + assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + end + test "remote ip when the remote ip middleware returns nil" do request = stub_request 'REMOTE_ADDR' => '127.0.0.1' assert_equal '127.0.0.1', request.remote_ip @@ -97,29 +162,47 @@ class RequestTest < ActiveSupport::TestCase test "remote ip with user specified trusted proxies String" do @trusted_proxies = "67.205.106.73" - request = stub_request 'REMOTE_ADDR' => '67.205.106.73', - 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + request = stub_request 'REMOTE_ADDR' => '3.4.5.6', + 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' assert_equal '3.4.5.6', request.remote_ip request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73', - 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' - assert_equal '3.4.5.6', request.remote_ip + 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' + assert_equal '172.16.0.1', request.remote_ip - request = stub_request 'REMOTE_ADDR' => '67.205.106.73,172.16.0.1', - 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' - assert_equal '3.4.5.6', request.remote_ip - - request = stub_request 'REMOTE_ADDR' => '67.205.106.74,172.16.0.1', - 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + request = stub_request 'REMOTE_ADDR' => '67.205.106.73,3.4.5.6', + 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' assert_equal '3.4.5.6', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73' - assert_equal 'unknown', request.remote_ip + assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 67.205.106.73' assert_equal '3.4.5.6', request.remote_ip end + test "remote ip v6 with user specified trusted proxies String" do + @trusted_proxies = 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + + request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + + request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + + request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1', + 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal nil, request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334' + assert_equal nil, request.remote_ip + end + test "remote ip with user specified trusted proxies Regexp" do @trusted_proxies = /^67\.205\.106\.73$/i @@ -128,7 +211,18 @@ class RequestTest < ActiveSupport::TestCase assert_equal '3.4.5.6', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73, 10.0.0.1, 9.9.9.9, 3.4.5.6' - assert_equal '10.0.0.1', request.remote_ip + assert_equal nil, request.remote_ip + end + + test "remote ip v6 with user specified trusted proxies Regexp" do + @trusted_proxies = /^fe80:0000:0000:0000:0202:b3ff:fe1e:8329$/i + + request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334' + assert_equal nil, request.remote_ip end test "domains" do diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index cc4279d9dd..4b8d308043 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -58,41 +58,46 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get "remove", :action => :destroy, :as => :remove end - match 'account/logout' => redirect("/logout"), :as => :logout_redirect - match 'account/login', :to => redirect("/login") - match 'secure', :to => redirect("/secure/login") + get 'account/logout' => redirect("/logout"), :as => :logout_redirect + get 'account/login', :to => redirect("/login") + get 'secure', :to => redirect("/secure/login") - match 'mobile', :to => redirect(:subdomain => 'mobile') - match 'super_new_documentation', :to => redirect(:host => 'super-docs.com') + get 'mobile', :to => redirect(:subdomain => 'mobile') + get 'documentation', :to => redirect(:domain => 'example-documentation.com', :path => '') + get 'new_documentation', :to => redirect(:path => '/documentation/new') + get 'super_new_documentation', :to => redirect(:host => 'super-docs.com') - match 'youtube_favorites/:youtube_id/:name', :to => redirect(YoutubeFavoritesRedirector) + get 'stores/:name', :to => redirect(:subdomain => 'stores', :path => '/%{name}') + get 'stores/:name(*rest)', :to => redirect(:subdomain => 'stores', :path => '/%{name}%{rest}') + + get 'youtube_favorites/:youtube_id/:name', :to => redirect(YoutubeFavoritesRedirector) constraints(lambda { |req| true }) do - match 'account/overview' + get 'account/overview' end - match '/account/nested/overview' - match 'sign_in' => "sessions#new" + get '/account/nested/overview' + get 'sign_in' => "sessions#new" - match 'account/modulo/:name', :to => redirect("/%{name}s") - match 'account/proc/:name', :to => redirect {|params, req| "/#{params[:name].pluralize}" } - match 'account/proc_req' => redirect {|params, req| "/#{req.method}" } + get 'account/modulo/:name', :to => redirect("/%{name}s") + get 'account/proc/:name', :to => redirect {|params, req| "/#{params[:name].pluralize}" } + get 'account/proc_req' => redirect {|params, req| "/#{req.method}" } - match 'account/google' => redirect('http://www.google.com/', :status => 302) + get 'account/google' => redirect('http://www.google.com/', :status => 302) match 'openid/login', :via => [:get, :post], :to => "openid#login" controller(:global) do get 'global/hide_notice' - match 'global/export', :to => :export, :as => :export_request - match '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ } - match 'global/:action' + get 'global/export', :to => :export, :as => :export_request + get '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ } + get 'global/:action' end - match "/local/:action", :controller => "local" + get "/local/:action", :controller => "local" - match "/projects/status(.:format)" - match "/404", :to => lambda { |env| [404, {"Content-Type" => "text/plain"}, ["NOT FOUND"]] } + get "/projects/status(.:format)" + get "/404", :to => lambda { |env| [404, {"Content-Type" => "text/plain"}, ["NOT FOUND"]] } constraints(:ip => /192\.168\.1\.\d\d\d/) do get 'admin' => "queenbee#index" @@ -277,25 +282,25 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp + get 'sprockets.js' => ::TestRoutingMapper::SprocketsApp - match 'people/:id/update', :to => 'people#update', :as => :update_person - match '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person + get 'people/:id/update', :to => 'people#update', :as => :update_person + get '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person # misc - match 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article + get 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article # default params - match 'inline_pages/(:id)', :to => 'pages#show', :id => 'home' - match 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' } + get 'inline_pages/(:id)', :to => 'pages#show', :id => 'home' + get 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' } defaults :id => 'home' do - match 'scoped_pages/(:id)', :to => 'pages#show' + get 'scoped_pages/(:id)', :to => 'pages#show' end namespace :account do - match 'shorthand' - match 'description', :to => :description, :as => "description" - match ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback + get 'shorthand' + get 'description', :to => :description, :as => "description" + get ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback resource :subscription, :credit, :credit_card root :to => "account#index" @@ -318,7 +323,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest controller :articles do scope '/articles', :as => 'article' do scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do - match '/:id', :to => :with_id, :as => "" + get '/:id', :to => :with_id, :as => "" end end end @@ -327,7 +332,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resources :rooms end - match '/info' => 'projects#info', :as => 'info' + get '/info' => 'projects#info', :as => 'info' namespace :admin do scope '(:locale)', :locale => /en|pl/ do @@ -361,7 +366,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest scope :path => 'api' do resource :me - match '/' => 'mes#index' + get '/' => 'mes#index' end get "(/:username)/followers" => "followers#index" @@ -374,7 +379,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - match "whatever/:controller(/:action(/:id))", :id => /\d+/ + get "whatever/:controller(/:action(/:id))", :id => /\d+/ resource :profile do get :settings @@ -407,7 +412,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest namespace :private do root :to => redirect('/private/index') - match "index", :to => 'private#index' + get "index", :to => 'private#index' end scope :only => [:index, :show] do @@ -489,7 +494,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show" end - match '/purchases/:token/:filename', + get '/purchases/:token/:filename', :to => 'purchases#fetch', :token => /[[:alnum:]]{10}/, :filename => /(.+)/, @@ -500,18 +505,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end scope '/countries/:country', :constraints => lambda { |params, req| params[:country].in?(["all", "France"]) } do - match '/', :to => 'countries#index' - match '/cities', :to => 'countries#cities' + get '/', :to => 'countries#index' + get '/cities', :to => 'countries#cities' end - match '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' } + get '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' } - match '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/ + get '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/ scope '/italians' do - match '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor - match '/sculptors', :to => 'italians#sculptors' - match '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/} + get '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor + get '/sculptors', :to => 'italians#sculptors' + get '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/} end end end @@ -627,7 +632,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest self.class.stub_controllers do |routes| routes.draw do namespace :admin do - match '/:controller(/:action(/:id(.:format)))' + get '/:controller(/:action(/:id(.:format)))' end end end @@ -693,11 +698,31 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest verify_redirect 'http://mobile.example.com/mobile' end + def test_redirect_hash_with_domain_and_path + get '/documentation' + verify_redirect 'http://www.example-documentation.com' + end + + def test_redirect_hash_with_path + get '/new_documentation' + verify_redirect 'http://www.example.com/documentation/new' + end + def test_redirect_hash_with_host get '/super_new_documentation?section=top' verify_redirect 'http://super-docs.com/super_new_documentation?section=top' end + def test_redirect_hash_path_substitution + get '/stores/iernest' + verify_redirect 'http://stores.example.com/iernest' + end + + def test_redirect_hash_path_substitution_with_catch_all + get '/stores/iernest/products' + verify_redirect 'http://stores.example.com/iernest/products' + end + def test_redirect_class get '/youtube_favorites/oHg5SJYRHA0/rick-rolld' verify_redirect 'http://www.youtube.com/watch?v=oHg5SJYRHA0' @@ -2231,12 +2256,12 @@ class TestAppendingRoutes < ActionDispatch::IntegrationTest s = self @app = ActionDispatch::Routing::RouteSet.new @app.append do - match '/hello' => s.simple_app('fail') - match '/goodbye' => s.simple_app('goodbye') + get '/hello' => s.simple_app('fail') + get '/goodbye' => s.simple_app('goodbye') end @app.draw do - match '/hello' => s.simple_app('hello') + get '/hello' => s.simple_app('hello') end end @@ -2344,12 +2369,12 @@ end class TestUriPathEscaping < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - match '/:segment' => lambda { |env| + get '/:segment' => lambda { |env| path_params = env['action_dispatch.request.path_parameters'] [200, { 'Content-Type' => 'text/plain' }, [path_params[:segment]]] }, :as => :segment - match '/*splat' => lambda { |env| + get '/*splat' => lambda { |env| path_params = env['action_dispatch.request.path_parameters'] [200, { 'Content-Type' => 'text/plain' }, [path_params[:splat]]] }, :as => :splat @@ -2381,7 +2406,7 @@ end class TestUnicodePaths < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - match "/#{Rack::Utils.escape("ほげ")}" => lambda { |env| + get "/#{Rack::Utils.escape("ほげ")}" => lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }, :as => :unicode_path end @@ -2411,10 +2436,10 @@ class TestMultipleNestedController < ActionDispatch::IntegrationTest app.draw do namespace :foo do namespace :bar do - match "baz" => "baz#index" + get "baz" => "baz#index" end end - match "pooh" => "pooh#index" + get "pooh" => "pooh#index" end end @@ -2433,8 +2458,8 @@ class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest app.draw do ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } - match "/~user" => ok - match "/young-and-fine" => ok + get "/~user" => ok + get "/young-and-fine" => ok end end @@ -2452,3 +2477,38 @@ class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest end end + +class TestRedirectInterpolation < ActionDispatch::IntegrationTest + Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| + app.draw do + ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + + get "/foo/:id" => redirect("/foo/bar/%{id}") + get "/bar/:id" => redirect(:path => "/foo/bar/%{id}") + get "/foo/bar/:id" => ok + end + end + + def app; Routes end + + test "redirect escapes interpolated parameters with redirect proc" do + get "/foo/1%3E" + verify_redirect "http://www.example.com/foo/bar/1%3E" + end + + test "redirect escapes interpolated parameters with option proc" do + get "/bar/1%3E" + verify_redirect "http://www.example.com/foo/bar/1%3E" + end + +private + def verify_redirect(url, status=301) + assert_equal status, @response.status + assert_equal url, @response.headers['Location'] + assert_equal expected_redirect_body(url), @response.body + end + + def expected_redirect_body(url) + %(<html><body>You are being <a href="#{ERB::Util.h(url)}">redirected</a>.</body></html>) + end +end diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb index 12405bf45d..a74e165826 100644 --- a/actionpack/test/dispatch/session/cache_store_test.rb +++ b/actionpack/test/dispatch/session/cache_store_test.rb @@ -164,7 +164,7 @@ class CacheStoreTest < ActionDispatch::IntegrationTest def with_test_route_set with_routing do |set| set.draw do - match ':action', :to => ::CacheStoreTest::TestController + get ':action', :to => ::CacheStoreTest::TestController end @app = self.class.build_app(set) do |middleware| diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index 19969394cd..631974d6c4 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -317,7 +317,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest def with_test_route_set(options = {}) with_routing do |set| set.draw do - match ':action', :to => ::CookieStoreTest::TestController + get ':action', :to => ::CookieStoreTest::TestController end options = { :key => SessionKey }.merge!(options) diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb index 5277c92b55..03234612ab 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -173,7 +173,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest def with_test_route_set with_routing do |set| set.draw do - match ':action', :to => ::MemCacheStoreTest::TestController + get ':action', :to => ::MemCacheStoreTest::TestController end @app = self.class.build_app(set) do |middleware| diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb index 2b54bc62b0..985ff2e81a 100644 --- a/actionpack/test/dispatch/url_generation_test.rb +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module TestUrlGeneration class WithMountPoint < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new - Routes.draw { match "/foo", :to => "my_route_generating#index", :as => :foo } + Routes.draw { get "/foo", :to => "my_route_generating#index", :as => :foo } class ::MyRouteGeneratingController < ActionController::Base include Routes.url_helpers diff --git a/actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb new file mode 100644 index 0000000000..bdd53014cd --- /dev/null +++ b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb @@ -0,0 +1 @@ +<b class="<%= customer.name.downcase %>"><%= yield %></b>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb new file mode 100644 index 0000000000..44d6121297 --- /dev/null +++ b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb @@ -0,0 +1 @@ +<b data-counter="<%= customer_counter %>"><%= yield %></b>
\ No newline at end of file diff --git a/actionpack/test/fixtures/translations/templates/default.erb b/actionpack/test/fixtures/translations/templates/default.erb new file mode 100644 index 0000000000..8b70031071 --- /dev/null +++ b/actionpack/test/fixtures/translations/templates/default.erb @@ -0,0 +1 @@ +<%= t('.missing', :default => :'.foo') %> diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb index 82b62e64f3..63066d40cd 100644 --- a/actionpack/test/template/date_helper_i18n_test.rb +++ b/actionpack/test/template/date_helper_i18n_test.rb @@ -12,24 +12,24 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase def test_distance_of_time_in_words_calls_i18n { # with include_seconds - [2.seconds, true] => [:'less_than_x_seconds', 5], - [9.seconds, true] => [:'less_than_x_seconds', 10], - [19.seconds, true] => [:'less_than_x_seconds', 20], - [30.seconds, true] => [:'half_a_minute', nil], - [59.seconds, true] => [:'less_than_x_minutes', 1], - [60.seconds, true] => [:'x_minutes', 1], + [2.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 5], + [9.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 10], + [19.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 20], + [30.seconds, { :include_seconds => true }] => [:'half_a_minute', nil], + [59.seconds, { :include_seconds => true }] => [:'less_than_x_minutes', 1], + [60.seconds, { :include_seconds => true }] => [:'x_minutes', 1], # without include_seconds - [29.seconds, false] => [:'less_than_x_minutes', 1], - [60.seconds, false] => [:'x_minutes', 1], - [44.minutes, false] => [:'x_minutes', 44], - [61.minutes, false] => [:'about_x_hours', 1], - [24.hours, false] => [:'x_days', 1], - [30.days, false] => [:'about_x_months', 1], - [60.days, false] => [:'x_months', 2], - [1.year, false] => [:'about_x_years', 1], - [3.years + 6.months, false] => [:'over_x_years', 3], - [3.years + 10.months, false] => [:'almost_x_years', 4] + [29.seconds, { :include_seconds => false }] => [:'less_than_x_minutes', 1], + [60.seconds, { :include_seconds => false }] => [:'x_minutes', 1], + [44.minutes, { :include_seconds => false }] => [:'x_minutes', 44], + [61.minutes, { :include_seconds => false }] => [:'about_x_hours', 1], + [24.hours, { :include_seconds => false }] => [:'x_days', 1], + [30.days, { :include_seconds => false }] => [:'about_x_months', 1], + [60.days, { :include_seconds => false }] => [:'x_months', 2], + [1.year, { :include_seconds => false }] => [:'about_x_years', 1], + [3.years + 6.months, { :include_seconds => false }] => [:'over_x_years', 3], + [3.years + 10.months, { :include_seconds => false }] => [:'almost_x_years', 4] }.each do |passed, expected| assert_distance_of_time_in_words_translates_key passed, expected @@ -37,7 +37,7 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase end def assert_distance_of_time_in_words_translates_key(passed, expected) - diff, include_seconds = *passed + diff, passed_options = *passed key, count = *expected to = @from + diff @@ -45,7 +45,12 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase options[:count] = count if count I18n.expects(:t).with(key, options) - distance_of_time_in_words(@from, to, include_seconds, :locale => 'en') + distance_of_time_in_words(@from, to, passed_options.merge(:locale => 'en')) + end + + def test_time_ago_in_words_passes_locale + I18n.expects(:t).with(:less_than_x_minutes, :scope => :'datetime.distance_in_words', :count => 1, :locale => 'ru') + time_ago_in_words(15.seconds.ago, :locale => 'ru') end def test_distance_of_time_pluralizations diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index c9b8a5bb70..96c8cf25b1 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -21,20 +21,33 @@ class DateHelperTest < ActionView::TestCase def assert_distance_of_time_in_words(from, to=nil) to ||= from - # 0..1 with include_seconds - assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 0.seconds, true) - assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 4.seconds, true) - assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 5.seconds, true) - assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 9.seconds, true) - assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 10.seconds, true) - assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 19.seconds, true) - assert_equal "half a minute", distance_of_time_in_words(from, to + 20.seconds, true) - assert_equal "half a minute", distance_of_time_in_words(from, to + 39.seconds, true) - assert_equal "less than a minute", distance_of_time_in_words(from, to + 40.seconds, true) - assert_equal "less than a minute", distance_of_time_in_words(from, to + 59.seconds, true) - assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, true) - assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, true) - + # 0..1 with :include_seconds => true + assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 0.seconds, :include_seconds => true) + assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 4.seconds, :include_seconds => true) + assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 5.seconds, :include_seconds => true) + assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 9.seconds, :include_seconds => true) + assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 10.seconds, :include_seconds => true) + assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 19.seconds, :include_seconds => true) + assert_equal "half a minute", distance_of_time_in_words(from, to + 20.seconds, :include_seconds => true) + assert_equal "half a minute", distance_of_time_in_words(from, to + 39.seconds, :include_seconds => true) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 40.seconds, :include_seconds => true) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 59.seconds, :include_seconds => true) + assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, :include_seconds => true) + assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, :include_seconds => true) + + # 0..1 with :include_seconds => false + assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds, :include_seconds => false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 4.seconds, :include_seconds => false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 5.seconds, :include_seconds => false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 9.seconds, :include_seconds => false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 10.seconds, :include_seconds => false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 19.seconds, :include_seconds => false) + assert_equal "less than a minute", distance_of_time_in_words(from, to + 20.seconds, :include_seconds => false) + assert_equal "1 minute", distance_of_time_in_words(from, to + 39.seconds, :include_seconds => false) + assert_equal "1 minute", distance_of_time_in_words(from, to + 40.seconds, :include_seconds => false) + assert_equal "1 minute", distance_of_time_in_words(from, to + 59.seconds, :include_seconds => false) + assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, :include_seconds => false) + assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, :include_seconds => false) # First case 0..1 assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds) assert_equal "less than a minute", distance_of_time_in_words(from, to + 29.seconds) @@ -95,7 +108,8 @@ class DateHelperTest < ActionView::TestCase # test to < from assert_equal "about 4 hours", distance_of_time_in_words(from + 4.hours, to) - assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, true) + assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => true) + assert_equal "less than a minute", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => false) end def test_distance_in_words @@ -103,6 +117,11 @@ class DateHelperTest < ActionView::TestCase assert_distance_of_time_in_words(from) end + def test_time_ago_in_words_passes_include_seconds + assert_equal "less than 20 seconds", time_ago_in_words(15.seconds.ago, :include_seconds => true) + assert_equal "less than a minute", time_ago_in_words(15.seconds.ago, :include_seconds => false) + end + def test_distance_in_words_with_time_zones from = Time.mktime(2004, 6, 6, 21, 45, 0) assert_distance_of_time_in_words(from.in_time_zone('Alaska')) @@ -125,13 +144,33 @@ class DateHelperTest < ActionView::TestCase start_date = Date.new 1982, 12, 3 end_date = Date.new 2010, 11, 30 assert_equal("almost 28 years", distance_of_time_in_words(start_date, end_date)) + assert_equal("almost 28 years", distance_of_time_in_words(end_date, start_date)) end def test_distance_in_words_with_integers - assert_equal "less than a minute", distance_of_time_in_words(59) + assert_equal "1 minute", distance_of_time_in_words(59) assert_equal "about 1 hour", distance_of_time_in_words(60*60) - assert_equal "less than a minute", distance_of_time_in_words(0, 59) + assert_equal "1 minute", distance_of_time_in_words(0, 59) assert_equal "about 1 hour", distance_of_time_in_words(60*60, 0) + assert_equal "about 3 years", distance_of_time_in_words(10**8) + assert_equal "about 3 years", distance_of_time_in_words(0, 10**8) + end + + def test_distance_in_words_with_times + assert_equal "1 minute", distance_of_time_in_words(30.seconds) + assert_equal "1 minute", distance_of_time_in_words(59.seconds) + assert_equal "2 minutes", distance_of_time_in_words(119.seconds) + assert_equal "2 minutes", distance_of_time_in_words(1.minute + 59.seconds) + assert_equal "3 minutes", distance_of_time_in_words(2.minute + 30.seconds) + assert_equal "44 minutes", distance_of_time_in_words(44.minutes + 29.seconds) + assert_equal "about 1 hour", distance_of_time_in_words(44.minutes + 30.seconds) + assert_equal "about 1 hour", distance_of_time_in_words(60.minutes) + + # include seconds + assert_equal "half a minute", distance_of_time_in_words(39.seconds, 0, :include_seconds => true) + assert_equal "less than a minute", distance_of_time_in_words(40.seconds, 0, :include_seconds => true) + assert_equal "less than a minute", distance_of_time_in_words(59.seconds, 0, :include_seconds => true) + assert_equal "1 minute", distance_of_time_in_words(60.seconds, 0, :include_seconds => true) end def test_time_ago_in_words @@ -567,7 +606,7 @@ class DateHelperTest < ActionView::TestCase end def test_select_minute_with_html_options - expected = expected = %(<select id="date_minute" name="date[minute]" class="selector" accesskey="M">\n) + expected = %(<select id="date_minute" name="date[minute]" class="selector" accesskey="M">\n) expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n) expected << "</select>\n" diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 3fa3898ec8..beb3ea752a 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -97,7 +97,7 @@ class FormHelperTest < ActionView::TestCase end end - match "/foo", :to => "controller#action" + get "/foo", :to => "controller#action" root :to => "main#index" end @@ -350,36 +350,52 @@ class FormHelperTest < ActionView::TestCase text_field("user", "email", :type => "email") end - def test_check_box + def test_check_box_is_html_safe assert check_box("post", "secret").html_safe? + end + + def test_check_box_checked_if_object_value_is_same_that_check_value assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret") ) + end + + def test_check_box_not_checked_if_object_value_is_same_that_unchecked_value @post.secret = 0 assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret") ) + end + + def test_check_box_checked_if_option_checked_is_present assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret" ,{"checked"=>"checked"}) ) + end + + def test_check_box_checked_if_object_value_is_true @post.secret = true assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret") ) + assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret?") ) + end + def test_check_box_checked_if_object_value_includes_checked_value @post.secret = ['0'] assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret") ) + @post.secret = ['1'] assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />', @@ -392,12 +408,92 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal('<input id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret", :include_hidden => false)) end - def test_check_box_with_explicit_checked_and_unchecked_values + def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_string @post.secret = "on" assert_dom_equal( '<input name="post[secret]" type="hidden" value="off" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="on" />', check_box("post", "secret", {}, "on", "off") ) + + @post.secret = "off" + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="off" /><input id="post_secret" name="post[secret]" type="checkbox" value="on" />', + check_box("post", "secret", {}, "on", "off") + ) + end + + def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_boolean + @post.secret = false + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="true" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="false" />', + check_box("post", "secret", {}, false, true) + ) + + @post.secret = true + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="true" /><input id="post_secret" name="post[secret]" type="checkbox" value="false" />', + check_box("post", "secret", {}, false, true) + ) + end + + def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_integer + @post.secret = 0 + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="1" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="0" />', + check_box("post", "secret", {}, 0, 1) + ) + + @post.secret = 1 + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />', + check_box("post", "secret", {}, 0, 1) + ) + + @post.secret = 2 + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />', + check_box("post", "secret", {}, 0, 1) + ) + end + + def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_float + @post.secret = 0.0 + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="1" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="0" />', + check_box("post", "secret", {}, 0, 1) + ) + + @post.secret = 1.1 + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />', + check_box("post", "secret", {}, 0, 1) + ) + + @post.secret = 2.2 + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />', + check_box("post", "secret", {}, 0, 1) + ) + end + + def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_big_decimal + @post.secret = BigDecimal.new(0) + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="1" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="0" />', + check_box("post", "secret", {}, 0, 1) + ) + + @post.secret = BigDecimal.new(1) + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />', + check_box("post", "secret", {}, 0, 1) + ) + + @post.secret = BigDecimal.new(2.2, 1) + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />', + check_box("post", "secret", {}, 0, 1) + ) end def test_check_box_with_nil_unchecked_value @@ -2169,6 +2265,23 @@ class FormHelperTest < ActionView::TestCase ActionView::Base.default_form_builder = old_default_form_builder end + def test_lazy_loading_default_form_builder + old_default_form_builder, ActionView::Base.default_form_builder = + ActionView::Base.default_form_builder, "FormHelperTest::LabelledFormBuilder" + + form_for(@post) do |f| + concat f.text_field(:title) + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + end + + assert_dom_equal expected, output_buffer + ensure + ActionView::Base.default_form_builder = old_default_form_builder + end + def test_fields_for_with_labelled_builder output_buffer = fields_for(:post, @post, :builder => LabelledFormBuilder) do |f| concat f.text_field(:title) diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index 0b96985002..fe7607ee26 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -42,36 +42,6 @@ class JavaScriptHelperTest < ActionView::TestCase assert_instance_of ActiveSupport::SafeBuffer, escape_javascript(ActiveSupport::SafeBuffer.new(given)) end - def test_button_to_function - assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />), - button_to_function("Greeting", "alert('Hello world!')") - end - - def test_button_to_function_with_onclick - assert_dom_equal "<input onclick=\"alert('Goodbye World :('); alert('Hello world!');\" type=\"button\" value=\"Greeting\" />", - button_to_function("Greeting", "alert('Hello world!')", :onclick => "alert('Goodbye World :(')") - end - - def test_button_to_function_without_function - assert_dom_equal "<input onclick=\";\" type=\"button\" value=\"Greeting\" />", - button_to_function("Greeting") - end - - def test_link_to_function - assert_dom_equal %(<a href="#" onclick="alert('Hello world!'); return false;">Greeting</a>), - link_to_function("Greeting", "alert('Hello world!')") - end - - def test_link_to_function_with_existing_onclick - assert_dom_equal %(<a href="#" onclick="confirm('Sanity!'); alert('Hello world!'); return false;">Greeting</a>), - link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')") - end - - def test_function_with_href - assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>), - link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/') - end - def test_javascript_tag self.output_buffer = 'foo' diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 122b07d348..cdaca56ef2 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -21,9 +21,7 @@ module RenderTestCases end def test_render_without_options - @view.render() - flunk "Render did not raise ArgumentError" - rescue ArgumentError => e + e = assert_raises(ArgumentError) { @view.render() } assert_match "You invoked render but did not give any of :partial, :template, :inline, :file or :text option.", e.message end @@ -153,25 +151,26 @@ module RenderTestCases end def test_render_partial_with_invalid_name - @view.render(:partial => "test/200") - flunk "Render did not raise ArgumentError" - rescue ArgumentError => e + e = assert_raises(ArgumentError) { @view.render(:partial => "test/200") } assert_equal "The partial name (test/200) is not a valid Ruby identifier; " + - "make sure your partial name starts with a letter or underscore, " + - "and is followed by any combinations of letters, numbers, or underscores.", e.message + "make sure your partial name starts with a letter or underscore, " + + "and is followed by any combinations of letters, numbers, or underscores.", e.message + end + + def test_render_partial_with_missing_filename + e = assert_raises(ArgumentError) { @view.render(:partial => "test/") } + assert_equal "The partial name (test/) is not a valid Ruby identifier; " + + "make sure your partial name starts with a letter or underscore, " + + "and is followed by any combinations of letters, numbers, or underscores.", e.message end def test_render_partial_with_incompatible_object - @view.render(:partial => nil) - flunk "Render did not raise ArgumentError" - rescue ArgumentError => e + e = assert_raises(ArgumentError) { @view.render(:partial => nil) } assert_equal "'#{nil.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.", e.message end def test_render_partial_with_errors - @view.render(:partial => "test/raise") - flunk "Render did not raise Template::Error" - rescue ActionView::Template::Error => e + e = assert_raises(ActionView::Template::Error) { @view.render(:partial => "test/raise") } assert_match %r!method.*doesnt_exist!, e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number @@ -180,9 +179,7 @@ module RenderTestCases end def test_render_sub_template_with_errors - @view.render(:template => "test/sub_template_raise") - flunk "Render did not raise Template::Error" - rescue ActionView::Template::Error => e + e = assert_raises(ActionView::Template::Error) { @view.render(:template => "test/sub_template_raise") } assert_match %r!method.*doesnt_exist!, e.message assert_equal "Trace of template inclusion: #{File.expand_path("#{FIXTURE_LOAD_PATH}/test/sub_template_raise.html.erb")}", e.sub_template_message assert_equal "1", e.line_number @@ -190,9 +187,7 @@ module RenderTestCases end def test_render_file_with_errors - @view.render(:file => File.expand_path("test/_raise", FIXTURE_LOAD_PATH)) - flunk "Render did not raise Template::Error" - rescue ActionView::Template::Error => e + e = assert_raises(ActionView::Template::Error) { @view.render(:file => File.expand_path("test/_raise", FIXTURE_LOAD_PATH)) } assert_match %r!method.*doesnt_exist!, e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number @@ -238,11 +233,26 @@ module RenderTestCases def test_render_partial_with_nil_values_in_collection assert_equal "Hello: davidHello: Anonymous", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), nil ]) end - + def test_render_partial_with_layout_using_collection_and_template assert_equal "<b>Hello: Amazon</b><b>Hello: Yahoo</b>", @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ]) end + def test_render_partial_with_layout_using_collection_and_template_makes_current_item_available_in_layout + assert_equal '<b class="amazon">Hello: Amazon</b><b class="yahoo">Hello: Yahoo</b>', + @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ]) + end + + def test_render_partial_with_layout_using_collection_and_template_makes_current_item_counter_available_in_layout + assert_equal '<b data-counter="0">Hello: Amazon</b><b data-counter="1">Hello: Yahoo</b>', + @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object_counter', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ]) + end + + def test_render_partial_with_layout_using_object_and_template_makes_object_available_in_layout + assert_equal '<b class="amazon">Hello: Amazon</b>', + @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object', :object => Customer.new("Amazon")) + end + def test_render_partial_with_empty_array_should_return_nil assert_nil @view.render(:partial => []) end @@ -274,7 +284,7 @@ module RenderTestCases # TODO: The reason for this test is unclear, improve documentation def test_render_missing_xml_partial_and_raise_missing_template @view.formats = [:xml] - assert_raise(ActionView::MissingTemplate) { @view.render(:partial => "test/layout_for_partial") } + assert_raises(ActionView::MissingTemplate) { @view.render(:partial => "test/layout_for_partial") } ensure @view.formats = nil end @@ -315,7 +325,7 @@ module RenderTestCases ActionView::Template.register_template_handler :foo, CustomHandler assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo) end - + def test_render_knows_about_types_registered_when_extensions_are_checked_earlier_in_initialization ActionView::Template::Handlers.extensions ActionView::Template.register_template_handler :foo, CustomHandler @@ -325,7 +335,7 @@ module RenderTestCases def test_render_ignores_templates_with_malformed_template_handlers ActiveSupport::Deprecation.silence do %w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name| - assert_raise(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") } + assert_raises(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") } end end end @@ -450,23 +460,15 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_incompatible_external_encoding with_external_encoding Encoding::SHIFT_JIS do - begin - @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield") - flunk 'Should have raised incompatible encoding error' - rescue ActionView::Template::Error => error - assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message - end + e = assert_raises(ActionView::Template::Error) { @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield") } + assert_match 'Your template was not saved as valid Shift_JIS', e.original_exception.message end end def test_render_utf8_template_with_partial_with_incompatible_encoding with_external_encoding Encoding::SHIFT_JIS do - begin - @view.render(:file => "test/utf8_magic_with_bare_partial", :formats => [:html], :layouts => "layouts/yield") - flunk 'Should have raised incompatible encoding error' - rescue ActionView::Template::Error => error - assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message - end + e = assert_raises(ActionView::Template::Error) { @view.render(:file => "test/utf8_magic_with_bare_partial", :formats => [:html], :layouts => "layouts/yield") } + assert_match 'Your template was not saved as valid Shift_JIS', e.original_exception.message end end diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb index adcbf1447f..108a674d95 100644 --- a/actionpack/test/template/test_test.rb +++ b/actionpack/test/template/test_test.rb @@ -48,7 +48,7 @@ class PeopleHelperTest < ActionView::TestCase def with_test_route_set with_routing do |set| set.draw do - match 'people', :to => 'people#index', :as => :people + get 'people', :to => 'people#index', :as => :people end yield end diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb index 397de9c2ce..97777ccff0 100644 --- a/actionpack/test/template/translation_helper_test.rb +++ b/actionpack/test/template/translation_helper_test.rb @@ -11,7 +11,8 @@ class TranslationHelperTest < ActiveSupport::TestCase :translations => { :templates => { :found => { :foo => 'Foo' }, - :array => { :foo => { :bar => 'Foo Bar' } } + :array => { :foo => { :bar => 'Foo Bar' } }, + :default => { :foo => 'Foo' } }, :foo => 'Foo', :hello => '<a>Hello World</a>', @@ -71,6 +72,10 @@ class TranslationHelperTest < ActiveSupport::TestCase assert_equal 'Foo Bar', @view.render(:file => 'translations/templates/array').strip end + def test_default_lookup_scoped_by_partial + assert_equal 'Foo', view.render(:file => 'translations/templates/default').strip + end + def test_missing_translation_scoped_by_partial expected = '<span class="translation_missing" title="translation missing: en.translations.templates.missing.missing">Missing</span>' assert_equal expected, view.render(:file => 'translations/templates/missing').strip @@ -102,4 +107,22 @@ class TranslationHelperTest < ActiveSupport::TestCase def test_translation_returning_an_array_ignores_html_suffix assert_equal ["foo", "bar"], translate(:'translations.array_html') end + + def test_translate_with_default_named_html + translation = translate(:'translations.missing', :default => :'translations.hello_html') + assert_equal '<a>Hello World</a>', translation + assert translation.html_safe? + end + + def test_translate_with_two_defaults_named_html + translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.hello_html']) + assert_equal '<a>Hello World</a>', translation + assert translation.html_safe? + end + + def test_translate_with_last_default_named_html + translation = translate(:'translations.missing', :default => [:'translations.missing', :'translations.hello_html']) + assert_equal '<a>Hello World</a>', translation + assert translation.html_safe? + end end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 88f506b217..eaa8bdbd26 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -15,9 +15,9 @@ class UrlHelperTest < ActiveSupport::TestCase routes = ActionDispatch::Routing::RouteSet.new routes.draw do - match "/" => "foo#bar" - match "/other" => "foo#other" - match "/article/:id" => "foo#article", :as => :article + get "/" => "foo#bar" + get "/other" => "foo#other" + get "/article/:id" => "foo#article", :as => :article end include routes.url_helpers @@ -471,25 +471,25 @@ end class UrlHelperControllerTest < ActionController::TestCase class UrlHelperController < ActionController::Base test_routes do - match 'url_helper_controller_test/url_helper/show/:id', + get 'url_helper_controller_test/url_helper/show/:id', :to => 'url_helper_controller_test/url_helper#show', :as => :show - match 'url_helper_controller_test/url_helper/profile/:name', + get 'url_helper_controller_test/url_helper/profile/:name', :to => 'url_helper_controller_test/url_helper#show', :as => :profile - match 'url_helper_controller_test/url_helper/show_named_route', + get 'url_helper_controller_test/url_helper/show_named_route', :to => 'url_helper_controller_test/url_helper#show_named_route', :as => :show_named_route - match "/:controller(/:action(/:id))" + get "/:controller(/:action(/:id))" - match 'url_helper_controller_test/url_helper/normalize_recall_params', + get 'url_helper_controller_test/url_helper/normalize_recall_params', :to => UrlHelperController.action(:normalize_recall), :as => :normalize_recall_params - match '/url_helper_controller_test/url_helper/override_url_helper/default', + get '/url_helper_controller_test/url_helper/override_url_helper/default', :to => 'url_helper_controller_test/url_helper#override_url_helper', :as => :override_url_helper end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index c6d8eae46c..789cff0673 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella* + * Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran* * `AM::Errors#to_json`: support `:full_messages` parameter *Bogdan Gusiev* diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc index 7d13a0123b..9b05384792 100644 --- a/activemodel/README.rdoc +++ b/activemodel/README.rdoc @@ -26,7 +26,7 @@ to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>. person = Person.new(:name => 'bob', :age => '18') person.name # => 'bob' person.age # => 18 - person.valid? # => false + person.valid? # => true It includes model name introspections, conversions, translations and validations, resulting in a class suitable to be used with Action Pack. @@ -116,9 +116,6 @@ behavior out of the box: person.errors.full_messages # => ["Name can not be nil"] - person.errors.full_messages - # => ["Name can not be nil"] - {Learn more}[link:classes/ActiveModel/Errors.html] * Model name introspection diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index d327913824..7f7fb90d87 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -151,13 +151,15 @@ module ActiveModel # Handle <tt>*_will_change!</tt> for +method_missing+. def attribute_will_change!(attr) + return if attribute_changed?(attr) + begin value = __send__(attr) value = value.duplicable? ? value.clone : value rescue TypeError, NoMethodError end - changed_attributes[attr] = value unless changed_attributes.include?(attr) + changed_attributes[attr] = value end # Handle <tt>reset_*!</tt> for +method_missing+. diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 016bf87d86..aba6618b56 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -3,7 +3,6 @@ require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/hash/reverse_merge' module ActiveModel # == Active Model Errors @@ -130,12 +129,12 @@ module ActiveModel # has more than one error message, yields once for each error message. # # p.errors.add(:name, "can't be blank") - # p.errors.each do |attribute, errors_array| + # p.errors.each do |attribute, error| # # Will yield :name and "can't be blank" # end # # p.errors.add(:name, "must be specified") - # p.errors.each do |attribute, errors_array| + # p.errors.each do |attribute, error| # # Will yield :name and "can't be blank" # # then yield :name and "must be specified" # end @@ -202,12 +201,12 @@ module ActiveModel # # <error>name must be specified</error> # # </errors> def to_xml(options={}) - to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true) + to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options)) end # Returns an Hash that can be used as the JSON representation for this object. # Options: - # * <tt>:full_messages</tt> - determines if json object should contain + # * <tt>:full_messages</tt> - determines if json object should contain # full messages or not. Default: <tt>false</tt>. def as_json(options=nil) to_hash(options && options[:full_messages]) @@ -217,7 +216,7 @@ module ActiveModel if full_messages messages = {} self.messages.each do |attribute, array| - messages[attribute] = array.map{|message| full_message(attribute, message) } + messages[attribute] = array.map { |message| full_message(attribute, message) } end messages else @@ -347,7 +346,7 @@ module ActiveModel :model => @base.class.model_name.human, :attribute => @base.class.human_attribute_name(attribute), :value => value - }.merge(options) + }.merge!(options) I18n.translate(key, options) end @@ -356,9 +355,10 @@ module ActiveModel def normalize_message(attribute, message, options) message ||= :invalid - if message.is_a?(Symbol) + case message + when Symbol generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS)) - elsif message.is_a?(Proc) + when Proc message.call else message diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml index ba49c6beaa..d17848c861 100644 --- a/activemodel/lib/active_model/locale/en.yml +++ b/activemodel/lib/active_model/locale/en.yml @@ -9,7 +9,7 @@ en: inclusion: "is not included in the list" exclusion: "is reserved" invalid: "is invalid" - confirmation: "doesn't match confirmation" + confirmation: "doesn't match %{attribute}" accepted: "must be accepted" empty: "can't be empty" blank: "can't be blank" diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb index 6825fdc653..3af95b09b0 100644 --- a/activemodel/lib/active_model/model.rb +++ b/activemodel/lib/active_model/model.rb @@ -2,11 +2,11 @@ module ActiveModel # == Active Model Basic Model # - # Includes the required interface for an object to interact with +ActionPack+, - # using different +ActiveModel+ modules. It includes model name introspections, + # Includes the required interface for an object to interact with <tt>ActionPack</tt>, + # using different <tt>ActiveModel</tt> modules. It includes model name introspections, # conversions, translations and validations. Besides that, it allows you to # initialize the object with a hash of attributes, pretty much like - # +ActiveRecord+ does. + # <tt>ActiveRecord</tt> does. # # A minimal implementation could be: # @@ -19,8 +19,8 @@ module ActiveModel # person.name # => 'bob' # person.age # => 18 # - # Note that, by default, +ActiveModel::Model+ implements +persisted?+ to - # return +false+, which is the most common case. You may want to override it + # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt> to + # return <tt>false</tt>, which is the most common case. You may want to override it # in your class to simulate a different scenario: # # class Person @@ -35,14 +35,14 @@ module ActiveModel # person = Person.new(:id => 1, :name => 'bob') # person.persisted? # => true # - # Also, if for some reason you need to run code on +initialize+, make sure you + # Also, if for some reason you need to run code on <tt>initialize</tt>, make sure you # call super if you want the attributes hash initialization to happen. # # class Person # include ActiveModel::Model # attr_accessor :id, :name, :omg # - # def initialize(attributes) + # def initialize(attributes={}) # super # @omg ||= true # end @@ -52,7 +52,7 @@ module ActiveModel # person.omg # => true # # For more detailed information on other functionalities available, please refer - # to the specific modules included in +ActiveModel::Model+ (see below). + # to the specific modules included in <tt>ActiveModel::Model</tt> (see below). module Model def self.included(base) base.class_eval do diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 5665e10002..2b5fc57a3a 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -54,7 +54,7 @@ module ActiveModel defaults << options[:default] if options[:default] defaults << @human - options = {:scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults}.merge(options.except(:default)) + options = { :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults }.merge!(options.except(:default)) I18n.translate(defaults.shift, options) end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index 32f2aa46bd..f3781f7a68 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -4,6 +4,8 @@ require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/enumerable' +require 'active_support/deprecation' +require 'active_support/core_ext/object/try' require 'active_support/descendants_tracker' module ActiveModel @@ -69,27 +71,34 @@ module ActiveModel end # Notify list of observers of a change. - def notify_observers(*arg) - observer_instances.each { |observer| observer.update(*arg) } + def notify_observers(*args) + observer_instances.each { |observer| observer.update(*args) } end # Total number of observers. - def count_observers + def observers_count observer_instances.size end + def count_observers + msg = "count_observers is deprecated in favor of observers_count" + ActiveSupport::Deprecation.warn(msg) + observers_count + end + protected def instantiate_observer(observer) #:nodoc: # string/symbol if observer.respond_to?(:to_sym) - observer.to_s.camelize.constantize.instance - elsif observer.respond_to?(:instance) + observer = observer.to_s.camelize.constantize + end + if observer.respond_to?(:instance) observer.instance else raise ArgumentError, - "#{observer} must be a lowercase, underscored class name (or an " + - "instance of the class itself) responding to the instance " + - "method. Example: Person.observers = :big_brother # calls " + + "#{observer} must be a lowercase, underscored class name (or " + + "the class itself) responding to the method :instance. " + + "Example: Person.observers = :big_brother # calls " + "BigBrother.instance" end end @@ -186,7 +195,7 @@ module ActiveModel def observe(*models) models.flatten! models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model } - redefine_method(:observed_classes) { models } + singleton_class.redefine_method(:observed_classes) { models } end # Returns an array of Classes to observe. @@ -205,15 +214,12 @@ module ActiveModel # The class observed by default is inferred from the observer's class name: # assert_equal Person, PersonObserver.observed_class def observed_class - if observed_class_name = name[/(.*)Observer/, 1] - observed_class_name.constantize - else - nil - end + name[/(.*)Observer/, 1].try :constantize end end # Start observing the declared classes and their subclasses. + # Called automatically by the instance method. def initialize observed_classes.each { |klass| add_observer!(klass) } end @@ -242,6 +248,7 @@ module ActiveModel klass.add_observer(self) end + # Returns true if notifications are disabled for this object. def disabled_for?(object) klass = object.class return false unless klass.respond_to?(:observers) diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index e7a57cf691..8711b24124 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -55,17 +55,14 @@ module ActiveModel module InstanceMethodsOnActivation # Returns self if the password is correct, otherwise false. def authenticate(unencrypted_password) - if BCrypt::Password.new(password_digest) == unencrypted_password - self - else - false - end + BCrypt::Password.new(password_digest) == unencrypted_password && self end - # Encrypts the password into the password_digest attribute. + # Encrypts the password into the password_digest attribute, only if the + # new password is not blank. def password=(unencrypted_password) - @password = unencrypted_password unless unencrypted_password.blank? + @password = unencrypted_password self.password_digest = BCrypt::Password.create(unencrypted_password) end end diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 4323ee1e09..4403ef060b 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -26,17 +26,18 @@ module ActiveModel # person.serializable_hash # => {"name"=>"Bob"} # # You need to declare an attributes hash which contains the attributes - # you want to serialize. When called, serializable hash will use + # you want to serialize. Attributes must be strings, not symbols. + # When called, serializable hash will use # instance methods that match the name of the attributes hash's keys. # In order to override this behavior, take a look at the private - # method read_attribute_for_serialization. + # method +read_attribute_for_serialization+. # # Most of the time though, you will want to include the JSON or XML # serializations. Both of these modules automatically include the - # ActiveModel::Serialization module, so there is no need to explicitly + # <tt>ActiveModel::Serialization</tt> module, so there is no need to explicitly # include it. # - # So a minimal implementation including XML and JSON would be: + # A minimal implementation including XML and JSON would be: # # class Person # include ActiveModel::Serializers::JSON @@ -63,7 +64,12 @@ module ActiveModel # person.to_json # => "{\"name\":\"Bob\"}" # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person... # - # Valid options are <tt>:only</tt>, <tt>:except</tt> and <tt>:methods</tt> . + # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and <tt>include</tt>. + # The following are all valid examples: + # + # person.serializable_hash(:only => 'name') + # person.serializable_hash(:include => :address) + # person.serializable_hash(:include => { :address => { :only => 'city' }}) module Serialization def serializable_hash(options = nil) options ||= {} @@ -78,8 +84,7 @@ module ActiveModel hash = {} attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) } - method_names = Array(options[:methods]).select { |n| respond_to?(n) } - method_names.each { |n| hash[n.to_s] = send(n) } + Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) } serializable_add_includes(options) do |association, records, opts| hash[association.to_s] = if records.is_a?(Enumerable) diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index 02b7c54d61..6f0ca92e2a 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/hash/reverse_merge' - module ActiveModel # == Active Model Translation @@ -43,19 +41,20 @@ module ActiveModel # # Specify +options+ with additional translating options. def human_attribute_name(attribute, options = {}) - defaults = [] + options = { :count => 1 }.merge!(options) parts = attribute.to_s.split(".", 2) attribute = parts.pop namespace = parts.pop + attributes_scope = "#{self.i18n_scope}.attributes" if namespace - lookup_ancestors.each do |klass| - defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}" + defaults = lookup_ancestors.map do |klass| + :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}" end - defaults << :"#{self.i18n_scope}.attributes.#{namespace}.#{attribute}" + defaults << :"#{attributes_scope}.#{namespace}.#{attribute}" else - lookup_ancestors.each do |klass| - defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}" + defaults = lookup_ancestors.map do |klass| + :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}" end end @@ -63,7 +62,7 @@ module ActiveModel defaults << options.delete(:default) if options[:default] defaults << attribute.humanize - options.reverse_merge! :count => 1, :default => defaults + options[:default] = defaults I18n.translate(defaults.shift, options) end end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 0e15155b85..3ed72bae3b 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -65,7 +65,7 @@ module ActiveModel # # attr_accessor :first_name, :last_name # - # validates_each :first_name, :last_name do |record, attr, value| + # validates_each :first_name, :last_name, :allow_blank => true do |record, attr, value| # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z # end # end @@ -128,6 +128,19 @@ module ActiveModel # end # end # + # Options: + # * <tt>:on</tt> - Specifies the context where this validation is active + # (e.g. <tt>:on => :create</tt> or <tt>:on => :custom_validation_context</tt>) + # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. + # * <tt>:allow_blank</tt> - Skip validation if attribute is blank. + # * <tt>:if</tt> - Specifies a method, proc or string to call to determine + # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>, + # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, + # proc or string should return or evaluate to a true or false value. + # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should + # not occur (e.g. <tt>:unless => :skip_validation</tt>, or + # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The + # method, proc or string should return or evaluate to a true or false value. def validate(*args, &block) options = args.extract_options! if options.key?(:on) @@ -145,7 +158,7 @@ module ActiveModel _validators.values.flatten.uniq end - # List all validators that being used to validate a specific attribute. + # List all validators that are being used to validate a specific attribute. def validators_on(*attributes) attributes.map do |attribute| _validators[attribute.to_sym] diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index c39c85e1af..dbafd0bd1a 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -8,7 +8,8 @@ module ActiveModel # Provides an interface for any class to have <tt>before_validation</tt> and # <tt>after_validation</tt> callbacks. # - # First, extend ActiveModel::Callbacks from the class you are creating: + # First, include ActiveModel::Validations::Callbacks from the class you are + # creating: # # class MyModel # include ActiveModel::Validations::Callbacks diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index e8526303e2..69ab74734d 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -5,7 +5,8 @@ module ActiveModel class ConfirmationValidator < EachValidator def validate_each(record, attribute, value) if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed) - record.errors.add(attribute, :confirmation, options) + human_attribute_name = record.class.human_attribute_name(attribute) + record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(:attribute => human_attribute_name)) end end diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 72b8562b93..991c5f7b82 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -126,12 +126,12 @@ module ActiveModel # end # # Standard configuration options (:on, :if and :unless), which are - # available on the class version of validates_with, should instead be - # placed on the <tt>validates</tt> method as these are applied and tested + # available on the class version of +validates_with+, should instead be + # placed on the +validates+ method as these are applied and tested # in the callback # # If you pass any additional configuration options, they will be passed - # to the class and available as <tt>options</tt>, please refer to the + # to the class and available as +options+, please refer to the # class version of this method for more information # def validates_with(*args, &block) diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb index f6ec24ae57..9891be8d77 100644 --- a/activemodel/test/cases/observing_test.rb +++ b/activemodel/test/cases/observing_test.rb @@ -70,23 +70,38 @@ class ObservingTest < ActiveModel::TestCase ObservedModel.instantiate_observers end + test "raises an appropriate error when a developer accidentally adds the wrong class (i.e. Widget instead of WidgetObserver)" do + assert_raise ArgumentError do + ObservedModel.observers = ['string'] + ObservedModel.instantiate_observers + end + assert_raise ArgumentError do + ObservedModel.observers = [:string] + ObservedModel.instantiate_observers + end + assert_raise ArgumentError do + ObservedModel.observers = [String] + ObservedModel.instantiate_observers + end + end + test "passes observers to subclasses" do FooObserver.instance bar = Class.new(Foo) - assert_equal Foo.count_observers, bar.count_observers + assert_equal Foo.observers_count, bar.observers_count end end class ObserverTest < ActiveModel::TestCase def setup ObservedModel.observers = :foo_observer - FooObserver.instance_eval do + FooObserver.singleton_class.instance_eval do alias_method :original_observed_classes, :observed_classes end end def teardown - FooObserver.instance_eval do + FooObserver.singleton_class.instance_eval do undef_method :observed_classes alias_method :observed_classes, :original_observed_classes end @@ -98,32 +113,25 @@ class ObserverTest < ActiveModel::TestCase test "tracks implicit observable models" do instance = FooObserver.new - assert instance.send(:observed_classes).include?(Foo), "Foo not in #{instance.send(:observed_classes).inspect}" - assert !instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{instance.send(:observed_classes).inspect}" + assert_equal [Foo], instance.observed_classes end test "tracks explicit observed model class" do - old_instance = FooObserver.new - assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}" FooObserver.observe ObservedModel instance = FooObserver.new - assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}" + assert_equal [ObservedModel], instance.observed_classes end test "tracks explicit observed model as string" do - old_instance = FooObserver.new - assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}" FooObserver.observe 'observed_model' instance = FooObserver.new - assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}" + assert_equal [ObservedModel], instance.observed_classes end test "tracks explicit observed model as symbol" do - old_instance = FooObserver.new - assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}" FooObserver.observe :observed_model instance = FooObserver.new - assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}" + assert_equal [ObservedModel], instance.observed_classes end test "calls existing observer event" do @@ -145,4 +153,15 @@ class ObserverTest < ActiveModel::TestCase end assert_equal :in_around_save, yielded_value end + + test "observe redefines observed_classes class method" do + class BarObserver < ActiveModel::Observer + observe :foo + end + + assert_equal [Foo], BarObserver.observed_classes + + BarObserver.observe(ObservedModel) + assert_equal [ObservedModel], BarObserver.observed_classes + end end diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 4338a3fc53..c451cc1aca 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -19,6 +19,12 @@ class SecurePasswordTest < ActiveModel::TestCase assert !@user.valid?, 'user should be invalid' end + test "blank password doesn't override previous password" do + @user.password = 'test' + @user.password = '' + assert_equal @user.password, 'test' + end + test "password must be present" do assert !@user.valid? assert_equal 1, @user.errors.size diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb index 54e86d48db..4999583802 100644 --- a/activemodel/test/cases/translation_test.rb +++ b/activemodel/test/cases/translation_test.rb @@ -82,9 +82,15 @@ class ActiveModelI18nTests < ActiveModel::TestCase end def test_human_does_not_modify_options - options = {:default => 'person model'} + options = { :default => 'person model' } Person.model_name.human(options) - assert_equal({:default => 'person model'}, options) + assert_equal({ :default => 'person model' }, options) + end + + def test_human_attribute_name_does_not_modify_options + options = { :default => 'Cool gender' } + Person.human_attribute_name('gender', options) + assert_equal({ :default => 'Cool gender' }, options) end end diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb index d0418170fa..f7556a249f 100644 --- a/activemodel/test/cases/validations/confirmation_validation_test.rb +++ b/activemodel/test/cases/validations/confirmation_validation_test.rb @@ -44,7 +44,7 @@ class ConfirmationValidationTest < ActiveModel::TestCase p.karma_confirmation = "None" assert p.invalid? - assert_equal ["doesn't match confirmation"], p.errors[:karma] + assert_equal ["doesn't match Karma"], p.errors[:karma_confirmation] p.karma = "None" assert p.valid? @@ -52,4 +52,23 @@ class ConfirmationValidationTest < ActiveModel::TestCase Person.reset_callbacks(:validate) end + def test_title_confirmation_with_i18n_attribute + @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend + I18n.load_path.clear + I18n.backend = I18n::Backend::Simple.new + I18n.backend.store_translations('en', { + :errors => {:messages => {:confirmation => "doesn't match %{attribute}"}}, + :activemodel => {:attributes => {:topic => {:title => 'Test Title'}}} + }) + + Topic.validates_confirmation_of(:title) + + t = Topic.new("title" => "We should be confirmed","title_confirmation" => "") + assert t.invalid? + assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation] + + I18n.load_path.replace @old_load_path + I18n.backend = @old_backend + end + end diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb index 0679e67f84..df0fcd243a 100644 --- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb @@ -37,7 +37,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase # validates_confirmation_of: generate_message(attr_name, :confirmation, :message => custom_message) def test_generate_message_confirmation_with_default_message - assert_equal "doesn't match confirmation", @person.errors.generate_message(:title, :confirmation) + assert_equal "doesn't match Title", @person.errors.generate_message(:title, :confirmation) end def test_generate_message_confirmation_with_custom_message diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index e9f0e430fe..6b6aad3bd1 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -81,7 +81,7 @@ class I18nValidationTest < ActiveModel::TestCase test "validates_confirmation_of on generated message #{name}" do Person.validates_confirmation_of :title, validation_options @person.title_confirmation = 'foo' - @person.errors.expects(:generate_message).with(:title, :confirmation, generate_message_options) + @person.errors.expects(:generate_message).with(:title_confirmation, :confirmation, generate_message_options.merge(:attribute => 'Title')) @person.valid? end end @@ -217,24 +217,29 @@ class I18nValidationTest < ActiveModel::TestCase # To make things DRY this macro is defined to define 3 tests for every validation case. def self.set_expectations_for_validation(validation, error_type, &block_that_sets_validation) + if error_type == :confirmation + attribute = :title_confirmation + else + attribute = :title + end # test "validates_confirmation_of finds custom model key translation when blank" test "#{validation} finds custom model key translation when #{error_type}" do - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message'}}}}}} I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}} yield(@person, {}) @person.valid? - assert_equal ['custom message'], @person.errors[:title] + assert_equal ['custom message'], @person.errors[attribute] end # test "validates_confirmation_of finds custom model key translation with interpolation when blank" test "#{validation} finds custom model key translation with interpolation when #{error_type}" do - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message with %{extra}'}}}}}} + I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message with %{extra}'}}}}}} I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}} yield(@person, {:extra => "extra information"}) @person.valid? - assert_equal ['custom message with extra information'], @person.errors[:title] + assert_equal ['custom message with extra information'], @person.errors[attribute] end # test "validates_confirmation_of finds global default key translation when blank" @@ -243,7 +248,7 @@ class I18nValidationTest < ActiveModel::TestCase yield(@person, {}) @person.valid? - assert_equal ['global message'], @person.errors[:title] + assert_equal ['global message'], @person.errors[attribute] end end diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb index 575154ffbd..90bc018ae1 100644 --- a/activemodel/test/cases/validations/validates_test.rb +++ b/activemodel/test/cases/validations/validates_test.rb @@ -154,6 +154,6 @@ class ValidatesTest < ActiveModel::TestCase topic.title = "What's happening" topic.title_confirmation = "Not this" assert !topic.valid? - assert_equal ['Y U NO CONFIRM'], topic.errors[:title] + assert_equal ['Y U NO CONFIRM'], topic.errors[:title_confirmation] end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 85cb3e0e20..aa84dacf07 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,27 @@ ## Rails 4.0.0 (unreleased) ## +* Added an :index option to automatically create indexes for references + and belongs_to statements in migrations. + + The `references` and `belongs_to` methods now support an `index` + option that receives either a boolean value or an options hash + that is identical to options available to the add_index method: + + create_table :messages do |t| + t.references :person, :index => true + end + + Is the same as: + + create_table :messages do |t| + t.references :person + end + add_index :messages, :person_id + + Generators have also been updated to use the new syntax. + + [Joshua Wood] + * Added bang methods for mutating `ActiveRecord::Relation` objects. For example, while `foo.where(:bar)` will return a new object leaving `foo` unchanged, `foo.where!(:bar)` will mutate the foo @@ -217,7 +239,7 @@ * PostgreSQL hstore types are automatically deserialized from the database. -## Rails 3.2.3 (unreleased) ## +## Rails 3.2.3 (March 30, 2012) ## * Added find_or_create_by_{attribute}! dynamic method. *Andrew White* diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 4e09a43f8e..e75003f261 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -132,7 +132,8 @@ module ActiveRecord # ActiveRecord::RecordNotFound is rescued within the method, and it is # not reraised. The proxy is \reset and +nil+ is the return value. def load_target - @target ||= find_target if find_target? + @target = find_target if (@stale_state && stale_target?) || find_target? + loaded! unless loaded? target rescue ActiveRecord::RecordNotFound diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 81c6e400d2..ddfc6f6c05 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -77,7 +77,7 @@ module ActiveRecord end def stale_state - owner[reflection.foreign_key].to_s + owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s end end end diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb index 2ee5dbbd70..88ce03a3cd 100644 --- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -27,7 +27,8 @@ module ActiveRecord end def stale_state - [super, owner[reflection.foreign_type].to_s] + foreign_key = super + foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s] end end end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 5eda0387c4..ad029d1101 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -61,11 +61,15 @@ module ActiveRecord @association end - def scoped + def scoped(options = nil) association = @association - association.scoped.extending do + scope = association.scoped + + scope.extending! do define_method(:proxy_association) { association } end + scope.merge!(options) if options + scope end def respond_to?(name, include_private = false) @@ -126,6 +130,19 @@ module ActiveRecord proxy_association.reload self end + + # Define array public methods because we know it should be invoked over + # the target, so we can have a performance improvement using those methods + # in association collections + Array.public_instance_methods.each do |m| + unless method_defined?(m) + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{m}(*args, &block) + target.public_send(:#{m}, *args, &block) if load_target + end + RUBY + end + end end end end diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index fd0e90aaf0..be890e5767 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -62,7 +62,7 @@ module ActiveRecord # properly support stale-checking for nested associations. def stale_state if through_reflection.macro == :belongs_to - owner[through_reflection.foreign_key].to_s + owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 561e48d52e..46c7fc71ac 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -2,6 +2,7 @@ require 'thread' require 'monitor' require 'set' require 'active_support/core_ext/module/deprecation' +require 'timeout' module ActiveRecord # Raised when a connection could not be obtained within the connection @@ -11,9 +12,6 @@ module ActiveRecord # Raised when a connection pool is full and another connection is requested class PoolFullError < ConnectionNotEstablished - def initialize size, timeout - super("Connection pool of size #{size} and timeout #{timeout}s is full") - end end module ConnectionAdapters @@ -94,6 +92,21 @@ module ActiveRecord attr_accessor :automatic_reconnect, :timeout attr_reader :spec, :connections, :size, :reaper + class Latch # :nodoc: + def initialize + @mutex = Mutex.new + @cond = ConditionVariable.new + end + + def release + @mutex.synchronize { @cond.broadcast } + end + + def await + @mutex.synchronize { @cond.wait @mutex } + end + end + # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification # object which describes database connection information (e.g. adapter, # host name, username, password, etc), as well as the maximum size for @@ -115,6 +128,7 @@ module ActiveRecord # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 + @latch = Latch.new @connections = [] @automatic_reconnect = true end @@ -139,8 +153,10 @@ module ActiveRecord # #release_connection releases the connection-thread association # and returns the connection to the pool. def release_connection(with_id = current_connection_id) - conn = @reserved_connections.delete(with_id) - checkin conn if conn + synchronize do + conn = @reserved_connections.delete(with_id) + checkin conn if conn + end end # If a connection already exists yield it to the block. If no connection @@ -205,23 +221,23 @@ module ActiveRecord # Raises: # - PoolFullError: no connection can be obtained from the pool. def checkout - # Checkout an available connection - synchronize do - # Try to find a connection that hasn't been leased, and lease it - conn = connections.find { |c| c.lease } - - # If all connections were leased, and we have room to expand, - # create a new connection and lease it. - if !conn && connections.size < size - conn = checkout_new_connection - conn.lease - end + loop do + # Checkout an available connection + synchronize do + # Try to find a connection that hasn't been leased, and lease it + conn = connections.find { |c| c.lease } + + # If all connections were leased, and we have room to expand, + # create a new connection and lease it. + if !conn && connections.size < size + conn = checkout_new_connection + conn.lease + end - if conn - checkout_and_verify conn - else - raise PoolFullError.new(size, timeout) + return checkout_and_verify(conn) if conn end + + Timeout.timeout(@timeout, PoolFullError) { @latch.await } end end @@ -238,6 +254,7 @@ module ActiveRecord release conn end + @latch.release end # Remove a connection from the connection pool. The connection will @@ -250,6 +267,7 @@ module ActiveRecord # from the reserved hash will be a little easier. release conn end + @latch.release end # Removes dead connections from the pool. A dead connection can occur @@ -262,17 +280,16 @@ module ActiveRecord remove conn if conn.in_use? && stale > conn.last_use && !conn.active? end end + @latch.release end private def release(conn) - thread_id = nil - - if @reserved_connections[current_connection_id] == conn - thread_id = current_connection_id + thread_id = if @reserved_connections[current_connection_id] == conn + current_connection_id else - thread_id = @reserved_connections.keys.find { |k| + @reserved_connections.keys.find { |k| @reserved_connections[k] == conn } end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index be712f2334..7b2961a04a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -59,7 +59,7 @@ module ActiveRecord # Executes insert +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. - def exec_insert(sql, name, binds) + def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) exec_query(sql, name, binds) end @@ -87,7 +87,7 @@ module ActiveRecord # passed in as +id_value+. def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds) - value = exec_insert(sql, name, binds) + value = exec_insert(sql, name, binds, pk, sequence_name) id_value || last_inserted_id(value) end 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 7ee8f40631..3546873550 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -65,11 +65,12 @@ module ActiveRecord class TableDefinition # An array of ColumnDefinition objects, representing the column changes # that have been defined. - attr_accessor :columns + attr_accessor :columns, :indexes def initialize(base) @columns = [] @columns_hash = {} + @indexes = {} @base = base end @@ -212,19 +213,22 @@ module ActiveRecord # # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of - # options, these will be used when creating the <tt>_type</tt> column. So what can be written like this: + # options, these will be used when creating the <tt>_type</tt> column. The <tt>:index</tt> option + # will also create an index, similar to calling <tt>add_index</tt>. So what can be written like this: # # create_table :taggings do |t| # t.integer :tag_id, :tagger_id, :taggable_id # t.string :tagger_type # t.string :taggable_type, :default => 'Photo' # end + # add_index :taggings, :tag_id, :name => 'index_taggings_on_tag_id' + # add_index :taggings, [:tagger_id, :tagger_type] # # Can also be written as follows using references: # # create_table :taggings do |t| - # t.references :tag - # t.references :tagger, :polymorphic => true + # t.references :tag, :index => { :name => 'index_taggings_on_tag_id' } + # t.references :tagger, :polymorphic => true, :index => true # t.references :taggable, :polymorphic => { :default => 'Photo' } # end def column(name, type, options = {}) @@ -255,6 +259,14 @@ module ActiveRecord end # end EOV end + + # Adds index options to the indexes hash, keyed by column name + # This is primarily used to track indexes that need to be created after the table + # === Examples + # index(:account_id, :name => 'index_projects_on_account_id') + def index(column_name, options = {}) + indexes[column_name] = options + end # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and # <tt>:updated_at</tt> to the table. @@ -267,9 +279,11 @@ module ActiveRecord def references(*args) options = args.extract_options! polymorphic = options.delete(:polymorphic) + index_options = options.delete(:index) args.each do |col| column("#{col}_id", :integer, options) column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil? + index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options end end alias :belongs_to :references @@ -435,9 +449,11 @@ module ActiveRecord def references(*args) options = args.extract_options! polymorphic = options.delete(:polymorphic) + index_options = options.delete(:index) args.each do |col| @base.add_column(@table_name, "#{col}_id", :integer, options) @base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil? + @base.add_index(@table_name, polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options end end alias :belongs_to :references diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 0784b2d11a..30a4f9aa35 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -171,6 +171,7 @@ module ActiveRecord create_sql << td.to_sql create_sql << ") #{options[:options]}" execute create_sql + td.indexes.each_pair { |c,o| add_index table_name, c, o } end # Creates a new join table with the name created using the lexical order of the first two @@ -375,7 +376,7 @@ module ActiveRecord # Note: SQLite doesn't support index length # # ====== Creating an index with a sort order (desc or asc, asc is the default) - # add_index(:accounts, [:branch_id, :party_id, :surname], :order => {:branch_id => :desc, :part_id => :asc}) + # add_index(:accounts, [:branch_id, :party_id, :surname], :order => {:branch_id => :desc, :party_id => :asc}) # generates # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname) # diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 1d713e472b..c6faae77cc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -163,11 +163,6 @@ module ActiveRecord # QUOTING ================================================== - # Override to return the quoted table name. Defaults to column quoting. - def quote_table_name(name) - quote_column_name(name) - end - # Returns a bind substitution value given a +column+ and list of current # +binds+ def substitute_at(column, index) @@ -299,7 +294,7 @@ module ActiveRecord raise exception end - def translate_exception(e, message) + def translate_exception(exception, message) # override in derived class ActiveRecord::StatementInvalid.new(message) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index d0ea468430..a848838a4e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -516,7 +516,7 @@ module ActiveRecord def pk_and_sequence_for(table) execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result| create_table = each_hash(result).first[:"Create Table"] - if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/ + if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/ keys = $1.split(",").map { |key| key.delete('`"') } keys.length == 1 ? [keys.first, nil] : nil else diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 3f45f23de8..92908d9599 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -226,7 +226,7 @@ module ActiveRecord end alias :create :insert_sql - def exec_insert(sql, name, binds) + def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) execute to_sql(sql, binds), name end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 10a178e369..273c165084 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -17,7 +17,7 @@ module ActiveRecord # Forward any unused config params to PGconn.connect. [:statement_limit, :encoding, :min_messages, :schema_search_path, :schema_order, :adapter, :pool, :wait_timeout, :template, - :reaping_frequency].each do |key| + :reaping_frequency, :insert_returning].each do |key| conn_params.delete key end conn_params.delete_if { |k,v| v.nil? } @@ -88,9 +88,8 @@ module ActiveRecord def escape_hstore(value) value.nil? ? 'NULL' - : value =~ /[=\s,>]/ ? '"%s"' % value.gsub(/(["\\])/, '\\\\\1') : value == "" ? '""' - : value.to_s.gsub(/(["\\])/, '\\\\\1') + : '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1') end end # :startdoc: @@ -259,6 +258,8 @@ module ActiveRecord # <encoding></tt> call on the connection. # * <tt>:min_messages</tt> - An optional client min messages that is used in a # <tt>SET client_min_messages TO <min_messages></tt> call on the connection. + # * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT<tt> statements + # defaults to true. # # Any further options are used as connection parameters to libpq. See # http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the @@ -406,6 +407,7 @@ module ActiveRecord initialize_type_map @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] + @use_insert_returning = @config.key?(:insert_returning) ? @config[:insert_returning] : true end # Clears the prepared statements cache. @@ -667,8 +669,11 @@ module ActiveRecord pk = primary_key(table_ref) if table_ref end - if pk + if pk && use_insert_returning? select_value("#{sql} RETURNING #{quote_column_name(pk)}") + elsif pk + super + last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk)) else super end @@ -783,11 +788,27 @@ module ActiveRecord pk = primary_key(table_ref) if table_ref end - sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk + if pk && use_insert_returning? + sql = "#{sql} RETURNING #{quote_column_name(pk)}" + end [sql, binds] end + def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) + val = exec_query(sql, name, binds) + if !use_insert_returning? && pk + unless sequence_name + table_ref = extract_table_ref_from_insert_sql(sql) + sequence_name = default_sequence_name(table_ref, pk) + return val unless sequence_name + end + last_insert_id_result(sequence_name) + else + val + end + end + # Executes an UPDATE query and returns the number of affected tuples. def update_sql(sql, name = nil) super.cmd_tuples @@ -1028,7 +1049,9 @@ module ActiveRecord # Returns the sequence name for a table's primary key or some other specified key. def default_sequence_name(table_name, pk = nil) #:nodoc: - serial_sequence(table_name, pk || 'id').split('.').last + result = serial_sequence(table_name, pk || 'id') + return nil unless result + result.split('.').last rescue ActiveRecord::StatementInvalid "#{table_name}_#{pk || 'id'}_seq" end @@ -1236,6 +1259,10 @@ module ActiveRecord end end + def use_insert_returning? + @use_insert_returning + end + protected # Returns the version of the connected PostgreSQL server. def postgresql_version @@ -1365,8 +1392,15 @@ module ActiveRecord # Returns the current ID of a table's sequence. def last_insert_id(sequence_name) #:nodoc: - r = exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]]) - Integer(r.rows.first.first) + Integer(last_insert_id_value(sequence_name)) + end + + def last_insert_id_value(sequence_name) + last_insert_id_result(sequence_name).rows.first.first + end + + def last_insert_id_result(sequence_name) #:nodoc: + exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]]) end # Executes a SELECT query and returns the results, performing any data type diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index ee5d10859c..83f75e3505 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -1,6 +1,8 @@ -require 'active_record/connection_adapters/sqlite_adapter' +require 'active_record/connection_adapters/abstract_adapter' +require 'active_record/connection_adapters/statement_pool' +require 'arel/visitors/bind_visitor' -gem 'sqlite3', '~> 1.3.5' +gem 'sqlite3', '~> 1.3.6' require 'sqlite3' module ActiveRecord @@ -35,7 +37,184 @@ module ActiveRecord end module ConnectionAdapters #:nodoc: - class SQLite3Adapter < SQLiteAdapter # :nodoc: + class SQLite3Column < Column #:nodoc: + class << self + def binary_to_string(value) + if value.encoding != Encoding::ASCII_8BIT + value = value.force_encoding(Encoding::ASCII_8BIT) + end + value + end + end + end + + # The SQLite3 adapter works SQLite 3.6.16 or newer + # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3). + # + # Options: + # + # * <tt>:database</tt> - Path to the database file. + class SQLite3Adapter < AbstractAdapter + class Version + include Comparable + + def initialize(version_string) + @version = version_string.split('.').map { |v| v.to_i } + end + + def <=>(version_string) + @version <=> version_string.split('.').map { |v| v.to_i } + end + end + + class StatementPool < ConnectionAdapters::StatementPool + def initialize(connection, max) + super + @cache = Hash.new { |h,pid| h[pid] = {} } + end + + def each(&block); cache.each(&block); end + def key?(key); cache.key?(key); end + def [](key); cache[key]; end + def length; cache.length; end + + def []=(sql, key) + while @max <= cache.size + dealloc(cache.shift.last[:stmt]) + end + cache[sql] = key + end + + def clear + cache.values.each do |hash| + dealloc hash[:stmt] + end + cache.clear + end + + private + def cache + @cache[$$] + end + + def dealloc(stmt) + stmt.close unless stmt.closed? + end + end + + class BindSubstitution < Arel::Visitors::SQLite # :nodoc: + include Arel::Visitors::BindVisitor + end + + def initialize(connection, logger, config) + super(connection, logger) + @statements = StatementPool.new(@connection, + config.fetch(:statement_limit) { 1000 }) + @config = config + + if config.fetch(:prepared_statements) { true } + @visitor = Arel::Visitors::SQLite.new self + else + @visitor = BindSubstitution.new self + end + end + + def adapter_name #:nodoc: + 'SQLite' + end + + # Returns true + def supports_ddl_transactions? + true + end + + # Returns true if SQLite version is '3.6.8' or greater, false otherwise. + def supports_savepoints? + sqlite_version >= '3.6.8' + end + + # Returns true, since this connection adapter supports prepared statement + # caching. + def supports_statement_cache? + true + end + + # Returns true, since this connection adapter supports migrations. + def supports_migrations? #:nodoc: + true + end + + # Returns true. + def supports_primary_key? #:nodoc: + true + end + + def requires_reloading? + true + end + + # Returns true + def supports_add_column? + true + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + super + clear_cache! + @connection.close rescue nil + end + + # Clears the prepared statements cache. + def clear_cache! + @statements.clear + end + + # Returns true + def supports_count_distinct? #:nodoc: + true + end + + # Returns true + def supports_autoincrement? #:nodoc: + true + end + + def supports_index_sort_order? + true + end + + def native_database_types #:nodoc: + { + :primary_key => default_primary_key_type, + :string => { :name => "varchar", :limit => 255 }, + :text => { :name => "text" }, + :integer => { :name => "integer" }, + :float => { :name => "float" }, + :decimal => { :name => "decimal" }, + :datetime => { :name => "datetime" }, + :timestamp => { :name => "datetime" }, + :time => { :name => "time" }, + :date => { :name => "date" }, + :binary => { :name => "blob" }, + :boolean => { :name => "boolean" } + } + end + + # Returns the current database encoding format as a string, eg: 'UTF-8' + def encoding + @connection.encoding.to_s + end + + # Returns true. + def supports_explain? + true + end + + + # QUOTING ================================================== + def quote(value, column = nil) if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) s = column.class.string_to_binary(value).unpack("H*")[0] @@ -45,10 +224,387 @@ module ActiveRecord end end - # Returns the current database encoding format as a string, eg: 'UTF-8' - def encoding - @connection.encoding.to_s + + def quote_string(s) #:nodoc: + @connection.class.quote(s) + end + + def quote_column_name(name) #:nodoc: + %Q("#{name.to_s.gsub('"', '""')}") + end + + # Quote date/time values for use in SQL input. Includes microseconds + # if the value is a Time responding to usec. + def quoted_date(value) #:nodoc: + if value.respond_to?(:usec) + "#{super}.#{sprintf("%06d", value.usec)}" + else + super + end + end + + def type_cast(value, column) # :nodoc: + return value.to_f if BigDecimal === value + return super unless String === value + return super unless column && value + + value = super + if column.type == :string && value.encoding == Encoding::ASCII_8BIT + logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger + value.encode! 'utf-8' + end + value + end + + # DATABASE STATEMENTS ====================================== + + def explain(arel, binds = []) + sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" + ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds)) + end + + class ExplainPrettyPrinter + # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles + # the output of the SQLite shell: + # + # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) + # 0|1|1|SCAN TABLE posts (~100000 rows) + # + def pp(result) # :nodoc: + result.rows.map do |row| + row.join('|') + end.join("\n") + "\n" + end + end + + def exec_query(sql, name = nil, binds = []) + log(sql, name, binds) do + + # Don't cache statements without bind values + if binds.empty? + stmt = @connection.prepare(sql) + cols = stmt.columns + records = stmt.to_a + stmt.close + stmt = records + else + cache = @statements[sql] ||= { + :stmt => @connection.prepare(sql) + } + stmt = cache[:stmt] + cols = cache[:cols] ||= stmt.columns + stmt.reset! + stmt.bind_params binds.map { |col, val| + type_cast(val, col) + } + end + + ActiveRecord::Result.new(cols, stmt.to_a) + end + end + + def exec_delete(sql, name = 'SQL', binds = []) + exec_query(sql, name, binds) + @connection.changes end + alias :exec_update :exec_delete + + def last_inserted_id(result) + @connection.last_insert_row_id + end + + def execute(sql, name = nil) #:nodoc: + log(sql, name) { @connection.execute(sql) } + end + + def update_sql(sql, name = nil) #:nodoc: + super + @connection.changes + end + + def delete_sql(sql, name = nil) #:nodoc: + sql += " WHERE 1=1" unless sql =~ /WHERE/i + super sql, name + end + + def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: + super + id_value || @connection.last_insert_row_id + end + alias :create :insert_sql + + def select_rows(sql, name = nil) + exec_query(sql, name).rows + end + + def create_savepoint + execute("SAVEPOINT #{current_savepoint_name}") + end + + def rollback_to_savepoint + execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}") + end + + def release_savepoint + execute("RELEASE SAVEPOINT #{current_savepoint_name}") + end + + def begin_db_transaction #:nodoc: + log('begin transaction',nil) { @connection.transaction } + end + + def commit_db_transaction #:nodoc: + log('commit transaction',nil) { @connection.commit } + end + + def rollback_db_transaction #:nodoc: + log('rollback transaction',nil) { @connection.rollback } + end + + # SCHEMA STATEMENTS ======================================== + + def tables(name = 'SCHEMA', table_name = nil) #:nodoc: + sql = <<-SQL + SELECT name + FROM sqlite_master + WHERE type = 'table' AND NOT name = 'sqlite_sequence' + SQL + sql << " AND name = #{quote_table_name(table_name)}" if table_name + + exec_query(sql, name).map do |row| + row['name'] + end + end + + def table_exists?(name) + name && tables('SCHEMA', name).any? + end + + # Returns an array of +SQLite3Column+ objects for the table specified by +table_name+. + def columns(table_name) #:nodoc: + table_structure(table_name).map do |field| + case field["dflt_value"] + when /^null$/i + field["dflt_value"] = nil + when /^'(.*)'$/ + field["dflt_value"] = $1.gsub("''", "'") + when /^"(.*)"$/ + field["dflt_value"] = $1.gsub('""', '"') + end + + SQLite3Column.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0) + end + end + + # Returns an array of indexes for the given table. + def indexes(table_name, name = nil) #:nodoc: + exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row| + IndexDefinition.new( + table_name, + row['name'], + row['unique'] != 0, + exec_query("PRAGMA index_info('#{row['name']}')").map { |col| + col['name'] + }) + end + end + + def primary_key(table_name) #:nodoc: + column = table_structure(table_name).find { |field| + field['pk'] == 1 + } + column && column['name'] + end + + def remove_index!(table_name, index_name) #:nodoc: + exec_query "DROP INDEX #{quote_column_name(index_name)}" + end + + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') + def rename_table(name, new_name) + exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" + end + + # See: http://www.sqlite.org/lang_altertable.html + # SQLite has an additional restriction on the ALTER TABLE statement + def valid_alter_table_options( type, options) + type.to_sym != :primary_key + end + + def add_column(table_name, column_name, type, options = {}) #:nodoc: + if supports_add_column? && valid_alter_table_options( type, options ) + super(table_name, column_name, type, options) + else + alter_table(table_name) do |definition| + definition.column(column_name, type, options) + end + end + end + + def remove_column(table_name, *column_names) #:nodoc: + raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty? + column_names.flatten.each do |column_name| + alter_table(table_name) do |definition| + definition.columns.delete(definition[column_name]) + end + end + end + alias :remove_columns :remove_column + + def change_column_default(table_name, column_name, default) #:nodoc: + alter_table(table_name) do |definition| + definition[column_name].default = default + end + end + + def change_column_null(table_name, column_name, null, default = nil) + unless null || default.nil? + exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + end + alter_table(table_name) do |definition| + definition[column_name].null = null + end + end + + def change_column(table_name, column_name, type, options = {}) #:nodoc: + alter_table(table_name) do |definition| + include_default = options_include_default?(options) + definition[column_name].instance_eval do + self.type = type + self.limit = options[:limit] if options.include?(:limit) + self.default = options[:default] if include_default + self.null = options[:null] if options.include?(:null) + self.precision = options[:precision] if options.include?(:precision) + self.scale = options[:scale] if options.include?(:scale) + end + end + end + + def rename_column(table_name, column_name, new_column_name) #:nodoc: + unless columns(table_name).detect{|c| c.name == column_name.to_s } + raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}" + end + alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s}) + end + + def empty_insert_statement_value + "VALUES(NULL)" + end + + protected + def select(sql, name = nil, binds = []) #:nodoc: + exec_query(sql, name, binds) + end + + def table_structure(table_name) + structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash + raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? + structure + end + + def alter_table(table_name, options = {}) #:nodoc: + altered_table_name = "altered_#{table_name}" + caller = lambda {|definition| yield definition if block_given?} + + transaction do + move_table(table_name, altered_table_name, + options.merge(:temporary => true)) + move_table(altered_table_name, table_name, &caller) + end + end + + def move_table(from, to, options = {}, &block) #:nodoc: + copy_table(from, to, options, &block) + drop_table(from) + end + + def copy_table(from, to, options = {}) #:nodoc: + options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s)) + create_table(to, options) do |definition| + @definition = definition + columns(from).each do |column| + column_name = options[:rename] ? + (options[:rename][column.name] || + options[:rename][column.name.to_sym] || + column.name) : column.name + + @definition.column(column_name, column.type, + :limit => column.limit, :default => column.default, + :precision => column.precision, :scale => column.scale, + :null => column.null) + end + @definition.primary_key(primary_key(from)) if primary_key(from) + yield @definition if block_given? + end + + copy_table_indexes(from, to, options[:rename] || {}) + copy_table_contents(from, to, + @definition.columns.map {|column| column.name}, + options[:rename] || {}) + end + + def copy_table_indexes(from, to, rename = {}) #:nodoc: + indexes(from).each do |index| + name = index.name + if to == "altered_#{from}" + name = "temp_#{name}" + elsif from == "altered_#{to}" + name = name[5..-1] + end + + to_column_names = columns(to).map { |c| c.name } + columns = index.columns.map {|c| rename[c] || c }.select do |column| + to_column_names.include?(column) + end + + unless columns.empty? + # index name can't be the same + opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") } + opts[:unique] = true if index.unique + add_index(to, columns, opts) + end + end + end + + def copy_table_contents(from, to, columns, rename = {}) #:nodoc: + column_mappings = Hash[columns.map {|name| [name, name]}] + rename.each { |a| column_mappings[a.last] = a.first } + from_columns = columns(from).collect {|col| col.name} + columns = columns.find_all{|col| from_columns.include?(column_mappings[col])} + quoted_columns = columns.map { |col| quote_column_name(col) } * ',' + + quoted_to = quote_table_name(to) + exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row| + sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES (" + sql << columns.map {|col| quote row[column_mappings[col]]} * ', ' + sql << ')' + exec_query sql + end + end + + def sqlite_version + @sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)')) + end + + def default_primary_key_type + if supports_autoincrement? + 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL' + else + 'INTEGER PRIMARY KEY NOT NULL' + end + end + + def translate_exception(exception, message) + case exception.message + when /column(s)? .* (is|are) not unique/ + RecordNotUnique.new(message, exception) + else + super + end + end end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb deleted file mode 100644 index 91e1482ffd..0000000000 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ /dev/null @@ -1,563 +0,0 @@ -require 'active_record/connection_adapters/abstract_adapter' -require 'active_record/connection_adapters/statement_pool' -require 'arel/visitors/bind_visitor' - -module ActiveRecord - module ConnectionAdapters #:nodoc: - class SQLiteColumn < Column #:nodoc: - class << self - def binary_to_string(value) - if value.encoding != Encoding::ASCII_8BIT - value = value.force_encoding(Encoding::ASCII_8BIT) - end - value - end - end - end - - # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby - # drivers (available both as gems and from http://rubyforge.org/projects/sqlite-ruby/). - # - # Options: - # - # * <tt>:database</tt> - Path to the database file. - class SQLiteAdapter < AbstractAdapter - class Version - include Comparable - - def initialize(version_string) - @version = version_string.split('.').map { |v| v.to_i } - end - - def <=>(version_string) - @version <=> version_string.split('.').map { |v| v.to_i } - end - end - - class StatementPool < ConnectionAdapters::StatementPool - def initialize(connection, max) - super - @cache = Hash.new { |h,pid| h[pid] = {} } - end - - def each(&block); cache.each(&block); end - def key?(key); cache.key?(key); end - def [](key); cache[key]; end - def length; cache.length; end - - def []=(sql, key) - while @max <= cache.size - dealloc(cache.shift.last[:stmt]) - end - cache[sql] = key - end - - def clear - cache.values.each do |hash| - dealloc hash[:stmt] - end - cache.clear - end - - private - def cache - @cache[$$] - end - - def dealloc(stmt) - stmt.close unless stmt.closed? - end - end - - class BindSubstitution < Arel::Visitors::SQLite # :nodoc: - include Arel::Visitors::BindVisitor - end - - def initialize(connection, logger, config) - super(connection, logger) - @statements = StatementPool.new(@connection, - config.fetch(:statement_limit) { 1000 }) - @config = config - - if config.fetch(:prepared_statements) { true } - @visitor = Arel::Visitors::SQLite.new self - else - @visitor = BindSubstitution.new self - end - end - - def adapter_name #:nodoc: - 'SQLite' - end - - # Returns true if SQLite version is '2.0.0' or greater, false otherwise. - def supports_ddl_transactions? - sqlite_version >= '2.0.0' - end - - # Returns true if SQLite version is '3.6.8' or greater, false otherwise. - def supports_savepoints? - sqlite_version >= '3.6.8' - end - - # Returns true, since this connection adapter supports prepared statement - # caching. - def supports_statement_cache? - true - end - - # Returns true, since this connection adapter supports migrations. - def supports_migrations? #:nodoc: - true - end - - # Returns true. - def supports_primary_key? #:nodoc: - true - end - - # Returns true. - def supports_explain? - true - end - - def requires_reloading? - true - end - - # Returns true if SQLite version is '3.1.6' or greater, false otherwise. - def supports_add_column? - sqlite_version >= '3.1.6' - end - - # Disconnects from the database if already connected. Otherwise, this - # method does nothing. - def disconnect! - super - clear_cache! - @connection.close rescue nil - end - - # Clears the prepared statements cache. - def clear_cache! - @statements.clear - end - - # Returns true if SQLite version is '3.2.6' or greater, false otherwise. - def supports_count_distinct? #:nodoc: - sqlite_version >= '3.2.6' - end - - # Returns true if SQLite version is '3.1.0' or greater, false otherwise. - def supports_autoincrement? #:nodoc: - sqlite_version >= '3.1.0' - end - - def supports_index_sort_order? - sqlite_version >= '3.3.0' - end - - def native_database_types #:nodoc: - { - :primary_key => default_primary_key_type, - :string => { :name => "varchar", :limit => 255 }, - :text => { :name => "text" }, - :integer => { :name => "integer" }, - :float => { :name => "float" }, - :decimal => { :name => "decimal" }, - :datetime => { :name => "datetime" }, - :timestamp => { :name => "datetime" }, - :time => { :name => "time" }, - :date => { :name => "date" }, - :binary => { :name => "blob" }, - :boolean => { :name => "boolean" } - } - end - - - # QUOTING ================================================== - - def quote_string(s) #:nodoc: - @connection.class.quote(s) - end - - def quote_column_name(name) #:nodoc: - %Q("#{name.to_s.gsub('"', '""')}") - end - - # Quote date/time values for use in SQL input. Includes microseconds - # if the value is a Time responding to usec. - def quoted_date(value) #:nodoc: - if value.respond_to?(:usec) - "#{super}.#{sprintf("%06d", value.usec)}" - else - super - end - end - - def type_cast(value, column) # :nodoc: - return value.to_f if BigDecimal === value - return super unless String === value - return super unless column && value - - value = super - if column.type == :string && value.encoding == Encoding::ASCII_8BIT - logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger - value.encode! 'utf-8' - end - value - end - - # DATABASE STATEMENTS ====================================== - - def explain(arel, binds = []) - sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" - ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds)) - end - - class ExplainPrettyPrinter - # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles - # the output of the SQLite shell: - # - # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) - # 0|1|1|SCAN TABLE posts (~100000 rows) - # - def pp(result) # :nodoc: - result.rows.map do |row| - row.join('|') - end.join("\n") + "\n" - end - end - - def exec_query(sql, name = nil, binds = []) - log(sql, name, binds) do - - # Don't cache statements without bind values - if binds.empty? - stmt = @connection.prepare(sql) - cols = stmt.columns - records = stmt.to_a - stmt.close - stmt = records - else - cache = @statements[sql] ||= { - :stmt => @connection.prepare(sql) - } - stmt = cache[:stmt] - cols = cache[:cols] ||= stmt.columns - stmt.reset! - stmt.bind_params binds.map { |col, val| - type_cast(val, col) - } - end - - ActiveRecord::Result.new(cols, stmt.to_a) - end - end - - def exec_delete(sql, name = 'SQL', binds = []) - exec_query(sql, name, binds) - @connection.changes - end - alias :exec_update :exec_delete - - def last_inserted_id(result) - @connection.last_insert_row_id - end - - def execute(sql, name = nil) #:nodoc: - log(sql, name) { @connection.execute(sql) } - end - - def update_sql(sql, name = nil) #:nodoc: - super - @connection.changes - end - - def delete_sql(sql, name = nil) #:nodoc: - sql += " WHERE 1=1" unless sql =~ /WHERE/i - super sql, name - end - - def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: - super - id_value || @connection.last_insert_row_id - end - alias :create :insert_sql - - def select_rows(sql, name = nil) - exec_query(sql, name).rows - end - - def create_savepoint - execute("SAVEPOINT #{current_savepoint_name}") - end - - def rollback_to_savepoint - execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}") - end - - def release_savepoint - execute("RELEASE SAVEPOINT #{current_savepoint_name}") - end - - def begin_db_transaction #:nodoc: - log('begin transaction',nil) { @connection.transaction } - end - - def commit_db_transaction #:nodoc: - log('commit transaction',nil) { @connection.commit } - end - - def rollback_db_transaction #:nodoc: - log('rollback transaction',nil) { @connection.rollback } - end - - # SCHEMA STATEMENTS ======================================== - - def tables(name = 'SCHEMA', table_name = nil) #:nodoc: - sql = <<-SQL - SELECT name - FROM sqlite_master - WHERE type = 'table' AND NOT name = 'sqlite_sequence' - SQL - sql << " AND name = #{quote_table_name(table_name)}" if table_name - - exec_query(sql, name).map do |row| - row['name'] - end - end - - def table_exists?(name) - name && tables('SCHEMA', name).any? - end - - # Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+. - def columns(table_name) #:nodoc: - table_structure(table_name).map do |field| - case field["dflt_value"] - when /^null$/i - field["dflt_value"] = nil - when /^'(.*)'$/ - field["dflt_value"] = $1.gsub("''", "'") - when /^"(.*)"$/ - field["dflt_value"] = $1.gsub('""', '"') - end - - SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0) - end - end - - # Returns an array of indexes for the given table. - def indexes(table_name, name = nil) #:nodoc: - exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row| - IndexDefinition.new( - table_name, - row['name'], - row['unique'] != 0, - exec_query("PRAGMA index_info('#{row['name']}')").map { |col| - col['name'] - }) - end - end - - def primary_key(table_name) #:nodoc: - column = table_structure(table_name).find { |field| - field['pk'] == 1 - } - column && column['name'] - end - - def remove_index!(table_name, index_name) #:nodoc: - exec_query "DROP INDEX #{quote_column_name(index_name)}" - end - - # Renames a table. - # - # Example: - # rename_table('octopuses', 'octopi') - def rename_table(name, new_name) - exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" - end - - # See: http://www.sqlite.org/lang_altertable.html - # SQLite has an additional restriction on the ALTER TABLE statement - def valid_alter_table_options( type, options) - type.to_sym != :primary_key - end - - def add_column(table_name, column_name, type, options = {}) #:nodoc: - if supports_add_column? && valid_alter_table_options( type, options ) - super(table_name, column_name, type, options) - else - alter_table(table_name) do |definition| - definition.column(column_name, type, options) - end - end - end - - def remove_column(table_name, *column_names) #:nodoc: - raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty? - column_names.flatten.each do |column_name| - alter_table(table_name) do |definition| - definition.columns.delete(definition[column_name]) - end - end - end - alias :remove_columns :remove_column - - def change_column_default(table_name, column_name, default) #:nodoc: - alter_table(table_name) do |definition| - definition[column_name].default = default - end - end - - def change_column_null(table_name, column_name, null, default = nil) - unless null || default.nil? - exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") - end - alter_table(table_name) do |definition| - definition[column_name].null = null - end - end - - def change_column(table_name, column_name, type, options = {}) #:nodoc: - alter_table(table_name) do |definition| - include_default = options_include_default?(options) - definition[column_name].instance_eval do - self.type = type - self.limit = options[:limit] if options.include?(:limit) - self.default = options[:default] if include_default - self.null = options[:null] if options.include?(:null) - self.precision = options[:precision] if options.include?(:precision) - self.scale = options[:scale] if options.include?(:scale) - end - end - end - - def rename_column(table_name, column_name, new_column_name) #:nodoc: - unless columns(table_name).detect{|c| c.name == column_name.to_s } - raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}" - end - alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s}) - end - - def empty_insert_statement_value - "VALUES(NULL)" - end - - protected - def select(sql, name = nil, binds = []) #:nodoc: - exec_query(sql, name, binds) - end - - def table_structure(table_name) - structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash - raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? - structure - end - - def alter_table(table_name, options = {}) #:nodoc: - altered_table_name = "altered_#{table_name}" - caller = lambda {|definition| yield definition if block_given?} - - transaction do - move_table(table_name, altered_table_name, - options.merge(:temporary => true)) - move_table(altered_table_name, table_name, &caller) - end - end - - def move_table(from, to, options = {}, &block) #:nodoc: - copy_table(from, to, options, &block) - drop_table(from) - end - - def copy_table(from, to, options = {}) #:nodoc: - options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s)) - create_table(to, options) do |definition| - @definition = definition - columns(from).each do |column| - column_name = options[:rename] ? - (options[:rename][column.name] || - options[:rename][column.name.to_sym] || - column.name) : column.name - - @definition.column(column_name, column.type, - :limit => column.limit, :default => column.default, - :precision => column.precision, :scale => column.scale, - :null => column.null) - end - @definition.primary_key(primary_key(from)) if primary_key(from) - yield @definition if block_given? - end - - copy_table_indexes(from, to, options[:rename] || {}) - copy_table_contents(from, to, - @definition.columns.map {|column| column.name}, - options[:rename] || {}) - end - - def copy_table_indexes(from, to, rename = {}) #:nodoc: - indexes(from).each do |index| - name = index.name - if to == "altered_#{from}" - name = "temp_#{name}" - elsif from == "altered_#{to}" - name = name[5..-1] - end - - to_column_names = columns(to).map { |c| c.name } - columns = index.columns.map {|c| rename[c] || c }.select do |column| - to_column_names.include?(column) - end - - unless columns.empty? - # index name can't be the same - opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") } - opts[:unique] = true if index.unique - add_index(to, columns, opts) - end - end - end - - def copy_table_contents(from, to, columns, rename = {}) #:nodoc: - column_mappings = Hash[columns.map {|name| [name, name]}] - rename.each { |a| column_mappings[a.last] = a.first } - from_columns = columns(from).collect {|col| col.name} - columns = columns.find_all{|col| from_columns.include?(column_mappings[col])} - quoted_columns = columns.map { |col| quote_column_name(col) } * ',' - - quoted_to = quote_table_name(to) - exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row| - sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES (" - sql << columns.map {|col| quote row[column_mappings[col]]} * ', ' - sql << ')' - exec_query sql - end - end - - def sqlite_version - @sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)')) - end - - def default_primary_key_type - if supports_autoincrement? - 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL' - else - 'INTEGER PRIMARY KEY NOT NULL' - end - end - - def translate_exception(exception, message) - case exception.message - when /column(s)? .* (is|are) not unique/ - RecordNotUnique.new(message, exception) - else - super - end - end - - end - end -end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index f52979ebd9..b163ef3c12 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -69,7 +69,7 @@ module ActiveRecord "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}" end - update_all(updates.join(', '), primary_key => id) + where(primary_key => id).update_all updates.join(', ') end # Increment a number field by one, usually representing a count. diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index bb504ae90f..4a987c2343 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -178,7 +178,7 @@ module ActiveRecord verify_readonly_attribute(name) raise ActiveRecordError, "can not update on a new record object" unless persisted? raw_write_attribute(name, value) - self.class.update_all({ name => value }, self.class.primary_key => id) == 1 + self.class.where(self.class.primary_key => id).update_all(name => value) == 1 end # Updates the attributes of the model from the passed-in hash and saves the @@ -268,7 +268,13 @@ module ActiveRecord clear_aggregation_cache clear_association_cache - fresh_object = self.class.unscoped { self.class.find(id, options) } + fresh_object = + if options && options[:lock] + self.class.unscoped { self.class.lock.find(id) } + else + self.class.unscoped { self.class.find(id) } + end + @attributes.update(fresh_object.instance_variable_get('@attributes')) @columns_hash = fresh_object.instance_variable_get('@columns_hash') @@ -313,7 +319,7 @@ module ActiveRecord @changed_attributes.except!(*changes.keys) primary_key = self.class.primary_key - self.class.unscoped.update_all(changes, { primary_key => self[primary_key] }) == 1 + self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1 end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 0dbaab306f..333d31d8a3 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -236,7 +236,10 @@ module ActiveRecord # Please check unscoped if you want to remove all previous scopes (including # the default_scope) during the execution of a block. def scoping - @klass.with_scope(self, :overwrite) { yield } + previous, klass.current_scope = klass.current_scope, self + yield + ensure + klass.current_scope = previous end # Updates all records with details given if they match a set of conditions supplied, limits and order can diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 1c2a06328f..3f880ce5e9 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -3,32 +3,41 @@ require 'active_support/core_ext/hash/keys' module ActiveRecord class Relation - class Merger - attr_reader :relation, :other + class HashMerger + attr_reader :relation, :hash - def initialize(relation, other) - @relation = relation + def initialize(relation, hash) + hash.assert_valid_keys(*Relation::VALUE_METHODS) - if other.default_scoped? && other.klass != relation.klass - @other = other.with_default_scope - else - @other = other - end + @relation = relation + @hash = hash end def merge - HashMerger.new(relation, other.values).merge + Merger.new(relation, other).merge + end + + # Applying values to a relation has some side effects. E.g. + # interpolation might take place for where values. So we should + # build a relation to merge in rather than directly merging + # the values. + def other + other = Relation.new(relation.klass, relation.table) + hash.each { |k, v| other.send("#{k}!", v) } + other end end - class HashMerger + class Merger attr_reader :relation, :values - def initialize(relation, values) - values.assert_valid_keys(*Relation::VALUE_METHODS) + def initialize(relation, other) + if other.default_scoped? && other.klass != relation.klass + other = other.with_default_scope + end @relation = relation - @values = values + @values = other.values end def normal_values diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 7bf9c16959..41e55dfd0e 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -5,6 +5,20 @@ require 'active_record/relation/merger' module ActiveRecord module SpawnMethods + + # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>. + # Returns an array representing the union of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array. + # + # ==== Examples + # + # Post.where(:published => true).joins(:comments).merge( Comment.where(:spam => false) ) + # # Performs a single join query with both where conditions. + # + # recent_posts = Post.order('created_at DESC').first(5) + # Post.where(:published => true).merge(recent_posts) + # # Returns the union of all published posts with the 5 most recently created posts. + # # (This is just an example. You'd probably want to do this with a single query!) + # def merge(other) if other.is_a?(Array) to_a & other @@ -16,11 +30,8 @@ module ActiveRecord end def merge!(other) - if other.is_a?(Hash) - Relation::HashMerger.new(self, other).merge - else - Relation::Merger.new(self, other).merge - end + klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger + klass.new(self, other).merge end # Removes from the query the condition(s) specified in +skips+. diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index a8f5e96190..66a486ae0a 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -10,118 +10,6 @@ module ActiveRecord end module ClassMethods - # with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be - # <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while - # <tt>:create</tt> parameters are an attributes hash. - # - # class Article < ActiveRecord::Base - # def self.create_with_scope - # with_scope(:find => where(:blog_id => 1), :create => { :blog_id => 1 }) do - # find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1 - # a = create(1) - # a.blog_id # => 1 - # end - # end - # end - # - # In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of - # <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged. - # - # <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing - # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the - # array of strings format for your joins. - # - # class Article < ActiveRecord::Base - # def self.find_with_scope - # with_scope(:find => where(:blog_id => 1).limit(1), :create => { :blog_id => 1 }) do - # with_scope(:find => limit(10)) do - # all # => SELECT * from articles WHERE blog_id = 1 LIMIT 10 - # end - # with_scope(:find => where(:author_id => 3)) do - # all # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1 - # end - # end - # end - # end - # - # You can ignore any previous scopings by using the <tt>with_exclusive_scope</tt> method. - # - # class Article < ActiveRecord::Base - # def self.find_with_exclusive_scope - # with_scope(:find => where(:blog_id => 1).limit(1)) do - # with_exclusive_scope(:find => limit(10)) do - # all # => SELECT * from articles LIMIT 10 - # end - # end - # end - # end - # - # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+. - def with_scope(scope = {}, action = :merge, &block) - # If another Active Record class has been passed in, get its current scope - scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope) - - previous_scope = self.current_scope - - if scope.is_a?(Hash) - # Dup first and second level of hash (method and params). - scope = scope.dup - scope.each do |method, params| - scope[method] = params.dup unless params == true - end - - scope.assert_valid_keys([ :find, :create ]) - relation = construct_finder_arel(scope[:find] || {}) - relation.default_scoped = true unless action == :overwrite - - if previous_scope && previous_scope.create_with_value && scope[:create] - scope_for_create = if action == :merge - previous_scope.create_with_value.merge(scope[:create]) - else - scope[:create] - end - - relation = relation.create_with(scope_for_create) - else - scope_for_create = scope[:create] - scope_for_create ||= previous_scope.create_with_value if previous_scope - relation = relation.create_with(scope_for_create) if scope_for_create - end - - scope = relation - end - - scope = previous_scope.merge(scope) if previous_scope && action == :merge - - self.current_scope = scope - begin - yield - ensure - self.current_scope = previous_scope - end - end - - protected - - # Works like with_scope, but discards any nested properties. - def with_exclusive_scope(method_scoping = {}, &block) - if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) } - raise ArgumentError, <<-MSG - New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope: - - User.unscoped.where(:active => true) - - Or call unscoped with a block: - - User.unscoped do - User.where(:active => true).all - end - - MSG - end - with_scope(method_scoping, :overwrite, &block) - end - def current_scope #:nodoc: Thread.current["#{self}_current_scope"] end @@ -129,15 +17,6 @@ module ActiveRecord def current_scope=(scope) #:nodoc: Thread.current["#{self}_current_scope"] = scope end - - private - - def construct_finder_arel(options = {}, scope = nil) - relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : options - relation = scope.merge(relation) if scope - relation - end - end def populate_with_current_scope_attributes diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 45c34005c3..db833fc7f1 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -87,7 +87,7 @@ module ActiveRecord # # Should return a scope, you can call 'super' here etc. # end # end - def default_scope(scope = {}) + def default_scope(scope = nil) scope = Proc.new if block_given? if scope.is_a?(Relation) || !scope.respond_to?(:call) @@ -103,14 +103,13 @@ module ActiveRecord end def build_default_scope #:nodoc: - if method(:default_scope).owner != ActiveRecord::Scoping::Default::ClassMethods + if !Base.is_a?(method(:default_scope).owner) + # The user has defined their own default scope method, so call that evaluate_default_scope { default_scope } elsif default_scopes.any? evaluate_default_scope do default_scopes.inject(relation) do |default_scope, scope| - if scope.is_a?(Hash) - default_scope.apply_finder_options(scope) - elsif !scope.is_a?(Relation) && scope.respond_to?(:call) + if !scope.is_a?(Relation) && scope.respond_to?(:call) default_scope.merge(unscoped { scope.call }) else default_scope.merge(scope) diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index b43e08157a..2af476c1ba 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -29,17 +29,16 @@ module ActiveRecord # You can define a \scope that applies to all finders using # ActiveRecord::Base.default_scope. def scoped(options = nil) - if options - scoped.apply_finder_options(options) + if current_scope + scope = current_scope.clone else - if current_scope - current_scope.clone - else - scope = relation - scope.default_scoped = true - scope - end + scope = relation + scope.default_scoped = true + scope end + + scope.merge!(options) if options + scope end ## @@ -172,7 +171,7 @@ module ActiveRecord # Article.published.featured.latest_article # Article.featured.titles - def scope(name, body = {}, &block) + def scope(name, body, &block) extension = Module.new(&block) if block # Check body.is_a?(Relation) to prevent the relation actually being @@ -189,9 +188,7 @@ module ActiveRecord end singleton_class.send(:define_method, name) do |*args| - options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body - options = scoped.apply_finder_options(options) if options.is_a?(Hash) - + options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body relation = scoped.merge(options) extension ? relation.extending(extension) : relation diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index ce43ae8066..ed47a26749 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -119,7 +119,7 @@ module ActiveRecord class << self; remove_possible_method :find_by_session_id; end def self.find_by_session_id(session_id) - find :first, :conditions => {:session_id=>session_id} + where(session_id: session_id).first end end end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index db618f617f..9e4b588ac2 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -196,7 +196,6 @@ module ActiveRecord # The following bundled adapters throw the ActiveRecord::RecordNotUnique exception: # * ActiveRecord::ConnectionAdapters::MysqlAdapter # * ActiveRecord::ConnectionAdapters::Mysql2Adapter - # * ActiveRecord::ConnectionAdapters::SQLiteAdapter # * ActiveRecord::ConnectionAdapters::SQLite3Adapter # * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter # diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb index f3bb70fb41..8e6ef20285 100644 --- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb +++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb @@ -14,6 +14,7 @@ module ActiveRecord def create_migration_file return unless options[:migration] && options[:parent].nil? + attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false migration_template "migration.rb", "db/migrate/create_#{table_name}.rb" end @@ -27,7 +28,7 @@ module ActiveRecord end def attributes_with_index - attributes.select { |a| a.has_index? || (a.reference? && options[:indexes]) } + attributes.select { |a| !a.reference? && a.has_index? } end def accessible_attributes diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 5aa4743e7b..475a292f85 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -56,6 +56,36 @@ module ActiveRecord end end + def test_pk_and_sequence_for + pk, seq = @conn.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @conn.default_sequence_name('ex', 'id'), seq + end + + def test_pk_and_sequence_for_with_non_standard_primary_key + @conn.exec_query('drop table if exists ex_with_non_standard_pk') + @conn.exec_query(<<-eosql) + CREATE TABLE `ex_with_non_standard_pk` ( + `code` INT(11) DEFAULT NULL auto_increment, + PRIMARY KEY (`code`)) + eosql + pk, seq = @conn.pk_and_sequence_for('ex_with_non_standard_pk') + assert_equal 'code', pk + assert_equal @conn.default_sequence_name('ex_with_non_standard_pk', 'code'), seq + end + + def test_pk_and_sequence_for_with_custom_index_type_pk + @conn.exec_query('drop table if exists ex_with_custom_index_type_pk') + @conn.exec_query(<<-eosql) + CREATE TABLE `ex_with_custom_index_type_pk` ( + `id` INT(11) DEFAULT NULL auto_increment, + PRIMARY KEY USING BTREE (`id`)) + eosql + pk, seq = @conn.pk_and_sequence_for('ex_with_custom_index_type_pk') + assert_equal 'id', pk + assert_equal @conn.default_sequence_name('ex_with_custom_index_type_pk', 'id'), seq + end + private def insert(ctx, data) binds = data.map { |name, value| diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index 292c7efebb..6faceaf7c0 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -130,7 +130,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase end def test_associations_work_with_reserved_words - assert_nothing_raised { Select.find(:all, :include => [:groups]) } + assert_nothing_raised { Select.scoped(:includes => [:groups]).all } end #the following functions were added to DRY test cases diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb index 29f885c6e7..d94bb629a7 100644 --- a/activerecord/test/cases/adapters/mysql/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/schema_test.rb @@ -20,7 +20,7 @@ module ActiveRecord end def test_schema - assert @omgpost.find(:first) + assert @omgpost.first end def test_primary_key diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb index cd9c1041dc..5e8065d80d 100644 --- a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb @@ -9,7 +9,7 @@ module ActiveRecord def test_update_question_marks str = "foo?bar" - x = Topic.find :first + x = Topic.first x.title = str x.content = str x.save! @@ -28,7 +28,7 @@ module ActiveRecord def test_update_null_bytes str = "foo\0bar" - x = Topic.find :first + x = Topic.first x.title = str x.content = str x.save! diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 3a9744e78f..32d4282623 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -130,7 +130,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase end def test_associations_work_with_reserved_words - assert_nothing_raised { Select.find(:all, :include => [:groups]) } + assert_nothing_raised { Select.scoped(:includes => [:groups]).all } end #the following functions were added to DRY test cases diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index de4d9c2b33..2c0ed73c92 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -20,7 +20,7 @@ module ActiveRecord end def test_schema - assert @omgpost.find(:first) + assert @omgpost.first end def test_primary_key diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 1644a58d92..23bafde17b 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 + require "cases/helper" require 'active_record/base' require 'active_record/connection_adapters/postgresql_adapter' @@ -88,7 +90,7 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase def test_rewrite @connection.execute "insert into hstores (tags) VALUES ('1=>2')" - x = Hstore.find :first + x = Hstore.first x.tags = { '"a\'' => 'b' } assert x.save! end @@ -96,13 +98,13 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase def test_select @connection.execute "insert into hstores (tags) VALUES ('1=>2')" - x = Hstore.find :first + x = Hstore.first assert_equal({'1' => '2'}, x.tags) end def test_select_multikey @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" - x = Hstore.find :first + x = Hstore.first assert_equal({'1' => '2', '2' => '3'}, x.tags) end @@ -134,13 +136,19 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase assert_cycle('a=>b' => 'bar', '1"foo' => '2') end + def test_quoting_special_characters + assert_cycle('ca' => 'cà', 'ac' => 'àc') + end + private def assert_cycle hash + # test creation x = Hstore.create!(:tags => hash) x.reload assert_equal(hash, x.tags) - # make sure updates work + # test updating + x = Hstore.create!(:tags => {}) x.tags = hash x.save! x.reload diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index a71d0bb848..92e31a3e44 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -49,6 +49,33 @@ module ActiveRecord assert_equal expect, id end + def test_insert_sql_with_returning_disabled + connection = connection_without_insert_returning + id = connection.insert_sql("insert into postgresql_partitioned_table_parent (number) VALUES (1)") + expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first + assert_equal expect, id + end + + def test_exec_insert_with_returning_disabled + connection = connection_without_insert_returning + result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id', 'postgresql_partitioned_table_parent_id_seq') + expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first + assert_equal expect, result.rows.first.first + end + + def test_exec_insert_with_returning_disabled_and_no_sequence_name_given + connection = connection_without_insert_returning + result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id') + expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first + assert_equal expect, result.rows.first.first + end + + def test_sql_for_insert_with_returning_disabled + connection = connection_without_insert_returning + result = connection.sql_for_insert('sql', nil, nil, nil, 'binds') + assert_equal ['sql', 'binds'], result + end + def test_serial_sequence assert_equal 'public.accounts_id_seq', @connection.serial_sequence('accounts', 'id') @@ -204,6 +231,10 @@ module ActiveRecord ctx.exec_insert(sql, 'SQL', binds) end + + def connection_without_insert_returning + ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false)) + end end end end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 1dbeb66af6..2ba9143cd5 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -5,7 +5,7 @@ require 'securerandom' module ActiveRecord module ConnectionAdapters - class SQLiteAdapter + class SQLite3Adapter class QuotingTest < ActiveRecord::TestCase def setup @conn = Base.sqlite3_connection :database => ':memory:', diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb index ae272e2c4b..2f04c60a9a 100644 --- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -1,7 +1,7 @@ require 'cases/helper' module ActiveRecord::ConnectionAdapters - class SQLiteAdapter + class SQLite3Adapter class StatementPoolTest < ActiveRecord::TestCase def test_cache_is_per_pid return skip('must support fork') unless Process.respond_to?(:fork) diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 9dd041af81..b351196fbd 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -73,14 +73,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_eager_loading_with_primary_key Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") - citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key) + citibank_result = Client.scoped(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key).first assert citibank_result.association_cache.key?(:firm_with_primary_key) end def test_eager_loading_with_primary_key_as_symbol Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") - citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key_symbols) + citibank_result = Client.scoped(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key_symbols).first assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols) end @@ -182,7 +182,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_with_select assert_equal Company.find(2).firm_with_select.attributes.size, 1 - assert_equal Company.find(2, :include => :firm_with_select ).firm_with_select.attributes.size, 1 + assert_equal Company.scoped(:includes => :firm_with_select ).find(2).firm_with_select.attributes.size, 1 end def test_belongs_to_counter @@ -334,7 +334,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_new_record_with_foreign_key_but_no_object c = Client.new("firm_id" => 1) # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id + assert_equal Firm.scoped(:order => "id").first, c.firm_with_basic_id end def test_setting_foreign_key_after_nil_target_loaded @@ -394,9 +394,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_association_assignment_sticks - post = Post.find(:first) + post = Post.first - author1, author2 = Author.find(:all, :limit => 2) + author1, author2 = Author.scoped(:limit => 2).all assert_not_nil author1 assert_not_nil author2 @@ -498,14 +498,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_nothing_raised do Account.find(@account.id).save! - Account.find(@account.id, :include => :firm).save! + Account.scoped(:includes => :firm).find(@account.id).save! end @account.firm.delete assert_nothing_raised do Account.find(@account.id).save! - Account.find(@account.id, :include => :firm).save! + Account.scoped(:includes => :firm).find(@account.id).save! end end @@ -705,4 +705,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal toy, sponsor.reload.sponsorable end + + test "stale tracking doesn't care about the type" do + apple = Firm.create("name" => "Apple") + citibank = Account.create("credit_limit" => 10) + + citibank.firm_id = apple.id + citibank.firm # load it + + citibank.firm_id = apple.id.to_s + + assert !citibank.association(:firm).stale_target? + end + + def test_reflect_the_most_recent_change + author1, author2 = Author.limit(2) + post = Post.new(:title => "foo", :body=> "bar") + + post.author = author1 + post.author_id = author2.id + + assert post.save + assert_equal post.author_id, author2.id + end end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 6733f3e889..01f7f18397 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -16,7 +16,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase :categorizations, :people, :categories, :edges, :vertices def test_eager_association_loading_with_cascaded_two_levels - authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id") + authors = Author.scoped(:includes=>{:posts=>:comments}, :order=>"authors.id").all assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -24,7 +24,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_and_one_level - authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id") + authors = Author.scoped(:includes=>[{:posts=>:comments}, :categorizations], :order=>"authors.id").all assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -84,7 +84,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations - authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id") + authors = Author.scoped(:includes=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id").all assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal 3, authors[1].posts.size @@ -92,7 +92,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference - authors = Author.find(:all, :include=>{:posts=>[:comments, :author]}, :order=>"authors.id") + authors = Author.scoped(:includes=>{:posts=>[:comments, :author]}, :order=>"authors.id").all assert_equal 3, authors.size assert_equal 5, authors[0].posts.size assert_equal authors(:david).name, authors[0].name @@ -100,13 +100,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_cascaded_two_levels_with_condition - authors = Author.find(:all, :include=>{:posts=>:comments}, :conditions=>"authors.id=1", :order=>"authors.id") + authors = Author.scoped(:includes=>{:posts=>:comments}, :where=>"authors.id=1", :order=>"authors.id").all assert_equal 1, authors.size assert_equal 5, authors[0].posts.size end def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong - firms = Firm.find(:all, :include=>{:account=>{:firm=>:account}}, :order=>"companies.id") + firms = Firm.scoped(:includes=>{:account=>{:firm=>:account}}, :order=>"companies.id").all assert_equal 2, firms.size assert_equal firms.first.account, firms.first.account.firm.account assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account } @@ -114,7 +114,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_has_many_sti - topics = Topic.find(:all, :include => :replies, :order => 'topics.id') + topics = Topic.scoped(:includes => :replies, :order => 'topics.id').all first, second, = topics(:first).replies.size, topics(:second).replies.size assert_no_queries do assert_equal first, topics[0].replies.size @@ -127,7 +127,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase silly.parent_id = 1 assert silly.save - topics = Topic.find(:all, :include => :replies, :order => ['topics.id', 'replies_topics.id']) + topics = Topic.scoped(:includes => :replies, :order => ['topics.id', 'replies_topics.id']).all assert_no_queries do assert_equal 2, topics[0].replies.size assert_equal 0, topics[1].replies.size @@ -135,14 +135,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_belongs_to_sti - replies = Reply.find(:all, :include => :topic, :order => 'topics.id') + replies = Reply.scoped(:includes => :topic, :order => 'topics.id').all assert replies.include?(topics(:second)) assert !replies.include?(topics(:first)) assert_equal topics(:first), assert_no_queries { replies.first.topic } end def test_eager_association_loading_with_multiple_stis_and_order - author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :conditions => 'posts.id = 4') + author = Author.scoped(:includes => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :where => 'posts.id = 4').first assert_equal authors(:david), author assert_no_queries do author.posts.first.special_comments @@ -151,7 +151,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_of_stis_with_multiple_references - authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4') + authors = Author.scoped(:includes => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :where => 'posts.id = 4').all assert_equal [authors(:david)], authors assert_no_queries do authors.first.posts.first.special_comments.first.post.special_comments @@ -160,7 +160,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_where_first_level_returns_nil - authors = Author.find(:all, :include => {:post_about_thinking => :comments}, :order => 'authors.id DESC') + authors = Author.scoped(:includes => {:post_about_thinking => :comments}, :order => 'authors.id DESC').all assert_equal [authors(:bob), authors(:mary), authors(:david)], authors assert_no_queries do authors[2].post_about_thinking.comments.first @@ -168,12 +168,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through - source = Vertex.find(:first, :include=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id') + source = Vertex.scoped(:includes=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id').first assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first } end def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many - sink = Vertex.find(:first, :include=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC') + sink = Vertex.scoped(:includes=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC').first assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first } end end diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb index 3044a8c312..75a6295350 100644 --- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb +++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb @@ -24,11 +24,11 @@ class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = false - post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging ) + post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff') assert_nil post.tagging ActiveRecord::Base.store_full_sti_class = true - post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging ) + post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff') assert_instance_of Tagging, post.tagging ensure ActiveRecord::Base.store_full_sti_class = old diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index 1e1958410c..bb0d6bc70b 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -92,8 +92,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase end def test_include_query - res = 0 - res = ShapeExpression.find :all, :include => [ :shape, { :paint => :non_poly } ] + res = ShapeExpression.scoped(:includes => [ :shape, { :paint => :non_poly } ]).all assert_equal NUM_SHAPE_EXPRESSIONS, res.size assert_queries(0) do res.each do |se| @@ -123,7 +122,7 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase assert_nothing_raised do # @davey_mcdave doesn't have any author_favorites includes = {:posts => :comments, :categorizations => :category, :author_favorites => :favorite_author } - Author.all :include => includes, :conditions => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name' + Author.scoped(:includes => includes, :where => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name').to_a end end end diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb index 07d0b24613..5805e71249 100644 --- a/activerecord/test/cases/associations/eager_singularization_test.rb +++ b/activerecord/test/cases/associations/eager_singularization_test.rb @@ -103,43 +103,43 @@ class EagerSingularizationTest < ActiveRecord::TestCase def test_eager_no_extra_singularization_belongs_to return unless @have_tables assert_nothing_raised do - Virus.find(:all, :include => :octopus) + Virus.scoped(:includes => :octopus).all end end def test_eager_no_extra_singularization_has_one return unless @have_tables assert_nothing_raised do - Octopus.find(:all, :include => :virus) + Octopus.scoped(:includes => :virus).all end end def test_eager_no_extra_singularization_has_many return unless @have_tables assert_nothing_raised do - Bus.find(:all, :include => :passes) + Bus.scoped(:includes => :passes).all end end def test_eager_no_extra_singularization_has_and_belongs_to_many return unless @have_tables assert_nothing_raised do - Crisis.find(:all, :include => :messes) - Mess.find(:all, :include => :crises) + Crisis.scoped(:includes => :messes).all + Mess.scoped(:includes => :crises).all end end def test_eager_no_extra_singularization_has_many_through_belongs_to return unless @have_tables assert_nothing_raised do - Crisis.find(:all, :include => :successes) + Crisis.scoped(:includes => :successes).all end end def test_eager_no_extra_singularization_has_many_through_has_many return unless @have_tables assert_nothing_raised do - Crisis.find(:all, :include => :compresses) + Crisis.scoped(:includes => :compresses).all end end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 196de34094..2e44005847 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -35,42 +35,42 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_one_through_join_model_with_conditions_on_the_through - member = Member.find(members(:some_other_guy).id, :include => :favourite_club) + member = Member.scoped(:includes => :favourite_club).find(members(:some_other_guy).id) assert_nil member.favourite_club end def test_loading_with_one_association - posts = Post.find(:all, :include => :comments) + posts = Post.scoped(:includes => :comments).all post = posts.find { |p| p.id == 1 } assert_equal 2, post.comments.size assert post.comments.include?(comments(:greetings)) - post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'") + post = Post.scoped(:includes => :comments, :where => "posts.title = 'Welcome to the weblog'").first assert_equal 2, post.comments.size assert post.comments.include?(comments(:greetings)) - posts = Post.find(:all, :include => :last_comment) + posts = Post.scoped(:includes => :last_comment).all post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end def test_loading_with_one_association_with_non_preload - posts = Post.find(:all, :include => :last_comment, :order => 'comments.id DESC') + posts = Post.scoped(:includes => :last_comment, :order => 'comments.id DESC').all post = posts.find { |p| p.id == 1 } assert_equal Post.find(1).last_comment, post.last_comment end def test_loading_conditions_with_or - posts = authors(:david).posts.references(:comments).find( - :all, :include => :comments, - :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'" - ) + posts = authors(:david).posts.references(:comments).scoped( + :includes => :comments, + :where => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'" + ).all assert_nil posts.detect { |p| p.author_id != authors(:david).id }, "expected to find only david's posts" end def test_with_ordering - list = Post.find(:all, :include => :comments, :order => "posts.id DESC") + list = Post.scoped(:includes => :comments, :order => "posts.id DESC").all [:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments, :authorless, :thinking, :welcome ].each_with_index do |post, index| @@ -84,14 +84,14 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_loading_with_multiple_associations - posts = Post.find(:all, :include => [ :comments, :author, :categories ], :order => "posts.id") + posts = Post.scoped(:includes => [ :comments, :author, :categories ], :order => "posts.id").all assert_equal 2, posts.first.comments.size assert_equal 2, posts.first.categories.size assert posts.first.comments.include?(comments(:greetings)) end def test_duplicate_middle_objects - comments = Comment.find :all, :conditions => 'post_id = 1', :include => [:post => :author] + comments = Comment.scoped(:where => 'post_id = 1', :includes => [:post => :author]).all assert_no_queries do comments.each {|comment| comment.post.author.name} end @@ -99,25 +99,25 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_preloading_has_many_in_multiple_queries_with_more_ids_than_database_can_handle Post.connection.expects(:in_clause_length).at_least_once.returns(5) - posts = Post.find(:all, :include=>:comments) + posts = Post.scoped(:includes=>:comments).all assert_equal 11, posts.size end def test_preloading_has_many_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle Post.connection.expects(:in_clause_length).at_least_once.returns(nil) - posts = Post.find(:all, :include=>:comments) + posts = Post.scoped(:includes=>:comments).all assert_equal 11, posts.size end def test_preloading_habtm_in_multiple_queries_with_more_ids_than_database_can_handle Post.connection.expects(:in_clause_length).at_least_once.returns(5) - posts = Post.find(:all, :include=>:categories) + posts = Post.scoped(:includes=>:categories).all assert_equal 11, posts.size end def test_preloading_habtm_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle Post.connection.expects(:in_clause_length).at_least_once.returns(nil) - posts = Post.find(:all, :include=>:categories) + posts = Post.scoped(:includes=>:categories).all assert_equal 11, posts.size end @@ -154,8 +154,8 @@ class EagerAssociationTest < ActiveRecord::TestCase popular_post.readers.create!(:person => people(:michael)) popular_post.readers.create!(:person => people(:david)) - readers = Reader.find(:all, :conditions => ["post_id = ?", popular_post.id], - :include => {:post => :comments}) + readers = Reader.scoped(:where => ["post_id = ?", popular_post.id], + :includes => {:post => :comments}).all readers.each do |reader| assert_equal [comment], reader.post.comments end @@ -167,8 +167,8 @@ class EagerAssociationTest < ActiveRecord::TestCase car_post.categories << categories(:technology) comment = car_post.comments.create!(:body => "hmm") - categories = Category.find(:all, :conditions => { 'posts.id' => car_post.id }, - :include => {:posts => :comments}) + categories = Category.scoped(:where => { 'posts.id' => car_post.id }, + :includes => {:posts => :comments}).all categories.each do |category| assert_equal [comment], category.posts[0].comments end @@ -186,7 +186,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_has_many_association_with_same_include_includes_only_once author_id = authors(:david).id - author = assert_queries(3) { Author.find(author_id, :include => {:posts_with_comments => :comments}) } # find the author, then find the posts, then find the comments + author = assert_queries(3) { Author.scoped(:includes => {:posts_with_comments => :comments}).find(author_id) } # find the author, then find the posts, then find the comments author.posts_with_comments.each do |post_with_comments| assert_equal post_with_comments.comments.length, post_with_comments.comments.count assert_nil post_with_comments.comments.uniq! @@ -197,7 +197,7 @@ class EagerAssociationTest < ActiveRecord::TestCase author = authors(:david) post = author.post_about_thinking_with_last_comment last_comment = post.last_comment - author = assert_queries(3) { Author.find(author.id, :include => {:post_about_thinking_with_last_comment => :last_comment})} # find the author, then find the posts, then find the comments + author = assert_queries(3) { Author.scoped(:includes => {:post_about_thinking_with_last_comment => :last_comment}).find(author.id)} # find the author, then find the posts, then find the comments assert_no_queries do assert_equal post, author.post_about_thinking_with_last_comment assert_equal last_comment, author.post_about_thinking_with_last_comment.last_comment @@ -208,7 +208,7 @@ class EagerAssociationTest < ActiveRecord::TestCase post = posts(:welcome) author = post.author author_address = author.author_address - post = assert_queries(3) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author, then find the address + post = assert_queries(3) { Post.scoped(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author, then find the address assert_no_queries do assert_equal author, post.author_with_address assert_equal author_address, post.author_with_address.author_address @@ -218,7 +218,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once post = posts(:welcome) post.update_attributes!(:author => nil) - post = assert_queries(1) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the author or address + post = assert_queries(1) { Post.scoped(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author which is null so no query for the author or address assert_no_queries do assert_equal nil, post.author_with_address end @@ -227,85 +227,85 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_null_belongs_to_polymorphic_association sponsor = sponsors(:moustache_club_sponsor_for_groucho) sponsor.update_attributes!(:sponsorable => nil) - sponsor = assert_queries(1) { Sponsor.find(sponsor.id, :include => :sponsorable) } + sponsor = assert_queries(1) { Sponsor.scoped(:includes => :sponsorable).find(sponsor.id) } assert_no_queries do assert_equal nil, sponsor.sponsorable end end def test_loading_from_an_association - posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id") + posts = authors(:david).posts.scoped(:includes => :comments, :order => "posts.id").all assert_equal 2, posts.first.comments.size end def test_loading_from_an_association_that_has_a_hash_of_conditions assert_nothing_raised do - Author.find(:all, :include => :hello_posts_with_hash_conditions) + Author.scoped(:includes => :hello_posts_with_hash_conditions).all end - assert !Author.find(authors(:david).id, :include => :hello_posts_with_hash_conditions).hello_posts.empty? + assert !Author.scoped(:includes => :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty? end def test_loading_with_no_associations - assert_nil Post.find(posts(:authorless).id, :include => :author).author + assert_nil Post.scoped(:includes => :author).find(posts(:authorless).id).author end def test_nested_loading_with_no_associations assert_nothing_raised do - Post.find(posts(:authorless).id, :include => {:author => :author_addresss}) + Post.scoped(:includes => {:author => :author_addresss}).find(posts(:authorless).id) end end def test_nested_loading_through_has_one_association - aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}) + aa = AuthorAddress.scoped(:includes => {:author => :posts}).find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order - aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :order => 'author_addresses.id') + aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'author_addresses.id').find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order_on_association - aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :order => 'authors.id') + aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'authors.id').find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_order_on_nested_association - aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :order => 'posts.id') + aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'posts.id').find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_conditions - aa = AuthorAddress.references(:author_addresses).find( - author_addresses(:david_address).id, :include => {:author => :posts}, - :conditions => "author_addresses.id > 0" - ) + aa = AuthorAddress.references(:author_addresses).scoped( + :includes => {:author => :posts}, + :where => "author_addresses.id > 0" + ).find author_addresses(:david_address).id assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_conditions_on_association - aa = AuthorAddress.references(:authors).find( - author_addresses(:david_address).id, :include => {:author => :posts}, - :conditions => "authors.id > 0" - ) + aa = AuthorAddress.references(:authors).scoped( + :includes => {:author => :posts}, + :where => "authors.id > 0" + ).find author_addresses(:david_address).id assert_equal aa.author.posts.count, aa.author.posts.length end def test_nested_loading_through_has_one_association_with_conditions_on_nested_association - aa = AuthorAddress.references(:posts).find( - author_addresses(:david_address).id, :include => {:author => :posts}, - :conditions => "posts.id > 0" - ) + aa = AuthorAddress.references(:posts).scoped( + :includes => {:author => :posts}, + :where => "posts.id > 0" + ).find author_addresses(:david_address).id assert_equal aa.author.posts.count, aa.author.posts.length end def test_eager_association_loading_with_belongs_to_and_foreign_keys - pets = Pet.find(:all, :include => :owner) + pets = Pet.scoped(:includes => :owner).all assert_equal 3, pets.length end def test_eager_association_loading_with_belongs_to - comments = Comment.find(:all, :include => :post) + comments = Comment.scoped(:includes => :post).all assert_equal 11, comments.length titles = comments.map { |c| c.post.title } assert titles.include?(posts(:welcome).title) @@ -313,31 +313,31 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_association_loading_with_belongs_to_and_limit - comments = Comment.find(:all, :include => :post, :limit => 5, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :limit => 5, :order => 'comments.id').all assert_equal 5, comments.length assert_equal [1,2,3,5,6], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_conditions - comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :where => 'post_id = 4', :limit => 3, :order => 'comments.id').all assert_equal 3, comments.length assert_equal [5,6,7], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset - comments = Comment.find(:all, :include => :post, :limit => 3, :offset => 2, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :limit => 3, :offset => 2, :order => 'comments.id').all assert_equal 3, comments.length assert_equal [3,5,6], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions - comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :where => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id').all assert_equal 3, comments.length assert_equal [6,7,8], comments.collect { |c| c.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array - comments = Comment.find(:all, :include => :post, :conditions => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :where => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id').all assert_equal 3, comments.length assert_equal [6,7,8], comments.collect { |c| c.id } end @@ -345,7 +345,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name assert_nothing_raised do ActiveSupport::Deprecation.silence do - Comment.find(:all, :include => :post, :conditions => ['posts.id = ?',4]) + Comment.scoped(:includes => :post, :where => ['posts.id = ?',4]).all end end end @@ -353,7 +353,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_conditions_hash comments = [] assert_nothing_raised do - comments = Comment.find(:all, :include => :post, :conditions => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id') + comments = Comment.scoped(:includes => :post, :where => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id').all end assert_equal 3, comments.length assert_equal [5,6,7], comments.collect { |c| c.id } @@ -366,14 +366,14 @@ class EagerAssociationTest < ActiveRecord::TestCase quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') assert_nothing_raised do ActiveSupport::Deprecation.silence do - Comment.find(:all, :include => :post, :conditions => ["#{quoted_posts_id} = ?",4]) + Comment.scoped(:includes => :post, :where => ["#{quoted_posts_id} = ?",4]).all end end end def test_eager_association_loading_with_belongs_to_and_order_string_with_unquoted_table_name assert_nothing_raised do - Comment.find(:all, :include => :post, :order => 'posts.id') + Comment.scoped(:includes => :post, :order => 'posts.id').all end end @@ -381,55 +381,55 @@ class EagerAssociationTest < ActiveRecord::TestCase quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') assert_nothing_raised do ActiveSupport::Deprecation.silence do - Comment.find(:all, :include => :post, :order => quoted_posts_id) + Comment.scoped(:includes => :post, :order => quoted_posts_id).all end end end def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations - posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :order => 'posts.id') + posts = Post.scoped(:includes => [:author, :very_special_comment], :limit => 1, :order => 'posts.id').all assert_equal 1, posts.length assert_equal [1], posts.collect { |p| p.id } end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations - posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id') + posts = Post.scoped(:includes => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id').all assert_equal 1, posts.length assert_equal [2], posts.collect { |p| p.id } end def test_eager_association_loading_with_belongs_to_inferred_foreign_key_from_association_name - author_favorite = AuthorFavorite.find(:first, :include => :favorite_author) + author_favorite = AuthorFavorite.scoped(:includes => :favorite_author).first assert_equal authors(:mary), assert_no_queries { author_favorite.favorite_author } end def test_eager_load_belongs_to_quotes_table_and_column_names - job = Job.find jobs(:unicyclist).id, :include => :ideal_reference + job = Job.includes(:ideal_reference).find jobs(:unicyclist).id references(:michael_unicyclist) assert_no_queries{ assert_equal references(:michael_unicyclist), job.ideal_reference} end def test_eager_load_has_one_quotes_table_and_column_names - michael = Person.find(people(:michael), :include => :favourite_reference) + michael = Person.scoped(:includes => :favourite_reference).find(people(:michael)) references(:michael_unicyclist) assert_no_queries{ assert_equal references(:michael_unicyclist), michael.favourite_reference} end def test_eager_load_has_many_quotes_table_and_column_names - michael = Person.find(people(:michael), :include => :references) + michael = Person.scoped(:includes => :references).find(people(:michael)) references(:michael_magician,:michael_unicyclist) assert_no_queries{ assert_equal references(:michael_magician,:michael_unicyclist), michael.references.sort_by(&:id) } end def test_eager_load_has_many_through_quotes_table_and_column_names - michael = Person.find(people(:michael), :include => :jobs) + michael = Person.scoped(:includes => :jobs).find(people(:michael)) jobs(:magician, :unicyclist) assert_no_queries{ assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) } end def test_eager_load_has_many_with_string_keys subscriptions = subscriptions(:webster_awdr, :webster_rfr) - subscriber =Subscriber.find(subscribers(:second).id, :include => :subscriptions) + subscriber =Subscriber.scoped(:includes => :subscriptions).find(subscribers(:second).id) assert_equal subscriptions, subscriber.subscriptions.sort_by(&:id) end @@ -447,25 +447,25 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_load_has_many_through_with_string_keys books = books(:awdr, :rfr) - subscriber = Subscriber.find(subscribers(:second).id, :include => :books) + subscriber = Subscriber.scoped(:includes => :books).find(subscribers(:second).id) assert_equal books, subscriber.books.sort_by(&:id) end def test_eager_load_belongs_to_with_string_keys subscriber = subscribers(:second) - subscription = Subscription.find(subscriptions(:webster_awdr).id, :include => :subscriber) + subscription = Subscription.scoped(:includes => :subscriber).find(subscriptions(:webster_awdr).id) assert_equal subscriber, subscription.subscriber end def test_eager_association_loading_with_explicit_join - posts = Post.find(:all, :include => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id') + posts = Post.scoped(:includes => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id').all assert_equal 1, posts.length end def test_eager_with_has_many_through - posts_with_comments = people(:michael).posts.find(:all, :include => :comments, :order => 'posts.id') - posts_with_author = people(:michael).posts.find(:all, :include => :author, :order => 'posts.id') - posts_with_comments_and_author = people(:michael).posts.find(:all, :include => [ :comments, :author ], :order => 'posts.id') + posts_with_comments = people(:michael).posts.scoped(:includes => :comments, :order => 'posts.id').all + posts_with_author = people(:michael).posts.scoped(:includes => :author, :order => 'posts.id').all + posts_with_comments_and_author = people(:michael).posts.scoped(:includes => [ :comments, :author ], :order => 'posts.id').all assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size } assert_equal authors(:david), assert_no_queries { posts_with_author.first.author } assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author } @@ -476,32 +476,32 @@ class EagerAssociationTest < ActiveRecord::TestCase Post.create!(:author => author, :title => "TITLE", :body => "BODY") author.author_favorites.create(:favorite_author_id => 1) author.author_favorites.create(:favorite_author_id => 2) - posts_with_author_favorites = author.posts.find(:all, :include => :author_favorites) + posts_with_author_favorites = author.posts.scoped(:includes => :author_favorites).all assert_no_queries { posts_with_author_favorites.first.author_favorites.first.author_id } end def test_eager_with_has_many_through_an_sti_join_model - author = Author.find(:first, :include => :special_post_comments, :order => 'authors.id') + author = Author.scoped(:includes => :special_post_comments, :order => 'authors.id').first assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments } end def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both - author = Author.find(:first, :include => :special_nonexistant_post_comments, :order => 'authors.id') + author = Author.scoped(:includes => :special_nonexistant_post_comments, :order => 'authors.id').first assert_equal [], author.special_nonexistant_post_comments end def test_eager_with_has_many_through_join_model_with_conditions - assert_equal Author.find(:first, :include => :hello_post_comments, - :order => 'authors.id').hello_post_comments.sort_by(&:id), - Author.find(:first, :order => 'authors.id').hello_post_comments.sort_by(&:id) + assert_equal Author.scoped(:includes => :hello_post_comments, + :order => 'authors.id').first.hello_post_comments.sort_by(&:id), + Author.scoped(:order => 'authors.id').first.hello_post_comments.sort_by(&:id) end def test_eager_with_has_many_through_join_model_with_conditions_on_top_level - assert_equal comments(:more_greetings), Author.find(authors(:david).id, :include => :comments_with_order_and_conditions).comments_with_order_and_conditions.first + assert_equal comments(:more_greetings), Author.scoped(:includes => :comments_with_order_and_conditions).find(authors(:david).id).comments_with_order_and_conditions.first end def test_eager_with_has_many_through_join_model_with_include - author_comments = Author.find(authors(:david).id, :include => :comments_with_include).comments_with_include.to_a + author_comments = Author.scoped(:includes => :comments_with_include).find(authors(:david).id).comments_with_include.to_a assert_no_queries do author_comments.first.post.title end @@ -509,7 +509,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_many_through_with_conditions_join_model_with_include post_tags = Post.find(posts(:welcome).id).misc_tags - eager_post_tags = Post.find(1, :include => :misc_tags).misc_tags + eager_post_tags = Post.scoped(:includes => :misc_tags).find(1).misc_tags assert_equal post_tags, eager_post_tags end @@ -520,16 +520,16 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_and_limit - posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2) + posts = Post.scoped(:order => 'posts.id asc', :includes => [ :author, :comments ], :limit => 2).all assert_equal 2, posts.size assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size } end def test_eager_with_has_many_and_limit_and_conditions if current_adapter?(:OpenBaseAdapter) - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id") + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id").all else - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.body = 'hello'", :order => "posts.id") + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").all end assert_equal 2, posts.size assert_equal [4,5], posts.collect { |p| p.id } @@ -537,9 +537,9 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_many_and_limit_and_conditions_array if current_adapter?(:OpenBaseAdapter) - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id") + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id").all else - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "posts.body = ?", 'hello' ], :order => "posts.id") + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").all end assert_equal 2, posts.size assert_equal [4,5], posts.collect { |p| p.id } @@ -547,7 +547,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers posts = ActiveSupport::Deprecation.silence do - Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) + Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "authors.name = ?", 'David' ]).all end assert_equal 2, posts.size @@ -558,34 +558,34 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_and_limit_and_high_offset - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => { 'authors.name' => 'David' }) + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).all assert_equal 0, posts.size end def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions assert_queries(1) do posts = Post.references(:authors, :comments). - find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, - :conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ]) + scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, + :where => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ]).all assert_equal 0, posts.size end end def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_hash_conditions assert_queries(1) do - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, - :conditions => { 'authors.name' => 'David', 'comments.body' => 'go crazy' }) + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, + :where => { 'authors.name' => 'David', 'comments.body' => 'go crazy' }).all assert_equal 0, posts.size end end def test_count_eager_with_has_many_and_limit_and_high_offset - posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => { 'authors.name' => 'David' }) + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).count(:all) assert_equal 0, posts end def test_eager_with_has_many_and_limit_with_no_results - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.title = 'magic forest'") + posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "posts.title = 'magic forest'").all assert_equal 0, posts.size end @@ -602,7 +602,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_and_belongs_to_many_and_limit - posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3) + posts = Post.scoped(:includes => :categories, :order => "posts.id", :limit => 3).all assert_equal 3, posts.size assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size @@ -667,41 +667,8 @@ class EagerAssociationTest < ActiveRecord::TestCase end end - def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers - Post.send(:with_scope, :find => { :conditions => "1=1" }) do - posts = - authors(:david).posts - .includes(:comments) - .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'") - .references(:comments) - .limit(2) - .to_a - assert_equal 2, posts.size - - count = Post.includes(:comments, :author) - .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')") - .references(:authors, :comments) - .limit(2) - .count - assert_equal count, posts.size - end - end - - def test_eager_with_scoped_order_using_association_limiting_without_explicit_scope - posts_with_explicit_order = - Post.where('comments.id is not null').references(:comments) - .includes(:comments).order('posts.id DESC').limit(2) - - posts_with_scoped_order = Post.order('posts.id DESC').scoping do - Post.where('comments.id is not null').references(:comments) - .includes(:comments).limit(2) - end - - assert_equal posts_with_explicit_order, posts_with_scoped_order - end - def test_eager_association_loading_with_habtm - posts = Post.find(:all, :include => :categories, :order => "posts.id") + posts = Post.scoped(:includes => :categories, :order => "posts.id").all assert_equal 2, posts[0].categories.size assert_equal 1, posts[1].categories.size assert_equal 0, posts[2].categories.size @@ -710,23 +677,23 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_inheritance - SpecialPost.find(:all, :include => [ :comments ]) + SpecialPost.scoped(:includes => [ :comments ]).all end def test_eager_has_one_with_association_inheritance - post = Post.find(4, :include => [ :very_special_comment ]) + post = Post.scoped(:includes => [ :very_special_comment ]).find(4) assert_equal "VerySpecialComment", post.very_special_comment.class.to_s end def test_eager_has_many_with_association_inheritance - post = Post.find(4, :include => [ :special_comments ]) + post = Post.scoped(:includes => [ :special_comments ]).find(4) post.special_comments.each do |special_comment| assert special_comment.is_a?(SpecialComment) end end def test_eager_habtm_with_association_inheritance - post = Post.find(6, :include => [ :special_categories ]) + post = Post.scoped(:includes => [ :special_categories ]).find(6) assert_equal 1, post.special_categories.size post.special_categories.each do |special_category| assert_equal "SpecialCategory", special_category.class.to_s @@ -735,8 +702,8 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_one_dependent_does_not_destroy_dependent assert_not_nil companies(:first_firm).account - f = Firm.find(:first, :include => :account, - :conditions => ["companies.name = ?", "37signals"]) + f = Firm.scoped(:includes => :account, + :where => ["companies.name = ?", "37signals"]).first assert_not_nil f.account assert_equal companies(:first_firm, :reload).account, f.account end @@ -750,16 +717,16 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_invalid_association_reference assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - Post.find(6, :include=> :monkeys ) + Post.scoped(:includes=> :monkeys ).find(6) } assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - Post.find(6, :include=>[ :monkeys ]) + Post.scoped(:includes=>[ :monkeys ]).find(6) } assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - Post.find(6, :include=>[ 'monkeys' ]) + Post.scoped(:includes=>[ 'monkeys' ]).find(6) } assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") { - Post.find(6, :include=>[ :monkeys, :elephants ]) + Post.scoped(:includes=>[ :monkeys, :elephants ]).find(6) } end @@ -804,51 +771,51 @@ class EagerAssociationTest < ActiveRecord::TestCase end def find_all_ordered(className, include=nil) - className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :include=>include) + className.scoped(:order=>"#{className.table_name}.#{className.primary_key}", :includes=>include).all end def test_limited_eager_with_order assert_equal( posts(:thinking, :sti_comments), - Post.find( - :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' }, + Post.scoped( + :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, :order => 'UPPER(posts.title)', :limit => 2, :offset => 1 - ) + ).all ) assert_equal( posts(:sti_post_and_comments, :sti_comments), - Post.find( - :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' }, + Post.scoped( + :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1 - ) + ).all ) end def test_limited_eager_with_multiple_order_columns assert_equal( posts(:thinking, :sti_comments), - Post.find( - :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' }, + Post.scoped( + :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1 - ) + ).all ) assert_equal( posts(:sti_post_and_comments, :sti_comments), - Post.find( - :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' }, + Post.scoped( + :includes => [:author, :comments], :where => { 'authors.name' => 'David' }, :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1 - ) + ).all ) end def test_limited_eager_with_numeric_in_association assert_equal( people(:david, :susan), - Person.references(:number1_fans_people).find( - :all, :include => [:readers, :primary_contact, :number1_fan], - :conditions => "number1_fans_people.first_name like 'M%'", + Person.references(:number1_fans_people).scoped( + :includes => [:readers, :primary_contact, :number1_fan], + :where => "number1_fans_people.first_name like 'M%'", :order => 'people.id', :limit => 2, :offset => 0 - ) + ).all ) end @@ -861,9 +828,9 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_polymorphic_type_condition - post = Post.find(posts(:thinking).id, :include => :taggings) + post = Post.scoped(:includes => :taggings).find(posts(:thinking).id) assert post.taggings.include?(taggings(:thinking_general)) - post = SpecialPost.find(posts(:thinking).id, :include => :taggings) + post = SpecialPost.scoped(:includes => :taggings).find(posts(:thinking).id) assert post.taggings.include?(taggings(:thinking_general)) end @@ -914,13 +881,13 @@ class EagerAssociationTest < ActiveRecord::TestCase end end def test_eager_with_valid_association_as_string_not_symbol - assert_nothing_raised { Post.find(:all, :include => 'comments') } + assert_nothing_raised { Post.scoped(:includes => 'comments').all } end def test_eager_with_floating_point_numbers assert_queries(2) do # Before changes, the floating point numbers will be interpreted as table names and will cause this to run in one query - Comment.find :all, :conditions => "123.456 = 123.456", :include => :post + Comment.scoped(:where => "123.456 = 123.456", :includes => :post).all end end @@ -974,21 +941,21 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_load_with_sti_sharing_association assert_queries(2) do #should not do 1 query per subclass - Comment.find :all, :include => :post + Comment.includes(:post).all end end def test_conditions_on_join_table_with_include_and_limit - assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => { 'developers_projects.access_level' => 1 }, :limit => 5).size + assert_equal 3, Developer.scoped(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).all.size end def test_order_on_join_table_with_include_and_limit - assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size + assert_equal 5, Developer.scoped(:includes => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).all.size end def test_eager_loading_with_order_on_joined_table_preloads posts = assert_queries(2) do - Post.find(:all, :joins => :comments, :include => :author, :order => 'comments.id DESC') + Post.scoped(:joins => :comments, :includes => :author, :order => 'comments.id DESC').all end assert_equal posts(:eager_other), posts[1] assert_equal authors(:mary), assert_no_queries { posts[1].author} @@ -996,24 +963,24 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_conditions_on_joined_table_preloads posts = assert_queries(2) do - Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') + Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} posts = assert_queries(2) do - Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') + Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} posts = assert_queries(2) do - Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id') + Post.scoped(:includes => :author, :joins => {:taggings => :tag}, :where => "tags.name = 'General'", :order => 'posts.id').all end assert_equal posts(:welcome, :thinking), posts posts = assert_queries(2) do - Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id') + Post.scoped(:includes => :author, :joins => {:taggings => {:tag => :taggings}}, :where => "taggings_tags.super_tag_id=2", :order => 'posts.id').all end assert_equal posts(:welcome, :thinking), posts @@ -1021,13 +988,13 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_conditions_on_string_joined_table_preloads posts = assert_queries(2) do - Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') + Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :where => "comments.body like 'Thank you%'", :order => 'posts.id').all end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} posts = assert_queries(2) do - Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') + Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} @@ -1036,7 +1003,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_loading_with_select_on_joined_table_preloads posts = assert_queries(2) do - Post.find(:all, :select => 'posts.*, authors.name as author_name', :include => :comments, :joins => :author, :order => 'posts.id') + Post.scoped(:select => 'posts.*, authors.name as author_name', :includes => :comments, :joins => :author, :order => 'posts.id').all end assert_equal 'David', posts[0].author_name assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments} @@ -1046,14 +1013,14 @@ class EagerAssociationTest < ActiveRecord::TestCase Author.columns authors = assert_queries(2) do - Author.find(:all, :include => :author_address, :joins => :comments, :conditions => "posts.title like 'Welcome%'") + Author.scoped(:includes => :author_address, :joins => :comments, :where => "posts.title like 'Welcome%'").all end assert_equal authors(:david), authors[0] assert_equal author_addresses(:david_address), authors[0].author_address end def test_preload_belongs_to_uses_exclusive_scope - people = Person.males.find(:all, :include => :primary_contact) + people = Person.males.scoped(:includes => :primary_contact).all assert_not_equal people.length, 0 people.each do |person| assert_no_queries {assert_not_nil person.primary_contact} @@ -1062,15 +1029,15 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_preload_has_many_uses_exclusive_scope - people = Person.males.find :all, :include => :agents + people = Person.males.includes(:agents).all people.each do |person| assert_equal Person.find(person.id).agents, person.agents end end def test_preload_has_many_using_primary_key - expected = Firm.find(:first).clients_using_primary_key.to_a - firm = Firm.find :first, :include => :clients_using_primary_key + expected = Firm.first.clients_using_primary_key.to_a + firm = Firm.includes(:clients_using_primary_key).first assert_no_queries do assert_equal expected, firm.clients_using_primary_key end @@ -1080,9 +1047,9 @@ class EagerAssociationTest < ActiveRecord::TestCase expected = Firm.find(1).clients_using_primary_key.sort_by(&:name) # Oracle adapter truncates alias to 30 characters if current_adapter?(:OracleAdapter) - firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name' + firm = Firm.scoped(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name').find(1) else - firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name' + firm = Firm.scoped(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name').find(1) end assert_no_queries do assert_equal expected, firm.clients_using_primary_key @@ -1091,7 +1058,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_preload_has_one_using_primary_key expected = accounts(:signals37) - firm = Firm.find :first, :include => :account_using_primary_key, :order => 'companies.id' + firm = Firm.scoped(:includes => :account_using_primary_key, :order => 'companies.id').first assert_no_queries do assert_equal expected, firm.account_using_primary_key end @@ -1099,7 +1066,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_include_has_one_using_primary_key expected = accounts(:signals37) - firm = Firm.find(:all, :include => :account_using_primary_key, :order => 'accounts.id').detect {|f| f.id == 1} + firm = Firm.scoped(:includes => :account_using_primary_key, :order => 'accounts.id').all.detect {|f| f.id == 1} assert_no_queries do assert_equal expected, firm.account_using_primary_key end @@ -1163,7 +1130,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_deep_including_through_habtm - posts = Post.find(:all, :include => {:categories => :categorizations}, :order => "posts.id") + posts = Post.scoped(:includes => {:categories => :categorizations}, :order => "posts.id").all assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length } assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length } assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length } diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index f5f5b96aa7..22fd80df28 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -355,7 +355,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_deleting_array david = Developer.find(1) david.projects.reload - david.projects.delete(Project.find(:all)) + david.projects.delete(Project.all) assert_equal 0, david.projects.size assert_equal 0, david.projects(true).size end @@ -375,7 +375,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase active_record.developers.reload assert_equal 3, active_record.developers_by_sql.size - active_record.developers_by_sql.delete(Developer.find(:all)) + active_record.developers_by_sql.delete(Developer.all) assert_equal 0, active_record.developers_by_sql(true).size end @@ -548,15 +548,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_find_with_merged_options assert_equal 1, projects(:active_record).limited_developers.size - assert_equal 1, projects(:active_record).limited_developers.find(:all).size - assert_equal 3, projects(:active_record).limited_developers.find(:all, :limit => nil).size + assert_equal 1, projects(:active_record).limited_developers.all.size + assert_equal 3, projects(:active_record).limited_developers.limit(nil).all.size end def test_dynamic_find_should_respect_association_order # Developers are ordered 'name DESC, id DESC' high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis') - assert_equal high_id_jamis, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'") + assert_equal high_id_jamis, projects(:active_record).developers.scoped(:where => "name = 'Jamis'").first assert_equal high_id_jamis, projects(:active_record).developers.find_by_name('Jamis') end @@ -566,7 +566,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase middle_id_jamis = developers(:poor_jamis) high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis') - assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find(:all, :conditions => "name = 'Jamis'") + assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.scoped(:where => "name = 'Jamis'").all assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find_all_by_name('Jamis') end @@ -576,12 +576,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_dynamic_find_all_should_respect_association_limit - assert_equal 1, projects(:active_record).limited_developers.find(:all, :conditions => "name = 'Jamis'").length + assert_equal 1, projects(:active_record).limited_developers.scoped(:where => "name = 'Jamis'").all.length assert_equal 1, projects(:active_record).limited_developers.find_all_by_name('Jamis').length end def test_dynamic_find_all_order_should_override_association_limit - assert_equal 2, projects(:active_record).limited_developers.find(:all, :conditions => "name = 'Jamis'", :limit => 9_000).length + assert_equal 2, projects(:active_record).limited_developers.scoped(:where => "name = 'Jamis'", :limit => 9_000).all.length assert_equal 2, projects(:active_record).limited_developers.find_all_by_name('Jamis', :limit => 9_000).length end @@ -632,7 +632,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_consider_type - developer = Developer.find(:first) + developer = Developer.first special_project = SpecialProject.create("name" => "Special Project") other_project = developer.projects.first @@ -667,7 +667,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase categories(:technology).select_testing_posts(true).each do |o| assert_respond_to o, :correctness_marker end - assert_respond_to categories(:technology).select_testing_posts.find(:first), :correctness_marker + assert_respond_to categories(:technology).select_testing_posts.first, :correctness_marker end def test_habtm_selects_all_columns_by_default @@ -681,10 +681,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_join_table_alias assert_equal( 3, - Developer.references(:developers_projects_join).find( - :all, :include => {:projects => :developers}, - :conditions => 'developers_projects_join.joined_on IS NOT NULL' - ).size + Developer.references(:developers_projects_join).scoped( + :includes => {:projects => :developers}, + :where => 'developers_projects_join.joined_on IS NOT NULL' + ).to_a.size ) end @@ -697,16 +697,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal( 3, - Developer.references(:developers_projects_join).find( - :all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL', + Developer.references(:developers_projects_join).scoped( + :includes => {:projects => :developers}, :where => 'developers_projects_join.joined_on IS NOT NULL', :group => group.join(",") - ).size + ).to_a.size ) end def test_find_grouped - all_posts_from_category1 = Post.find(:all, :conditions => "category_id = 1", :joins => :categories) - grouped_posts_of_category1 = Post.find(:all, :conditions => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories) + all_posts_from_category1 = Post.scoped(:where => "category_id = 1", :joins => :categories).all + grouped_posts_of_category1 = Post.scoped(:where => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories).all assert_equal 5, all_posts_from_category1.size assert_equal 2, grouped_posts_of_category1.size end @@ -780,8 +780,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, project.developers.size assert_equal 1, developer.projects.size - assert_equal developer, project.developers.find(:first) - assert_equal project, developer.projects.find(:first) + assert_equal developer, project.developers.first + assert_equal project, developer.projects.first end def test_self_referential_habtm_without_foreign_key_set_should_raise_exception @@ -798,13 +798,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog') end - def test_counting_on_habtm_association_and_not_array - david = Developer.find(1) - # Extra parameter just to make sure we aren't falling back to - # Array#count in Ruby >=1.8.7, which would raise an ArgumentError - assert_nothing_raised { david.projects.count(:all, :conditions => '1=1') } - end - def test_count david = Developer.find(1) assert_equal 2, david.projects.count @@ -827,7 +820,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_association_proxy_transaction_method_starts_transaction_in_association_class Post.expects(:transaction) - Category.find(:first).posts.transaction do + Category.first.posts.transaction do # nothing end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 6a4f972356..a98e5b115c 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -242,27 +242,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first def test_counting_with_counter_sql - assert_equal 2, Firm.find(:first, :order => "id").clients.count + assert_equal 2, Firm.scoped(:order => "id").first.clients.count end def test_counting - assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count - end - - def test_counting_with_empty_hash_conditions - assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {}) - end - - def test_counting_with_single_conditions - assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => ['name=?', "Microsoft"]) + assert_equal 2, Firm.scoped(:order => "id").first.plain_clients.count end def test_counting_with_single_hash - assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {:name => "Microsoft"}) + assert_equal 1, Firm.scoped(:order => "id").first.plain_clients.where(:name => "Microsoft").count end def test_counting_with_column_name_and_hash - assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:name) + assert_equal 2, Firm.scoped(:order => "id").first.plain_clients.count(:name) end def test_counting_with_association_limit @@ -272,7 +264,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding - assert_equal 2, Firm.find(:first, :order => "id").clients.length + assert_equal 2, Firm.scoped(:order => "id").first.clients.length end def test_finding_array_compatibility @@ -281,14 +273,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_with_blank_conditions [[], {}, nil, ""].each do |blank| - assert_equal 2, Firm.find(:first, :order => "id").clients.find(:all, :conditions => blank).size + assert_equal 2, Firm.scoped(:order => "id").first.clients.where(blank).all.size end end def test_find_many_with_merged_options assert_equal 1, companies(:first_firm).limited_clients.size - assert_equal 1, companies(:first_firm).limited_clients.find(:all).size - assert_equal 2, companies(:first_firm).limited_clients.find(:all, :limit => nil).size + assert_equal 1, companies(:first_firm).limited_clients.all.size + assert_equal 2, companies(:first_firm).limited_clients.limit(nil).all.size end def test_find_should_append_to_association_order @@ -301,30 +293,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_dynamic_find_should_respect_association_order - assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find(:first, :conditions => "type = 'Client'") + assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.scoped(:where => "type = 'Client'").first assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client') end def test_dynamic_find_all_should_respect_association_order - assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find(:all, :conditions => "type = 'Client'") + assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.scoped(:where => "type = 'Client'").all assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client') end def test_dynamic_find_all_should_respect_association_limit - assert_equal 1, companies(:first_firm).limited_clients.find(:all, :conditions => "type = 'Client'").length + assert_equal 1, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'").all.length assert_equal 1, companies(:first_firm).limited_clients.find_all_by_type('Client').length end def test_dynamic_find_all_limit_should_override_association_limit - assert_equal 2, companies(:first_firm).limited_clients.find(:all, :conditions => "type = 'Client'", :limit => 9_000).length + assert_equal 2, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'", :limit => 9_000).all.length assert_equal 2, companies(:first_firm).limited_clients.find_all_by_type('Client', :limit => 9_000).length end - def test_dynamic_find_all_should_respect_readonly_access - companies(:first_firm).readonly_clients.find(:all).each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } } - companies(:first_firm).readonly_clients.find(:all).each { |c| assert c.readonly? } - end - def test_dynamic_find_or_create_from_two_attributes_using_an_association author = authors(:david) number_of_posts = Post.count @@ -341,59 +328,59 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_triple_equality # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - assert !(Array === Firm.find(:first, :order => "id").clients) - assert Firm.find(:first, :order => "id").clients === Array + assert !(Array === Firm.scoped(:order => "id").first.clients) + assert Firm.scoped(:order => "id").first.clients === Array end def test_finding_default_orders - assert_equal "Summit", Firm.find(:first, :order => "id").clients.first.name + assert_equal "Summit", Firm.scoped(:order => "id").first.clients.first.name end def test_finding_with_different_class_name_and_order - assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_sorted_desc.first.name + assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_sorted_desc.first.name end def test_finding_with_foreign_key - assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_of_firm.first.name + assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_of_firm.first.name end def test_finding_with_condition - assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms.first.name + assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_like_ms.first.name end def test_finding_with_condition_hash - assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms_with_hash_conditions.first.name + assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_like_ms_with_hash_conditions.first.name end def test_finding_using_primary_key - assert_equal "Summit", Firm.find(:first, :order => "id").clients_using_primary_key.first.name + assert_equal "Summit", Firm.scoped(:order => "id").first.clients_using_primary_key.first.name end def test_finding_using_sql - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first first_client = firm.clients_using_sql.first assert_not_nil first_client assert_equal "Microsoft", first_client.name assert_equal 1, firm.clients_using_sql.size - assert_equal 1, Firm.find(:first, :order => "id").clients_using_sql.size + assert_equal 1, Firm.scoped(:order => "id").first.clients_using_sql.size end def test_finding_using_sql_take_into_account_only_uniq_ids - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first client = firm.clients_using_sql.first assert_equal client, firm.clients_using_sql.find(client.id, client.id) assert_equal client, firm.clients_using_sql.find(client.id, client.id.to_s) end def test_counting_using_sql - assert_equal 1, Firm.find(:first, :order => "id").clients_using_counter_sql.size - assert Firm.find(:first, :order => "id").clients_using_counter_sql.any? - assert_equal 0, Firm.find(:first, :order => "id").clients_using_zero_counter_sql.size - assert !Firm.find(:first, :order => "id").clients_using_zero_counter_sql.any? + assert_equal 1, Firm.scoped(:order => "id").first.clients_using_counter_sql.size + assert Firm.scoped(:order => "id").first.clients_using_counter_sql.any? + assert_equal 0, Firm.scoped(:order => "id").first.clients_using_zero_counter_sql.size + assert !Firm.scoped(:order => "id").first.clients_using_zero_counter_sql.any? end def test_counting_non_existant_items_using_sql - assert_equal 0, Firm.find(:first, :order => "id").no_clients_using_counter_sql.size + assert_equal 0, Firm.scoped(:order => "id").first.no_clients_using_counter_sql.size end def test_counting_using_finder_sql @@ -408,7 +395,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_ids - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find } @@ -428,7 +415,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_string_ids_when_using_finder_sql - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first client = firm.clients_using_finder_sql.find("2") assert_kind_of Client, client @@ -444,9 +431,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all - firm = Firm.find(:first, :order => "id") - assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length - assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length + firm = Firm.scoped(:order => "id").first + assert_equal 2, firm.clients.scoped(:where => "#{QUOTED_TYPE} = 'Client'").all.length + assert_equal 1, firm.clients.scoped(:where => "name = 'Summit'").all.length end def test_find_each @@ -465,7 +452,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = companies(:first_firm) assert_queries(2) do - firm.clients.find_each(:batch_size => 1, :conditions => {:name => "Microsoft"}) do |c| + firm.clients.where(name: 'Microsoft').find_each(batch_size: 1) do |c| assert_equal firm.id, c.firm_id assert_equal "Microsoft", c.name end @@ -490,29 +477,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_all_sanitized # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - firm = Firm.find(:first, :order => "id") - summit = firm.clients.find(:all, :conditions => "name = 'Summit'") - assert_equal summit, firm.clients.find(:all, :conditions => ["name = ?", "Summit"]) - assert_equal summit, firm.clients.find(:all, :conditions => ["name = :name", { :name => "Summit" }]) + firm = Firm.scoped(:order => "id").first + summit = firm.clients.scoped(:where => "name = 'Summit'").all + assert_equal summit, firm.clients.scoped(:where => ["name = ?", "Summit"]).all + assert_equal summit, firm.clients.scoped(:where => ["name = :name", { :name => "Summit" }]).all end def test_find_first - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first client2 = Client.find(2) - assert_equal firm.clients.first, firm.clients.find(:first, :order => "id") - assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'", :order => "id") + assert_equal firm.clients.first, firm.clients.scoped(:order => "id").first + assert_equal client2, firm.clients.scoped(:where => "#{QUOTED_TYPE} = 'Client'", :order => "id").first end def test_find_first_sanitized - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first client2 = Client.find(2) - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id") - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id") + assert_equal client2, firm.clients.scoped(:where => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id").first + assert_equal client2, firm.clients.scoped(:where => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id").first end def test_find_all_with_include_and_conditions assert_nothing_raised do - Developer.find(:all, :joins => :audit_logs, :conditions => {'audit_logs.message' => nil, :name => 'Smith'}) + Developer.scoped(:joins => :audit_logs, :where => {'audit_logs.message' => nil, :name => 'Smith'}).all end end @@ -522,8 +509,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_grouped - all_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1") - grouped_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count') + all_clients_of_firm1 = Client.scoped(:where => "firm_id = 1").all + grouped_clients_of_firm1 = Client.scoped(:where => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count').all assert_equal 2, all_clients_of_firm1.size assert_equal 1, grouped_clients_of_firm1.size end @@ -581,7 +568,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_with_bang_on_has_many_raises_when_record_not_saved assert_raise(ActiveRecord::RecordInvalid) do - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first firm.plain_clients.create! end end @@ -813,7 +800,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_deleting_updates_counter_cache - topic = Topic.first(:order => "id ASC") + topic = Topic.order("id ASC").first assert_equal topic.replies.to_a.size, topic.replies_count topic.replies.delete(topic.replies.first) @@ -1008,14 +995,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_delete_all_association_with_primary_key_deletes_correct_records - firm = Firm.find(:first) + firm = Firm.first # break the vanilla firm_id foreign key assert_equal 2, firm.clients.count firm.clients.first.update_column(:firm_id, nil) assert_equal 1, firm.clients(true).count assert_equal 1, firm.clients_using_primary_key_with_delete_all.count old_record = firm.clients_using_primary_key_with_delete_all.first - firm = Firm.find(:first) + firm = Firm.first firm.destroy assert_nil Client.find_by_id(old_record.id) end @@ -1123,7 +1110,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = companies(:first_firm) assert_equal 2, firm.clients.size firm.destroy - assert Client.find(:all, :conditions => "firm_id=#{firm.id}").empty? + assert Client.scoped(:where => "firm_id=#{firm.id}").all.empty? end def test_dependence_for_associations_with_hash_condition @@ -1133,7 +1120,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_destroy_dependent_when_deleted_from_association # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first assert_equal 2, firm.clients.size client = firm.clients.first @@ -1161,7 +1148,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.destroy rescue "do nothing" - assert_equal 2, Client.find(:all, :conditions => "firm_id=#{firm.id}").size + assert_equal 2, Client.scoped(:where => "firm_id=#{firm.id}").all.size end def test_dependence_on_account @@ -1224,16 +1211,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_adding_array_and_collection - assert_nothing_raised { Firm.find(:first).clients + Firm.find(:all).last.clients } - end - - def test_find_all_without_conditions - firm = companies(:first_firm) - assert_equal 2, firm.clients.find(:all).length + assert_nothing_raised { Firm.first.clients + Firm.all.last.clients } end def test_replace_with_less - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first firm.clients = [companies(:first_client)] assert firm.save, "Could not save firm" firm.reload @@ -1247,7 +1229,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_new - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first firm.clients = [companies(:second_client), Client.new("name" => "New Client")] firm.save firm.reload @@ -1347,27 +1329,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_dynamic_find_should_respect_association_order_for_through - assert_equal Comment.find(10), authors(:david).comments_desc.find(:first, :conditions => "comments.type = 'SpecialComment'") + assert_equal Comment.find(10), authors(:david).comments_desc.scoped(:where => "comments.type = 'SpecialComment'").first assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type('SpecialComment') end def test_dynamic_find_all_should_respect_association_order_for_through - assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find(:all, :conditions => "comments.type = 'SpecialComment'") + assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.scoped(:where => "comments.type = 'SpecialComment'").all assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find_all_by_type('SpecialComment') end def test_dynamic_find_all_should_respect_association_limit_for_through - assert_equal 1, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'").length + assert_equal 1, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'").all.length assert_equal 1, authors(:david).limited_comments.find_all_by_type('SpecialComment').length end def test_dynamic_find_all_order_should_override_association_limit_for_through - assert_equal 4, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'", :limit => 9_000).length + assert_equal 4, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'", :limit => 9_000).all.length assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length end def test_find_all_include_over_the_same_table_for_through - assert_equal 2, people(:michael).posts.find(:all, :include => :people).length + assert_equal 2, people(:michael).posts.scoped(:includes => :people).all.length end def test_has_many_through_respects_hash_conditions @@ -1499,17 +1481,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end - def test_calling_first_or_last_with_find_options_on_loaded_association_should_fetch_with_query - firm = companies(:first_firm) - firm.clients.class # force load target - - assert_queries 2 do - assert firm.clients.loaded? - firm.clients.first(:order => 'name') - firm.clients.last(:order => 'name') - end - end - def test_calling_first_or_last_with_integer_on_association_should_load_association firm = companies(:first_firm) @@ -1567,11 +1538,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = Namespaced::Firm.create({ :name => 'Some Company' }) firm.clients.create({ :name => 'Some Client' }) - stats = Namespaced::Firm.find(firm.id, { + stats = Namespaced::Firm.scoped( :select => "#{Namespaced::Firm.table_name}.id, COUNT(#{Namespaced::Client.table_name}.id) AS num_clients", :joins => :clients, :group => "#{Namespaced::Firm.table_name}.id" - }) + ).find firm.id assert_equal 1, stats.num_clients.to_i ensure @@ -1580,7 +1551,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_association_proxy_transaction_method_starts_transaction_in_association_class Comment.expects(:transaction) - Post.find(:first).comments.transaction do + Post.first.comments.transaction do # nothing end end @@ -1597,7 +1568,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_creating_using_primary_key - firm = Firm.find(:first, :order => "id") + firm = Firm.scoped(:order => "id").first client = firm.clients_using_primary_key.create!(:name => 'test') assert_equal firm.name, client.firm_name end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index e9b930204f..1c06007d86 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -533,7 +533,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_count_with_include_should_alias_join_table - assert_equal 2, people(:michael).posts.count(:include => :readers) + assert_equal 2, people(:michael).posts.includes(:readers).count end def test_inner_join_with_quoted_table_name @@ -568,7 +568,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_association_proxy_transaction_method_starts_transaction_in_association_class Tag.expects(:transaction) - Post.find(:first).tags.transaction do + Post.first.tags.transaction do # nothing end end @@ -651,7 +651,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_collection_singular_ids_setter company = companies(:rails_core) - dev = Developer.find(:first) + dev = Developer.first company.developer_ids = [dev.id] assert_equal [dev], company.developers @@ -671,7 +671,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set company = companies(:rails_core) - ids = [Developer.find(:first).id, -9999] + ids = [Developer.first.id, -9999] assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids} end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 9c05b36426..88ec65706c 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -25,13 +25,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_queries(1) { assert_nil firm.account } assert_queries(0) { assert_nil firm.account } - firms = Firm.find(:all, :include => :account) + firms = Firm.scoped(:includes => :account).all assert_queries(0) { firms.each(&:account) } end def test_with_select assert_equal Firm.find(1).account_with_select.attributes.size, 2 - assert_equal Firm.find(1, :include => :account_with_select).account_with_select.attributes.size, 2 + assert_equal Firm.scoped(:includes => :account_with_select).find(1).account_with_select.attributes.size, 2 end def test_finding_using_primary_key @@ -294,13 +294,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_dependence_with_missing_association_and_nullify Account.destroy_all - firm = DependentFirm.find(:first) + firm = DependentFirm.first assert_nil firm.account firm.destroy end def test_finding_with_interpolated_condition - firm = Firm.find(:first) + firm = Firm.first superior = firm.clients.create(:name => 'SuperiorCo') superior.rating = 10 superior.save @@ -346,14 +346,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_nothing_raised do Firm.find(@firm.id).save! - Firm.find(@firm.id, :include => :account).save! + Firm.scoped(:includes => :account).find(@firm.id).save! end @firm.account.destroy assert_nothing_raised do Firm.find(@firm.id).save! - Firm.find(@firm.id, :include => :account).save! + Firm.scoped(:includes => :account).find(@firm.id).save! end end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 2503349c08..94b9639e57 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -73,7 +73,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_eager_loading members = assert_queries(3) do #base table, through table, clubs table - Member.find(:all, :include => :club, :conditions => ["name = ?", "Groucho Marx"]) + Member.scoped(:includes => :club, :where => ["name = ?", "Groucho Marx"]).all end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].club} @@ -81,7 +81,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_eager_loading_through_polymorphic members = assert_queries(3) do #base table, through table, clubs table - Member.find(:all, :include => :sponsor_club, :conditions => ["name = ?", "Groucho Marx"]) + Member.scoped(:includes => :sponsor_club, :where => ["name = ?", "Groucho Marx"]).all end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].sponsor_club} @@ -89,14 +89,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_with_conditions_eager_loading # conditions on the through table - assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :favourite_club).favourite_club + assert_equal clubs(:moustache_club), Member.scoped(:includes => :favourite_club).find(@member.id).favourite_club memberships(:membership_of_favourite_club).update_column(:favourite, false) - assert_equal nil, Member.find(@member.id, :include => :favourite_club).reload.favourite_club + assert_equal nil, Member.scoped(:includes => :favourite_club).find(@member.id).reload.favourite_club # conditions on the source table - assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :hairy_club).hairy_club + assert_equal clubs(:moustache_club), Member.scoped(:includes => :hairy_club).find(@member.id).hairy_club clubs(:moustache_club).update_column(:name, "Association of Clean-Shaven Persons") - assert_equal nil, Member.find(@member.id, :include => :hairy_club).reload.hairy_club + assert_equal nil, Member.scoped(:includes => :hairy_club).find(@member.id).reload.hairy_club end def test_has_one_through_polymorphic_with_source_type @@ -104,14 +104,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_eager_has_one_through_polymorphic_with_source_type - clubs = Club.find(:all, :include => :sponsored_member, :conditions => ["name = ?","Moustache and Eyebrow Fancier Club"]) + clubs = Club.scoped(:includes => :sponsored_member, :where => ["name = ?","Moustache and Eyebrow Fancier Club"]).all # Only the eyebrow fanciers club has a sponsored_member assert_not_nil assert_no_queries {clubs[0].sponsored_member} end def test_has_one_through_nonpreload_eagerloading members = assert_queries(1) do - Member.find(:all, :include => :club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback + Member.scoped(:includes => :club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').all #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].club} @@ -119,7 +119,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic members = assert_queries(1) do - Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback + Member.scoped(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').all #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries {members[0].sponsor_club} @@ -128,7 +128,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record Sponsor.new(:sponsor_club => clubs(:crazy_club), :sponsorable => members(:groucho)).save! members = assert_queries(1) do - Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC') #force fallback + Member.scoped(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC').all #force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } @@ -197,7 +197,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase @member.member_detail = @member_detail @member.organization = @organization @member_details = assert_queries(3) do - MemberDetail.find(:all, :include => :member_type) + MemberDetail.scoped(:includes => :member_type).all end @new_detail = @member_details[0] assert @new_detail.send(:association, :member_type).loaded? @@ -210,14 +210,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase assert_nothing_raised do Club.find(@club.id).save! - Club.find(@club.id, :include => :sponsored_member).save! + Club.scoped(:includes => :sponsored_member).find(@club.id).save! end @club.sponsor.destroy assert_nothing_raised do Club.find(@club.id).save! - Club.find(@club.id, :include => :sponsored_member).save! + Club.scoped(:includes => :sponsored_member).find(@club.id).save! end end diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index 68a1e62328..1d61d5c474 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -72,17 +72,17 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase def test_count_honors_implicit_inner_joins real_count = Author.scoped.to_a.sum{|a| a.posts.count } - assert_equal real_count, Author.count(:joins => :posts), "plain inner join count should match the number of referenced posts records" + assert_equal real_count, Author.joins(:posts).count, "plain inner join count should match the number of referenced posts records" end def test_calculate_honors_implicit_inner_joins real_count = Author.scoped.to_a.sum{|a| a.posts.count } - assert_equal real_count, Author.calculate(:count, 'authors.id', :joins => :posts), "plain inner join count should match the number of referenced posts records" + assert_equal real_count, Author.joins(:posts).calculate(:count, 'authors.id'), "plain inner join count should match the number of referenced posts records" end def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions real_count = Author.scoped.to_a.select {|a| a.posts.any? {|p| p.title =~ /^Welcome/} }.length - authors_with_welcoming_post_titles = Author.calculate(:count, 'authors.id', :joins => :posts, :distinct => true, :conditions => "posts.title like 'Welcome%'") + authors_with_welcoming_post_titles = Author.scoped(:joins => :posts, :where => "posts.title like 'Welcome%'").calculate(:count, 'authors.id', :distinct => true) assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'" end diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 76282213d8..f35ffb2994 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -96,7 +96,7 @@ class InverseHasOneTests < ActiveRecord::TestCase def test_parent_instance_should_be_shared_with_eager_loaded_child_on_find - m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :face) + m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :face).first f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' @@ -104,7 +104,7 @@ class InverseHasOneTests < ActiveRecord::TestCase f.man.name = 'Mungo' assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance" - m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :face, :order => 'faces.id') + m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :face, :order => 'faces.id').first f = m.face assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' @@ -114,7 +114,7 @@ class InverseHasOneTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_built_child - m = Man.find(:first) + m = Man.first f = m.build_face(:description => 'haunted') assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" @@ -125,7 +125,7 @@ class InverseHasOneTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_created_child - m = Man.find(:first) + m = Man.first f = m.create_face(:description => 'haunted') assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" @@ -136,7 +136,7 @@ class InverseHasOneTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method - m = Man.find(:first) + m = Man.first f = m.create_face!(:description => 'haunted') assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" @@ -147,7 +147,7 @@ class InverseHasOneTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_replaced_via_accessor_child - m = Man.find(:first) + m = Man.first f = Face.new(:description => 'haunted') m.face = f assert_not_nil f.man @@ -159,7 +159,7 @@ class InverseHasOneTests < ActiveRecord::TestCase end def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error - assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).dirty_face } + assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.dirty_face } end end @@ -179,7 +179,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_eager_loaded_children - m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :interests) + m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :interests).first is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" @@ -189,7 +189,7 @@ class InverseHasManyTests < ActiveRecord::TestCase assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance" end - m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :interests, :order => 'interests.id') + m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :interests, :order => 'interests.id').first is = m.interests is.each do |i| assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" @@ -201,7 +201,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_block_style_built_child - m = Man.find(:first) + m = Man.first i = m.interests.build {|ii| ii.topic = 'Industrial Revolution Re-enactment'} assert_not_nil i.topic, "Child attributes supplied to build via blocks should be populated" assert_not_nil i.man @@ -213,7 +213,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child - m = Man.find(:first) + m = Man.first i = m.interests.create!(:topic => 'Industrial Revolution Re-enactment') assert_not_nil i.man assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" @@ -224,7 +224,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_newly_block_style_created_child - m = Man.find(:first) + m = Man.first i = m.interests.create {|ii| ii.topic = 'Industrial Revolution Re-enactment'} assert_not_nil i.topic, "Child attributes supplied to create via blocks should be populated" assert_not_nil i.man @@ -248,7 +248,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_parent_instance_should_be_shared_with_replaced_via_accessor_children - m = Man.find(:first) + m = Man.first i = Interest.new(:topic => 'Industrial Revolution Re-enactment') m.interests = [i] assert_not_nil i.man @@ -260,7 +260,7 @@ class InverseHasManyTests < ActiveRecord::TestCase end def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error - assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).secret_interests } + assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests } end end @@ -278,7 +278,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase end def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find - f = Face.find(:first, :include => :man, :conditions => {:description => 'trusting'}) + f = Face.scoped(:includes => :man, :where => {:description => 'trusting'}).first m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' @@ -286,7 +286,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase m.face.description = 'pleasing' assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance" - f = Face.find(:first, :include => :man, :order => 'men.id', :conditions => {:description => 'trusting'}) + f = Face.scoped(:includes => :man, :order => 'men.id', :where => {:description => 'trusting'}).first m = f.man assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' @@ -331,7 +331,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase end def test_child_instance_should_be_shared_with_replaced_via_accessor_parent - f = Face.find(:first) + f = Face.first m = Man.new(:name => 'Charles') f.man = m assert_not_nil m.face @@ -343,7 +343,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase end def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error - assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_man } + assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_man } end end @@ -351,7 +351,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase fixtures :men, :faces, :interests def test_child_instance_should_be_shared_with_parent_on_find - f = Face.find(:first, :conditions => {:description => 'confused'}) + f = Face.scoped(:where => {:description => 'confused'}).first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' @@ -361,7 +361,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase end def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find - f = Face.find(:first, :conditions => {:description => 'confused'}, :include => :man) + f = Face.scoped(:where => {:description => 'confused'}, :includes => :man).first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' @@ -369,7 +369,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase m.polymorphic_face.description = 'pleasing' assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance" - f = Face.find(:first, :conditions => {:description => 'confused'}, :include => :man, :order => 'men.id') + f = Face.scoped(:where => {:description => 'confused'}, :includes => :man, :order => 'men.id').first m = f.polymorphic_man assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance" f.description = 'gormless' @@ -421,19 +421,19 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase def test_trying_to_access_inverses_that_dont_exist_shouldnt_raise_an_error # Ideally this would, if only for symmetry's sake with other association types - assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_polymorphic_man } + assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man } end def test_trying_to_set_polymorphic_inverses_that_dont_exist_at_all_should_raise_an_error # fails because no class has the correct inverse_of for horrible_polymorphic_man - assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_polymorphic_man = Man.first } + assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man = Man.first } end def test_trying_to_set_polymorphic_inverses_that_dont_exist_on_the_instance_being_set_should_raise_an_error # passes because Man does have the correct inverse_of - assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).polymorphic_man = Man.first } + assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Man.first } # fails because Interest does have the correct inverse_of - assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).polymorphic_man = Interest.first } + assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Interest.first } end end @@ -444,7 +444,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do - i = Interest.find(:first) + i = Interest.first i.zine i.man end @@ -452,7 +452,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase def test_that_we_can_create_associations_that_have_the_same_reciprocal_name_from_different_models assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do - i = Interest.find(:first) + i = Interest.first i.build_zine(:title => 'Get Some in Winter! 2008') i.build_man(:name => 'Gordon') i.save! diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index dde5b26a88..3606ce691c 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -46,12 +46,12 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert !authors(:mary).unique_categorized_posts.loaded? assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count } assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count(:title) } - assert_queries(1) { assert_equal 0, author.unique_categorized_posts.count(:title, :conditions => "title is NULL") } + assert_queries(1) { assert_equal 0, author.unique_categorized_posts.where(title: nil).count(:title) } assert !authors(:mary).unique_categorized_posts.loaded? end def test_has_many_uniq_through_find - assert_equal 1, authors(:mary).unique_categorized_posts.find(:all).size + assert_equal 1, authors(:mary).unique_categorized_posts.all.size end def test_has_many_uniq_through_dynamic_find @@ -71,7 +71,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_polymorphic_has_many_going_through_join_model_with_find - assert_equal tags(:general), tag = posts(:welcome).tags.find(:first) + assert_equal tags(:general), tag = posts(:welcome).tags.first assert_no_queries do tag.tagging end @@ -85,7 +85,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection_with_find - assert_equal tags(:general), tag = posts(:welcome).funky_tags.find(:first) + assert_equal tags(:general), tag = posts(:welcome).funky_tags.first assert_no_queries do tag.tagging end @@ -237,8 +237,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_has_many_through - posts = Post.find(:all, :order => 'posts.id') - posts_with_authors = Post.find(:all, :include => :authors, :order => 'posts.id') + posts = Post.scoped(:order => 'posts.id').all + posts_with_authors = Post.scoped(:includes => :authors, :order => 'posts.id').all assert_equal posts.length, posts_with_authors.length posts.length.times do |i| assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length } @@ -246,7 +246,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_one - post = Post.find_by_id(posts(:welcome).id, :include => :tagging) + post = Post.includes(:tagging).find posts(:welcome).id tagging = taggings(:welcome_general) assert_no_queries do assert_equal tagging, post.tagging @@ -254,7 +254,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_one_defined_in_abstract_parent - item = Item.find_by_id(items(:dvd).id, :include => :tagging) + item = Item.includes(:tagging).find items(:dvd).id tagging = taggings(:godfather) assert_no_queries do assert_equal tagging, item.tagging @@ -262,8 +262,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_many_through - posts = Post.find(:all, :order => 'posts.id') - posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id') + posts = Post.scoped(:order => 'posts.id').all + posts_with_tags = Post.scoped(:includes => :tags, :order => 'posts.id').all assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } @@ -271,8 +271,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_polymorphic_has_many - posts = Post.find(:all, :order => 'posts.id') - posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id') + posts = Post.scoped(:order => 'posts.id').all + posts_with_taggings = Post.scoped(:includes => :taggings, :order => 'posts.id').all assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } @@ -280,20 +280,20 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_find_all - assert_equal [categories(:general)], authors(:david).categories.find(:all) + assert_equal [categories(:general)], authors(:david).categories.all end def test_has_many_find_first - assert_equal categories(:general), authors(:david).categories.find(:first) + assert_equal categories(:general), authors(:david).categories.first end def test_has_many_with_hash_conditions - assert_equal categories(:general), authors(:david).categories_like_general.find(:first) + assert_equal categories(:general), authors(:david).categories_like_general.first end def test_has_many_find_conditions - assert_equal categories(:general), authors(:david).categories.find(:first, :conditions => "categories.name = 'General'") - assert_nil authors(:david).categories.find(:first, :conditions => "categories.name = 'Technology'") + assert_equal categories(:general), authors(:david).categories.scoped(:where => "categories.name = 'General'").first + assert_nil authors(:david).categories.scoped(:where => "categories.name = 'Technology'").first end def test_has_many_class_methods_called_by_method_missing @@ -327,14 +327,6 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order('authors.id') end - def test_both_scoped_and_explicit_joins_should_be_respected - assert_nothing_raised do - Post.send(:with_scope, :find => {:joins => "left outer join comments on comments.id = posts.id"}) do - Post.find :all, :select => "comments.id, authors.id", :joins => "left outer join authors on authors.id = posts.author_id" - end - end - end - def test_belongs_to_polymorphic_with_counter_cache assert_equal 1, posts(:welcome)[:taggings_count] tagging = posts(:welcome).taggings.create(:tag => tags(:general)) @@ -372,7 +364,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_eager_has_many_polymorphic_with_source_type - tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts) + tag_with_include = Tag.scoped(:includes => :tagged_posts).find(tags(:general).id) desired = posts(:welcome, :thinking) assert_no_queries do # added sort by ID as otherwise test using JRuby was failing as array elements were in different order @@ -382,20 +374,20 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_has_many_find_all - assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').first + assert_equal comments(:greetings), authors(:david).comments.scoped(:order => 'comments.id').all.first end def test_has_many_through_has_many_find_all_with_custom_class - assert_equal comments(:greetings), authors(:david).funky_comments.find(:all, :order => 'comments.id').first + assert_equal comments(:greetings), authors(:david).funky_comments.scoped(:order => 'comments.id').all.first end def test_has_many_through_has_many_find_first - assert_equal comments(:greetings), authors(:david).comments.find(:first, :order => 'comments.id') + assert_equal comments(:greetings), authors(:david).comments.scoped(:order => 'comments.id').first end def test_has_many_through_has_many_find_conditions - options = { :conditions => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' } - assert_equal comments(:does_it_hurt), authors(:david).comments.find(:first, options) + options = { :where => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' } + assert_equal comments(:does_it_hurt), authors(:david).comments.scoped(options).first end def test_has_many_through_has_many_find_by_id @@ -411,7 +403,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_include_has_many_through_polymorphic_has_many - author = Author.find_by_id(authors(:david).id, :include => :taggings) + author = Author.includes(:taggings).find authors(:david).id expected_taggings = taggings(:welcome_general, :thinking_general) assert_no_queries do assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id } @@ -419,7 +411,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_eager_load_has_many_through_has_many - author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id' + author = Author.scoped(:where => ['name = ?', 'David'], :includes => :comments, :order => 'comments.id').first SpecialComment.new; VerySpecialComment.new assert_no_queries do assert_equal [1,2,3,5,6,7,8,9,10,12], author.comments.collect(&:id) @@ -427,7 +419,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_eager_load_has_many_through_has_many_with_conditions - post = Post.find(:first, :include => :invalid_tags) + post = Post.scoped(:includes => :invalid_tags).first assert_no_queries do post.invalid_tags end @@ -435,8 +427,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_eager_belongs_to_and_has_one_not_singularized assert_nothing_raised do - Author.find(:first, :include => :author_address) - AuthorAddress.find(:first, :include => :author) + Author.scoped(:includes => :author_address).first + AuthorAddress.scoped(:includes => :author).first end end @@ -452,7 +444,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_uses_conditions_specified_on_the_has_many_association - author = Author.find(:first) + author = Author.first assert_present author.comments assert_blank author.nonexistant_comments end @@ -622,7 +614,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_polymorphic_has_many expected = taggings(:welcome_general) - p = Post.find(posts(:welcome).id, :include => :taggings) + p = Post.scoped(:includes => :taggings).find(posts(:welcome).id) assert_no_queries {assert p.taggings.include?(expected)} assert posts(:welcome).taggings.include?(taggings(:welcome_general)) end @@ -630,18 +622,18 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_polymorphic_has_one expected = posts(:welcome) - tagging = Tagging.find(taggings(:welcome_general).id, :include => :taggable) + tagging = Tagging.scoped(:includes => :taggable).find(taggings(:welcome_general).id) assert_no_queries { assert_equal expected, tagging.taggable} end def test_polymorphic_belongs_to - p = Post.find(posts(:welcome).id, :include => {:taggings => :taggable}) + p = Post.scoped(:includes => {:taggings => :taggable}).find(posts(:welcome).id) assert_no_queries {assert_equal posts(:welcome), p.taggings.first.taggable} end def test_preload_polymorphic_has_many_through - posts = Post.find(:all, :order => 'posts.id') - posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id') + posts = Post.scoped(:order => 'posts.id').all + posts_with_tags = Post.scoped(:includes => :tags, :order => 'posts.id').all assert_equal posts.length, posts_with_tags.length posts.length.times do |i| assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length } @@ -649,7 +641,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_preload_polymorph_many_types - taggings = Tagging.find :all, :include => :taggable, :conditions => ['taggable_type != ?', 'FakeModel'] + taggings = Tagging.scoped(:includes => :taggable, :where => ['taggable_type != ?', 'FakeModel']).all assert_no_queries do taggings.first.taggable.id taggings[1].taggable.id @@ -662,13 +654,13 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_preload_nil_polymorphic_belongs_to assert_nothing_raised do - Tagging.find(:all, :include => :taggable, :conditions => ['taggable_type IS NULL']) + Tagging.scoped(:includes => :taggable, :where => ['taggable_type IS NULL']).all end end def test_preload_polymorphic_has_many - posts = Post.find(:all, :order => 'posts.id') - posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id') + posts = Post.scoped(:order => 'posts.id').all + posts_with_taggings = Post.scoped(:includes => :taggings, :order => 'posts.id').all assert_equal posts.length, posts_with_taggings.length posts.length.times do |i| assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length } @@ -676,7 +668,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_belongs_to_shared_parent - comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 1') + comments = Comment.scoped(:includes => :post, :where => 'post_id = 1').all assert_no_queries do assert_equal comments.first.post, comments[1].post end diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 017905e0ac..1d0550afaf 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -74,8 +74,8 @@ class AssociationsTest < ActiveRecord::TestCase def test_include_with_order_works - assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)} - assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)} + assert_nothing_raised {Account.scoped(:order => 'id', :includes => :firm).first} + assert_nothing_raised {Account.scoped(:order => :id, :includes => :firm).first} end def test_bad_collection_keys @@ -214,6 +214,10 @@ class AssociationProxyTest < ActiveRecord::TestCase david = developers(:david) assert_equal david.association(:projects), david.projects.proxy_association end + + def test_scoped_allows_conditions + assert developers(:david).projects.scoped(where: 'foo').where_values.include?('foo') + end end class OverridingAssociationsTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ef01476ae4..1093fedea1 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -244,7 +244,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase # DB2 is not case-sensitive return true if current_adapter?(:DB2Adapter) - assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes + assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.first.attributes end def test_hashes_not_mangled @@ -481,23 +481,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase end def test_typecast_attribute_from_select_to_false - topic = Topic.create(:title => 'Budget') + Topic.create(:title => 'Budget') # Oracle does not support boolean expressions in SELECT if current_adapter?(:OracleAdapter) - topic = Topic.find(:first, :select => "topics.*, 0 as is_test") + topic = Topic.scoped(:select => "topics.*, 0 as is_test").first else - topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test") + topic = Topic.scoped(:select => "topics.*, 1=2 as is_test").first end assert !topic.is_test? end def test_typecast_attribute_from_select_to_true - topic = Topic.create(:title => 'Budget') + Topic.create(:title => 'Budget') # Oracle does not support boolean expressions in SELECT if current_adapter?(:OracleAdapter) - topic = Topic.find(:first, :select => "topics.*, 1 as is_test") + topic = Topic.scoped(:select => "topics.*, 1 as is_test").first else - topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test") + topic = Topic.scoped(:select => "topics.*, 2=2 as is_test").first end assert topic.is_test? end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index d15487ab11..c93c91803e 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -87,7 +87,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas end def test_save_fails_for_invalid_has_one - firm = Firm.find(:first) + firm = Firm.first assert firm.valid? firm.build_account @@ -99,7 +99,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas end def test_save_succeeds_for_invalid_has_one_with_validate_false - firm = Firm.find(:first) + firm = Firm.first assert firm.valid? firm.build_unvalidated_account @@ -155,20 +155,20 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas end def test_not_resaved_when_unchanged - firm = Firm.find(:first, :include => :account) + firm = Firm.scoped(:includes => :account).first firm.name += '-changed' assert_queries(1) { firm.save! } - firm = Firm.find(:first) - firm.account = Account.find(:first) + firm = Firm.first + firm.account = Account.first assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! } - firm = Firm.find(:first).dup - firm.account = Account.find(:first) + firm = Firm.first.dup + firm.account = Account.first assert_queries(2) { firm.save! } - firm = Firm.find(:first).dup - firm.account = Account.find(:first).dup + firm = Firm.first.dup + firm.account = Account.first.dup assert_queries(2) { firm.save! } end @@ -228,7 +228,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end def test_assignment_before_parent_saved - client = Client.find(:first) + client = Client.first apple = Firm.new("name" => "Apple") client.firm = apple assert_equal apple, client.firm @@ -342,7 +342,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end def test_build_and_then_save_parent_should_not_reload_target - client = Client.find(:first) + client = Client.first apple = client.build_firm(:name => "Apple") client.save! assert_no_queries { assert_equal apple, client.firm } @@ -384,7 +384,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa end def test_invalid_adding_with_validate_false - firm = Firm.find(:first) + firm = Firm.first client = Client.new firm.unvalidated_clients_of_firm << client @@ -397,7 +397,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_valid_adding_with_validate_false no_of_clients = Client.count - firm = Firm.find(:first) + firm = Firm.first client = Client.new("name" => "Apple") assert firm.valid? @@ -627,7 +627,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase def test_a_child_marked_for_destruction_should_not_be_destroyed_twice @pirate.ship.mark_for_destruction assert @pirate.save - @pirate.ship.expects(:destroy).never + class << @pirate.ship + def destroy; raise "Should not be called" end + end assert @pirate.save end @@ -672,7 +674,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase def test_a_parent_marked_for_destruction_should_not_be_destroyed_twice @ship.pirate.mark_for_destruction assert @ship.save - @ship.pirate.expects(:destroy).never + class << @ship.pirate + def destroy; raise "Should not be called" end + end assert @ship.save end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index ef22936e18..8a4ce5e963 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -192,7 +192,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_previously_changed - topic = Topic.find :first + topic = Topic.first topic.title = '<3<3<3' assert_equal({}, topic.previous_changes) @@ -202,7 +202,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_previously_changed_dup - topic = Topic.find :first + topic = Topic.first topic.title = '<3<3<3' topic.save! @@ -228,7 +228,7 @@ class BasicsTest < ActiveRecord::TestCase ) # For adapters which support microsecond resolution. - if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLiteAdapter) + if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLite3Adapter) assert_equal 11, Topic.find(1).written_on.sec assert_equal 223300, Topic.find(1).written_on.usec assert_equal 9900, Topic.find(2).written_on.usec @@ -350,13 +350,13 @@ class BasicsTest < ActiveRecord::TestCase end def test_load - topics = Topic.find(:all, :order => 'id') + topics = Topic.scoped(:order => 'id').all assert_equal(4, topics.size) assert_equal(topics(:first).title, topics.first.title) end def test_load_with_condition - topics = Topic.find(:all, :conditions => "author_name = 'Mary'") + topics = Topic.scoped(:where => "author_name = 'Mary'").all assert_equal(1, topics.size) assert_equal(topics(:second).title, topics.first.title) @@ -478,7 +478,7 @@ class BasicsTest < ActiveRecord::TestCase if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) def test_update_all_with_order_and_limit - assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC') + assert_equal 1, Topic.limit(1).order('id DESC').update_all(:content => 'bulk updated!') end end @@ -1264,10 +1264,10 @@ class BasicsTest < ActiveRecord::TestCase end def test_quoting_arrays - replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ]) + replies = Reply.scoped(:where => [ "id IN (?)", topics(:first).replies.collect(&:id) ]).all assert_equal topics(:first).replies.size, replies.size - replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ]) + replies = Reply.scoped(:where => [ "id IN (?)", [] ]).all assert_equal 0, replies.size end @@ -1568,22 +1568,19 @@ class BasicsTest < ActiveRecord::TestCase def test_count_with_join res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'" - res2 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", :joins => "LEFT JOIN comments ON posts.id=comments.post_id") + res2 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count assert_equal res, res2 res3 = nil assert_nothing_raised do - res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", - :joins => "LEFT JOIN comments ON posts.id=comments.post_id") + res3 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count end assert_equal res, res3 res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" res5 = nil assert_nothing_raised do - res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id", - :joins => "p, comments co", - :select => "p.id") + res5 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count end assert_equal res4, res5 @@ -1591,145 +1588,64 @@ class BasicsTest < ActiveRecord::TestCase res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" res7 = nil assert_nothing_raised do - res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id", - :joins => "p, comments co", - :select => "p.id", - :distinct => true) + res7 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count(distinct: true) end assert_equal res6, res7 end - def test_scoped_find_conditions - scoped_developers = Developer.send(:with_scope, :find => { :conditions => 'salary > 90000' }) do - Developer.find(:all, :conditions => 'id < 5') - end - assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000 - assert_equal 3, scoped_developers.size - end - def test_no_limit_offset assert_nothing_raised do - Developer.find(:all, :offset => 2) - end - end - - def test_scoped_find_limit_offset - scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :offset => 2 }) do - Developer.find(:all, :order => 'id') + Developer.scoped(:offset => 2).all end - assert !scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) - assert_equal 3, scoped_developers.size - - # Test without scoped find conditions to ensure we get the whole thing - developers = Developer.find(:all, :order => 'id') - assert_equal Developer.count, developers.size - end - - def test_scoped_find_order - # Test order in scope - scoped_developers = Developer.send(:with_scope, :find => { :limit => 1, :order => 'salary DESC' }) do - Developer.find(:all) - end - assert_equal 'Jamis', scoped_developers.first.name - assert scoped_developers.include?(developers(:jamis)) - # Test scope without order and order in find - scoped_developers = Developer.send(:with_scope, :find => { :limit => 1 }) do - Developer.find(:all, :order => 'salary DESC') - end - # Test scope order + find order, order has priority - scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :order => 'id DESC' }) do - Developer.find(:all, :order => 'salary ASC') - end - assert scoped_developers.include?(developers(:poor_jamis)) - assert ! scoped_developers.include?(developers(:david)) - assert ! scoped_developers.include?(developers(:jamis)) - assert_equal 3, scoped_developers.size - - # Test without scoped find conditions to ensure we get the right thing - assert ! scoped_developers.include?(Developer.find(1)) - assert scoped_developers.include?(Developer.find(11)) - end - - def test_scoped_find_limit_offset_including_has_many_association - topics = Topic.send(:with_scope, :find => {:limit => 1, :offset => 1, :include => :replies}) do - Topic.find(:all, :order => "topics.id") - end - assert_equal 1, topics.size - assert_equal 2, topics.first.id - end - - def test_scoped_find_order_including_has_many_association - developers = Developer.send(:with_scope, :find => { :order => 'developers.salary DESC', :include => :projects }) do - Developer.find(:all) - end - assert developers.size >= 2 - (1...developers.size).each do |i| - assert developers[i-1].salary >= developers[i].salary - end - end - - def test_scoped_find_with_group_and_having - developers = Developer.send(:with_scope, :find => { :group => 'developers.salary', :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary" }) do - Developer.find(:all) - end - assert_equal 3, developers.size end def test_find_last - last = Developer.find :last - assert_equal last, Developer.find(:first, :order => 'id desc') + last = Developer.last + assert_equal last, Developer.scoped(:order => 'id desc').first end def test_last - assert_equal Developer.find(:first, :order => 'id desc'), Developer.last + assert_equal Developer.scoped(:order => 'id desc').first, Developer.last end def test_all developers = Developer.all assert_kind_of Array, developers - assert_equal Developer.find(:all), developers + assert_equal Developer.all, developers end def test_all_with_conditions - assert_equal Developer.find(:all, :order => 'id desc'), Developer.order('id desc').all + assert_equal Developer.scoped(:order => 'id desc').all, Developer.order('id desc').all end def test_find_ordered_last - last = Developer.find :last, :order => 'developers.salary ASC' - assert_equal last, Developer.find(:all, :order => 'developers.salary ASC').last + last = Developer.scoped(:order => 'developers.salary ASC').last + assert_equal last, Developer.scoped(:order => 'developers.salary ASC').all.last end def test_find_reverse_ordered_last - last = Developer.find :last, :order => 'developers.salary DESC' - assert_equal last, Developer.find(:all, :order => 'developers.salary DESC').last + last = Developer.scoped(:order => 'developers.salary DESC').last + assert_equal last, Developer.scoped(:order => 'developers.salary DESC').all.last end def test_find_multiple_ordered_last - last = Developer.find :last, :order => 'developers.name, developers.salary DESC' - assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last + last = Developer.scoped(:order => 'developers.name, developers.salary DESC').last + assert_equal last, Developer.scoped(:order => 'developers.name, developers.salary DESC').all.last end def test_find_keeps_multiple_order_values - combined = Developer.find(:all, :order => 'developers.name, developers.salary') - assert_equal combined, Developer.find(:all, :order => ['developers.name', 'developers.salary']) + combined = Developer.scoped(:order => 'developers.name, developers.salary').all + assert_equal combined, Developer.scoped(:order => ['developers.name', 'developers.salary']).all end def test_find_keeps_multiple_group_values - combined = Developer.find(:all, :group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at') - assert_equal combined, Developer.find(:all, :group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at']) + combined = Developer.scoped(:group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at').all + assert_equal combined, Developer.scoped(:group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at']).all end def test_find_symbol_ordered_last - last = Developer.find :last, :order => :salary - assert_equal last, Developer.find(:all, :order => :salary).last - end - - def test_find_scoped_ordered_last - last_developer = Developer.send(:with_scope, :find => { :order => 'developers.salary ASC' }) do - Developer.find(:last) - end - assert_equal last_developer, Developer.find(:all, :order => 'developers.salary ASC').last + last = Developer.scoped(:order => :salary).last + assert_equal last, Developer.scoped(:order => :salary).all.last end def test_abstract_class @@ -1804,7 +1720,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_to_param_should_return_string - assert_kind_of String, Client.find(:first).to_param + assert_kind_of String, Client.first.to_param end def test_inspect_class @@ -1823,8 +1739,8 @@ class BasicsTest < ActiveRecord::TestCase end def test_inspect_limited_select_instance - assert_equal %(#<Topic id: 1>), Topic.find(:first, :select => 'id', :conditions => 'id = 1').inspect - assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.find(:first, :select => 'id, title', :conditions => 'id = 1').inspect + assert_equal %(#<Topic id: 1>), Topic.scoped(:select => 'id', :where => 'id = 1').first.inspect + assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.scoped(:select => 'id, title', :where => 'id = 1').first.inspect end def test_inspect_class_without_table @@ -2087,4 +2003,9 @@ class BasicsTest < ActiveRecord::TestCase assert_equal record, klass.public_send(meth, :foo, :bar) end end + + test "scoped can take a values hash" do + klass = Class.new(ActiveRecord::Base) + assert_equal ['foo'], klass.scoped(select: 'foo').select_values + end end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 660098b9ad..cdd4b49042 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -27,30 +27,18 @@ class EachTest < ActiveRecord::TestCase def test_each_should_raise_if_select_is_set_without_id assert_raise(RuntimeError) do - Post.find_each(:select => :title, :batch_size => 1) { |post| post } + Post.select(:title).find_each(:batch_size => 1) { |post| post } end end def test_each_should_execute_if_id_is_in_select assert_queries(6) do - Post.find_each(:select => "id, title, type", :batch_size => 2) do |post| + Post.select("id, title, type").find_each(:batch_size => 2) do |post| assert_kind_of Post, post end end end - def test_each_should_raise_if_the_order_is_set - assert_raise(RuntimeError) do - Post.find_each(:order => "title") { |post| post } - end - end - - def test_each_should_raise_if_the_limit_is_set - assert_raise(RuntimeError) do - Post.find_each(:limit => 1) { |post| post } - end - end - def test_warn_if_limit_scope_is_set ActiveRecord::Base.logger.expects(:warn) Post.limit(1).find_each { |post| post } diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 48cb05e36e..2e566ff873 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -52,23 +52,17 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 55, Account.where("companies.name != 'Summit'").references(:companies).includes(:firm).maximum(:credit_limit) end - def test_should_get_maximum_of_field_with_scoped_include - Account.send :with_scope, :find => { :include => :firm } do - assert_equal 55, Account.where("companies.name != 'Summit'").references(:companies).maximum(:credit_limit) - end - end - def test_should_get_minimum_of_field assert_equal 50, Account.minimum(:credit_limit) end def test_should_group_by_field - c = Account.sum(:credit_limit, :group => :firm_id) + c = Account.group(:firm_id).sum(:credit_limit) [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) } end def test_should_group_by_multiple_fields - c = Account.count(:all, :group => ['firm_id', :credit_limit]) + c = Account.group('firm_id', :credit_limit).count(:all) [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) } end @@ -81,32 +75,32 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_summed_field - c = Account.sum(:credit_limit, :group => :firm_id) + c = Account.group(:firm_id).sum(:credit_limit) assert_equal 50, c[1] assert_equal 105, c[6] assert_equal 60, c[2] end def test_should_order_by_grouped_field - c = Account.sum(:credit_limit, :group => :firm_id, :order => "firm_id") + c = Account.scoped(:group => :firm_id, :order => "firm_id").sum(:credit_limit) assert_equal [1, 2, 6, 9], c.keys.compact end def test_should_order_by_calculation - c = Account.sum(:credit_limit, :group => :firm_id, :order => "sum_credit_limit desc, firm_id") + c = Account.scoped(:group => :firm_id, :order => "sum_credit_limit desc, firm_id").sum(:credit_limit) assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] } assert_equal [6, 2, 9, 1], c.keys.compact end def test_should_limit_calculation - c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL", - :group => :firm_id, :order => "firm_id", :limit => 2) + c = Account.scoped(:where => "firm_id IS NOT NULL", + :group => :firm_id, :order => "firm_id", :limit => 2).sum(:credit_limit) assert_equal [1, 2], c.keys.compact end def test_should_limit_calculation_with_offset - c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL", - :group => :firm_id, :order => "firm_id", :limit => 2, :offset => 1) + c = Account.scoped(:where => "firm_id IS NOT NULL", :group => :firm_id, + :order => "firm_id", :limit => 2, :offset => 1).sum(:credit_limit) assert_equal [2, 6], c.keys.compact end @@ -156,16 +150,8 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_summed_field_having_condition - c = Account.sum(:credit_limit, :group => :firm_id, - :having => 'sum(credit_limit) > 50') - assert_nil c[1] - assert_equal 105, c[6] - assert_equal 60, c[2] - end - - def test_should_group_by_summed_field_having_sanitized_condition - c = Account.sum(:credit_limit, :group => :firm_id, - :having => ['sum(credit_limit) > ?', 50]) + c = Account.scoped(:group => :firm_id, + :having => 'sum(credit_limit) > 50').sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_equal 60, c[2] @@ -179,19 +165,19 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_summed_association - c = Account.sum(:credit_limit, :group => :firm) + c = Account.group(:firm).sum(:credit_limit) assert_equal 50, c[companies(:first_firm)] assert_equal 105, c[companies(:rails_core)] assert_equal 60, c[companies(:first_client)] end def test_should_sum_field_with_conditions - assert_equal 105, Account.sum(:credit_limit, :conditions => 'firm_id = 6') + assert_equal 105, Account.where('firm_id = 6').sum(:credit_limit) end def test_should_return_zero_if_sum_conditions_return_nothing - assert_equal 0, Account.sum(:credit_limit, :conditions => '1 = 2') - assert_equal 0, companies(:rails_core).companies.sum(:id, :conditions => '1 = 2') + assert_equal 0, Account.where('1 = 2').sum(:credit_limit) + assert_equal 0, companies(:rails_core).companies.where('1 = 2').sum(:id) end def test_sum_should_return_valid_values_for_decimals @@ -200,24 +186,24 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_group_by_summed_field_with_conditions - c = Account.sum(:credit_limit, :conditions => 'firm_id > 1', - :group => :firm_id) + c = Account.scoped(:where => 'firm_id > 1', + :group => :firm_id).sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_equal 60, c[2] end def test_should_group_by_summed_field_with_conditions_and_having - c = Account.sum(:credit_limit, :conditions => 'firm_id > 1', - :group => :firm_id, - :having => 'sum(credit_limit) > 60') + c = Account.scoped(:where => 'firm_id > 1', + :group => :firm_id, + :having => 'sum(credit_limit) > 60').sum(:credit_limit) assert_nil c[1] assert_equal 105, c[6] assert_nil c[2] end def test_should_group_by_fields_with_table_alias - c = Account.sum(:credit_limit, :group => 'accounts.firm_id') + c = Account.group('accounts.firm_id').sum(:credit_limit) assert_equal 50, c[1] assert_equal 105, c[6] assert_equal 60, c[2] @@ -229,14 +215,14 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_calculate_grouped_with_invalid_field - c = Account.count(:all, :group => 'accounts.firm_id') + c = Account.group('accounts.firm_id').count(:all) assert_equal 1, c[1] assert_equal 2, c[6] assert_equal 1, c[2] end def test_should_calculate_grouped_association_with_invalid_field - c = Account.count(:all, :group => :firm) + c = Account.group(:firm).count(:all) assert_equal 1, c[companies(:first_firm)] assert_equal 2, c[companies(:rails_core)] assert_equal 1, c[companies(:first_client)] @@ -255,7 +241,7 @@ class CalculationsTest < ActiveRecord::TestCase column.expects(:type_cast).with("ABC").returns("ABC") Account.expects(:columns).at_least_once.returns([column]) - c = Account.count(:all, :group => :firm) + c = Account.group(:firm).count(:all) first_key = c.keys.first assert_equal Firm, first_key.class assert_equal 1, c[first_key] @@ -263,22 +249,14 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_calculate_grouped_association_with_foreign_key_option Account.belongs_to :another_firm, :class_name => 'Firm', :foreign_key => 'firm_id' - c = Account.count(:all, :group => :another_firm) + c = Account.group(:another_firm).count(:all) assert_equal 1, c[companies(:first_firm)] assert_equal 2, c[companies(:rails_core)] assert_equal 1, c[companies(:first_client)] end - def test_should_not_modify_options_when_using_includes - options = {:conditions => 'companies.id > 1', :include => :firm} - options_copy = options.dup - - Account.references(:companies).count(:all, options) - assert_equal options_copy, options - end - def test_should_calculate_grouped_by_function - c = Company.count(:all, :group => "UPPER(#{QUOTED_TYPE})") + c = Company.group("UPPER(#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] assert_equal 1, c['DEPENDENTFIRM'] assert_equal 4, c['CLIENT'] @@ -286,7 +264,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_calculate_grouped_by_function_with_table_alias - c = Company.count(:all, :group => "UPPER(companies.#{QUOTED_TYPE})") + c = Company.group("UPPER(companies.#{QUOTED_TYPE})").count(:all) assert_equal 2, c[nil] assert_equal 1, c['DEPENDENTFIRM'] assert_equal 4, c['CLIENT'] @@ -306,25 +284,24 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_sum_scoped_field_with_conditions - assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7') + assert_equal 8, companies(:rails_core).companies.where('id > 7').sum(:id) end def test_should_group_by_scoped_field - c = companies(:rails_core).companies.sum(:id, :group => :name) + c = companies(:rails_core).companies.group(:name).sum(:id) assert_equal 7, c['Leetsoft'] assert_equal 8, c['Jadedpixel'] end def test_should_group_by_summed_field_through_association_and_having - c = companies(:rails_core).companies.sum(:id, :group => :name, - :having => 'sum(id) > 7') + c = companies(:rails_core).companies.group(:name).having('sum(id) > 7').sum(:id) assert_nil c['Leetsoft'] assert_equal 8, c['Jadedpixel'] 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) + assert_equal 6, Account.includes(:firm).count(:distinct => true) + assert_equal 4, Account.includes(:firm).select(:credit_limit).count(:distinct => true) end def test_should_not_perform_joined_include_by_default @@ -348,11 +325,11 @@ class CalculationsTest < ActiveRecord::TestCase Account.last.update_column('credit_limit', 49) Account.first.update_column('credit_limit', 51) - assert_equal 1, Account.scoped(:select => "credit_limit").count(:conditions => ['credit_limit >= 50']) + assert_equal 1, Account.scoped(:select => "credit_limit").where('credit_limit >= 50').count end def test_should_count_manual_select_with_include - assert_equal 6, Account.count(:select => "DISTINCT accounts.id", :include => :firm) + assert_equal 6, Account.scoped(:select => "DISTINCT accounts.id", :includes => :firm).count end def test_count_with_column_parameter @@ -360,16 +337,16 @@ class CalculationsTest < ActiveRecord::TestCase end def test_count_with_column_and_options_parameter - assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50 AND firm_id IS NOT NULL") + assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id) end def test_should_count_field_in_joined_table - assert_equal 5, Account.count('companies.id', :joins => :firm) - assert_equal 4, Account.count('companies.id', :joins => :firm, :distinct => true) + assert_equal 5, Account.joins(:firm).count('companies.id') + assert_equal 4, Account.joins(:firm).count('companies.id', :distinct => true) end def test_should_count_field_in_joined_table_with_group_by - c = Account.count('companies.id', :group => 'accounts.firm_id', :joins => :firm) + c = Account.scoped(:group => 'accounts.firm_id', :joins => :firm).count('companies.id') [1,6,2,9].each { |firm_id| assert c.keys.include?(firm_id) } end @@ -392,17 +369,17 @@ class CalculationsTest < ActiveRecord::TestCase end def test_count_with_from_option - assert_equal Company.count(:all), Company.count(:all, :from => 'companies') - assert_equal Account.count(:all, :conditions => "credit_limit = 50"), - Account.count(:all, :from => 'accounts', :conditions => "credit_limit = 50") - assert_equal Company.count(:type, :conditions => {:type => "Firm"}), - Company.count(:type, :conditions => {:type => "Firm"}, :from => 'companies') + assert_equal Company.count(:all), Company.from('companies').count(:all) + assert_equal Account.where("credit_limit = 50").count(:all), + Account.from('accounts').where("credit_limit = 50").count(:all) + assert_equal Company.where(:type => "Firm").count(:type), + Company.where(:type => "Firm").from('companies').count(:type) end def test_sum_with_from_option - assert_equal Account.sum(:credit_limit), Account.sum(:credit_limit, :from => 'accounts') - assert_equal Account.sum(:credit_limit, :conditions => "credit_limit > 50"), - Account.sum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") + assert_equal Account.sum(:credit_limit), Account.from('accounts').sum(:credit_limit) + assert_equal Account.where("credit_limit > 50").sum(:credit_limit), + Account.where("credit_limit > 50").from('accounts').sum(:credit_limit) end def test_sum_array_compatibility @@ -410,33 +387,33 @@ class CalculationsTest < ActiveRecord::TestCase end def test_average_with_from_option - assert_equal Account.average(:credit_limit), Account.average(:credit_limit, :from => 'accounts') - assert_equal Account.average(:credit_limit, :conditions => "credit_limit > 50"), - Account.average(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") + assert_equal Account.average(:credit_limit), Account.from('accounts').average(:credit_limit) + assert_equal Account.where("credit_limit > 50").average(:credit_limit), + Account.where("credit_limit > 50").from('accounts').average(:credit_limit) end def test_minimum_with_from_option - assert_equal Account.minimum(:credit_limit), Account.minimum(:credit_limit, :from => 'accounts') - assert_equal Account.minimum(:credit_limit, :conditions => "credit_limit > 50"), - Account.minimum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") + assert_equal Account.minimum(:credit_limit), Account.from('accounts').minimum(:credit_limit) + assert_equal Account.where("credit_limit > 50").minimum(:credit_limit), + Account.where("credit_limit > 50").from('accounts').minimum(:credit_limit) end def test_maximum_with_from_option - assert_equal Account.maximum(:credit_limit), Account.maximum(:credit_limit, :from => 'accounts') - assert_equal Account.maximum(:credit_limit, :conditions => "credit_limit > 50"), - Account.maximum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50") + assert_equal Account.maximum(:credit_limit), Account.from('accounts').maximum(:credit_limit) + assert_equal Account.where("credit_limit > 50").maximum(:credit_limit), + Account.where("credit_limit > 50").from('accounts').maximum(:credit_limit) end def test_from_option_with_specified_index if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2' - assert_equal Edge.count(:all), Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)') - assert_equal Edge.count(:all, :conditions => 'sink_id < 5'), - Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)', :conditions => 'sink_id < 5') + assert_equal Edge.count(:all), Edge.from('edges USE INDEX(unique_edge_index)').count(:all) + assert_equal Edge.where('sink_id < 5').count(:all), + Edge.from('edges USE INDEX(unique_edge_index)').where('sink_id < 5').count(:all) end end def test_from_option_with_table_different_than_class - assert_equal Account.count(:all), Company.count(:all, :from => 'accounts') + assert_equal Account.count(:all), Company.from('accounts').count(:all) end def test_distinct_is_honored_when_used_with_count_operation_after_group diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index da93500ce3..8dc9f761c2 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -96,6 +96,30 @@ module ActiveRecord end end + def test_full_pool_blocks + cs = @pool.size.times.map { @pool.checkout } + t = Thread.new { @pool.checkout } + + # make sure our thread is in the timeout section + Thread.pass until t.status == "sleep" + + connection = cs.first + connection.close + assert_equal connection, t.join.value + end + + def test_removing_releases_latch + cs = @pool.size.times.map { @pool.checkout } + t = Thread.new { @pool.checkout } + + # make sure our thread is in the timeout section + Thread.pass until t.status == "sleep" + + connection = cs.first + @pool.remove connection + assert_respond_to t.join.value, :execute + end + def test_reap_and_active @pool.checkout @pool.checkout diff --git a/activerecord/test/cases/custom_locking_test.rb b/activerecord/test/cases/custom_locking_test.rb index d63ecdbcc5..42ef51ef3e 100644 --- a/activerecord/test/cases/custom_locking_test.rb +++ b/activerecord/test/cases/custom_locking_test.rb @@ -9,7 +9,7 @@ module ActiveRecord if current_adapter?(:MysqlAdapter, :Mysql2Adapter) assert_match 'SHARE MODE', Person.lock('LOCK IN SHARE MODE').to_sql assert_sql(/LOCK IN SHARE MODE/) do - Person.find(1, :lock => 'LOCK IN SHARE MODE') + Person.scoped(:lock => 'LOCK IN SHARE MODE').find(1) end end end diff --git a/activerecord/test/cases/deprecated_finder_test.rb b/activerecord/test/cases/deprecated_finder_test.rb deleted file mode 100644 index 2afc91b769..0000000000 --- a/activerecord/test/cases/deprecated_finder_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require "cases/helper" -require 'models/entrant' - -class DeprecatedFinderTest < ActiveRecord::TestCase - fixtures :entrants - - def test_deprecated_find_all_was_removed - assert_raise(NoMethodError) { Entrant.find_all } - end - - def test_deprecated_find_first_was_removed - assert_raise(NoMethodError) { Entrant.find_first } - end - - def test_deprecated_find_on_conditions_was_removed - assert_raise(NoMethodError) { Entrant.find_on_conditions } - end - - def test_count - assert_equal(0, Entrant.count(:conditions => "id > 3")) - assert_equal(1, Entrant.count(:conditions => ["id > ?", 2])) - assert_equal(2, Entrant.count(:conditions => ["id > ?", 1])) - end - - def test_count_by_sql - assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3")) - assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2])) - assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1])) - end -end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 54e0b40b4f..2650040a80 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -288,7 +288,7 @@ class DirtyTest < ActiveRecord::TestCase with_partial_updates Pirate, false do assert_queries(2) { 2.times { pirate.save! } } - Pirate.update_all({ :updated_on => old_updated_on }, :id => pirate.id) + Pirate.where(id: pirate.id).update_all(:updated_on => old_updated_on) end with_partial_updates Pirate, true do @@ -306,7 +306,7 @@ class DirtyTest < ActiveRecord::TestCase with_partial_updates Person, false do assert_queries(2) { 2.times { person.save! } } - Person.update_all({ :first_name => 'baz' }, :id => person.id) + Person.where(id: person.id).update_all(:first_name => 'baz') end with_partial_updates Person, true do diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index 83c9b6e107..cb7781f8e7 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -28,7 +28,9 @@ if ActiveRecord::Base.connection.supports_explain? original = base.logger base.logger = nil - base.logger.expects(:warn).never + class << base.logger + def warn; raise "Should not be called" end + end with_threshold(0) do car = Car.where(:name => 'honda').first diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 70f3c4bcc5..29469c42ed 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -82,12 +82,6 @@ class FinderTest < ActiveRecord::TestCase Address.new(existing_address.street + "1", existing_address.city, existing_address.country)) end - def test_exists_with_scoped_include - Developer.send(:with_scope, :find => { :include => :projects, :order => "projects.name" }) do - assert Developer.exists? - end - end - def test_exists_does_not_instantiate_records Developer.expects(:instantiate).never Developer.exists? @@ -104,14 +98,14 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_ids_with_limit_and_offset - assert_equal 2, Entrant.find([1,3,2], :limit => 2).size - assert_equal 1, Entrant.find([1,3,2], :limit => 3, :offset => 2).size + assert_equal 2, Entrant.scoped(:limit => 2).find([1,3,2]).size + assert_equal 1, Entrant.scoped(:limit => 3, :offset => 2).find([1,3,2]).size # Also test an edge case: If you have 11 results, and you set a # limit of 3 and offset of 9, then you should find that there # will be only 2 results, regardless of the limit. - devs = Developer.find :all - last_devs = Developer.find devs.map(&:id), :limit => 3, :offset => 9 + devs = Developer.all + last_devs = Developer.scoped(:limit => 3, :offset => 9).find devs.map(&:id) assert_equal 2, last_devs.size end @@ -123,56 +117,6 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) } end - def test_find_all_with_limit - assert_equal(2, Entrant.find(:all, :limit => 2).size) - assert_equal(0, Entrant.find(:all, :limit => 0).size) - end - - def test_find_all_with_prepared_limit_and_offset - entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 1) - - assert_equal(2, entrants.size) - assert_equal(entrants(:second).name, entrants.first.name) - - assert_equal 3, Entrant.count - entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2) - assert_equal(1, entrants.size) - assert_equal(entrants(:third).name, entrants.first.name) - end - - def test_find_all_with_limit_and_offset_and_multiple_order_clauses - first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0 - second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3 - third_three_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 - last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 9 - - assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[2,7],[2,9],[2,11]], third_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[3,8],[3,10]], last_posts.map { |p| [p.author_id, p.id] } - end - - - def test_find_with_group - developers = Developer.find(:all, :group => "salary", :select => "salary") - assert_equal 4, developers.size - assert_equal 4, developers.map(&:salary).uniq.size - end - - def test_find_with_group_and_having - developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary") - assert_equal 3, developers.size - assert_equal 3, developers.map(&:salary).uniq.size - assert developers.all? { |developer| developer.salary > 10000 } - end - - def test_find_with_group_and_sanitized_having - developers = Developer.find(:all, :group => "salary", :having => ["sum(salary) > ?", 10000], :select => "salary") - assert_equal 3, developers.size - assert_equal 3, developers.map(&:salary).uniq.size - assert developers.all? { |developer| developer.salary > 10000 } - end - def test_find_with_group_and_sanitized_having_method developers = Developer.group(:salary).having("sum(salary) > ?", 10000).select('salary').all assert_equal 3, developers.size @@ -199,16 +143,6 @@ class FinderTest < ActiveRecord::TestCase assert_equal [Account], accounts.collect(&:class).uniq end - def test_find_first - first = Topic.find(:first, :conditions => "title = 'The First Topic'") - assert_equal(topics(:first).title, first.title) - end - - def test_find_first_failing - first = Topic.find(:first, :conditions => "title = 'The First Topic!'") - assert_nil(first) - end - def test_first assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title end @@ -292,7 +226,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_only_some_columns - topic = Topic.find(1, :select => "author_name") + topic = Topic.scoped(:select => "author_name").find(1) assert_raise(ActiveModel::MissingAttributeError) {topic.title} assert_nil topic.read_attribute("title") assert_equal "David", topic.author_name @@ -302,36 +236,24 @@ class FinderTest < ActiveRecord::TestCase assert_respond_to topic, "author_name" end - def test_find_on_blank_conditions - [nil, " ", [], {}].each do |blank| - assert_nothing_raised { Topic.find(:first, :conditions => blank) } - end - end - - def test_find_on_blank_bind_conditions - [ [""], ["",{}] ].each do |blank| - assert_nothing_raised { Topic.find(:first, :conditions => blank) } - end - end - def test_find_on_array_conditions - assert Topic.find(1, :conditions => ["approved = ?", false]) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) } + assert Topic.scoped(:where => ["approved = ?", false]).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => ["approved = ?", true]).find(1) } end def test_find_on_hash_conditions - assert Topic.find(1, :conditions => { :approved => false }) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) } + assert Topic.scoped(:where => { :approved => false }).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :approved => true }).find(1) } end def test_find_on_hash_conditions_with_explicit_table_name - assert Topic.find(1, :conditions => { 'topics.approved' => false }) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) } + assert Topic.scoped(:where => { 'topics.approved' => false }).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { 'topics.approved' => true }).find(1) } end def test_find_on_hash_conditions_with_hashed_table_name - assert Topic.find(1, :conditions => {:topics => { :approved => false }}) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => {:topics => { :approved => true }}) } + assert Topic.scoped(:where => {:topics => { :approved => false }}).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => {:topics => { :approved => true }}).find(1) } end def test_find_with_hash_conditions_on_joined_table @@ -341,16 +263,16 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_hash_conditions_on_joined_table_and_with_range - firms = DependentFirm.all :joins => :account, :conditions => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }} + firms = DependentFirm.scoped :joins => :account, :where => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }} assert_equal 1, firms.size assert_equal companies(:rails_core), firms.first end def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate david = customers(:david) - assert Customer.find(david.id, :conditions => { 'customers.name' => david.name, :address => david.address }) + assert Customer.scoped(:where => { 'customers.name' => david.name, :address => david.address }).find(david.id) assert_raise(ActiveRecord::RecordNotFound) { - Customer.find(david.id, :conditions => { 'customers.name' => david.name + "1", :address => david.address }) + Customer.scoped(:where => { 'customers.name' => david.name + "1", :address => david.address }).find(david.id) } end @@ -359,71 +281,71 @@ class FinderTest < ActiveRecord::TestCase end def test_find_on_hash_conditions_with_range - assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1..2 }).map(&:id).sort - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) } + assert_equal [1,2], Topic.scoped(:where => { :id => 1..2 }).all.map(&:id).sort + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :id => 2..3 }).find(1) } end def test_find_on_hash_conditions_with_end_exclusive_range - assert_equal [1,2,3], Topic.find(:all, :conditions => { :id => 1..3 }).map(&:id).sort - assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1...3 }).map(&:id).sort - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(3, :conditions => { :id => 2...3 }) } + assert_equal [1,2,3], Topic.scoped(:where => { :id => 1..3 }).all.map(&:id).sort + assert_equal [1,2], Topic.scoped(:where => { :id => 1...3 }).all.map(&:id).sort + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :id => 2...3 }).find(3) } end def test_find_on_hash_conditions_with_multiple_ranges - assert_equal [1,2,3], Comment.find(:all, :conditions => { :id => 1..3, :post_id => 1..2 }).map(&:id).sort - assert_equal [1], Comment.find(:all, :conditions => { :id => 1..1, :post_id => 1..10 }).map(&:id).sort + assert_equal [1,2,3], Comment.scoped(:where => { :id => 1..3, :post_id => 1..2 }).all.map(&:id).sort + assert_equal [1], Comment.scoped(:where => { :id => 1..1, :post_id => 1..10 }).all.map(&:id).sort end def test_find_on_hash_conditions_with_array_of_integers_and_ranges - assert_equal [1,2,3,5,6,7,8,9], Comment.find(:all, :conditions => {:id => [1..2, 3, 5, 6..8, 9]}).map(&:id).sort + assert_equal [1,2,3,5,6,7,8,9], Comment.scoped(:where => {:id => [1..2, 3, 5, 6..8, 9]}).all.map(&:id).sort end def test_find_on_multiple_hash_conditions - assert Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false }) - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) } - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) } - assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) } + assert Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false }).find(1) + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }).find(1) } + assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) } end def test_condition_interpolation - assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"]) - assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"]) - assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"]) - assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on + assert_kind_of Firm, Company.where("name = '%s'", "37signals").first + assert_nil Company.scoped(:where => ["name = '%s'", "37signals!"]).first + assert_nil Company.scoped(:where => ["name = '%s'", "37signals!' OR 1=1"]).first + assert_kind_of Time, Topic.scoped(:where => ["id = %d", 1]).first.written_on end def test_condition_array_interpolation - assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"]) - assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"]) - assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"]) - assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on + assert_kind_of Firm, Company.scoped(:where => ["name = '%s'", "37signals"]).first + assert_nil Company.scoped(:where => ["name = '%s'", "37signals!"]).first + assert_nil Company.scoped(:where => ["name = '%s'", "37signals!' OR 1=1"]).first + assert_kind_of Time, Topic.scoped(:where => ["id = %d", 1]).first.written_on end def test_condition_hash_interpolation - assert_kind_of Firm, Company.find(:first, :conditions => { :name => "37signals"}) - assert_nil Company.find(:first, :conditions => { :name => "37signals!"}) - assert_kind_of Time, Topic.find(:first, :conditions => {:id => 1}).written_on + assert_kind_of Firm, Company.scoped(:where => { :name => "37signals"}).first + assert_nil Company.scoped(:where => { :name => "37signals!"}).first + assert_kind_of Time, Topic.scoped(:where => {:id => 1}).first.written_on end def test_hash_condition_find_malformed assert_raise(ActiveRecord::StatementInvalid) { - Company.find(:first, :conditions => { :id => 2, :dhh => true }) + Company.scoped(:where => { :id => 2, :dhh => true }).first } end def test_hash_condition_find_with_escaped_characters Company.create("name" => "Ain't noth'n like' \#stuff") - assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff" }) + assert Company.scoped(:where => { :name => "Ain't noth'n like' \#stuff" }).first end def test_hash_condition_find_with_array - p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc') - assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2] }, :order => 'id asc') - assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2.id] }, :order => 'id asc') + p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').all + assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2] }, :order => 'id asc').all + assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2.id] }, :order => 'id asc').all end def test_hash_condition_find_with_nil - topic = Topic.find(:first, :conditions => { :last_read => nil } ) + topic = Topic.scoped(:where => { :last_read => nil } ).first assert_not_nil topic assert_nil topic.last_read end @@ -431,42 +353,42 @@ class FinderTest < ActiveRecord::TestCase def test_hash_condition_find_with_aggregate_having_one_mapping balance = customers(:david).balance assert_kind_of Money, balance - found_customer = Customer.find(:first, :conditions => {:balance => balance}) + found_customer = Customer.scoped(:where => {:balance => balance}).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate gps_location = customers(:david).gps_location assert_kind_of GpsLocation, gps_location - found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location}) + found_customer = Customer.scoped(:where => {:gps_location => gps_location}).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value balance = customers(:david).balance assert_kind_of Money, balance - found_customer = Customer.find(:first, :conditions => {:balance => balance.amount}) + found_customer = Customer.scoped(:where => {:balance => balance.amount}).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value gps_location = customers(:david).gps_location assert_kind_of GpsLocation, gps_location - found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location.gps_location}) + found_customer = Customer.scoped(:where => {:gps_location => gps_location.gps_location}).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_aggregate_having_three_mappings address = customers(:david).address assert_kind_of Address, address - found_customer = Customer.find(:first, :conditions => {:address => address}) + found_customer = Customer.scoped(:where => {:address => address}).first assert_equal customers(:david), found_customer end def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not address = customers(:david).address assert_kind_of Address, address - found_customer = Customer.find(:first, :conditions => {:address => address, :name => customers(:david).name}) + found_customer = Customer.scoped(:where => {:address => address, :name => customers(:david).name}).first assert_equal customers(:david), found_customer end @@ -474,7 +396,7 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_active_record_default_timezone :local do topic = Topic.first - assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getutc]) + assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getutc]).first end end end @@ -483,7 +405,7 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_active_record_default_timezone :local do topic = Topic.first - assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getutc}) + assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getutc}).first end end end @@ -492,7 +414,7 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_active_record_default_timezone :utc do topic = Topic.first - assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getlocal]) + assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getlocal]).first end end end @@ -501,32 +423,32 @@ class FinderTest < ActiveRecord::TestCase with_env_tz 'America/New_York' do with_active_record_default_timezone :utc do topic = Topic.first - assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getlocal}) + assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getlocal}).first end end end def test_bind_variables - assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"]) - assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"]) - assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!' OR 1=1"]) - assert_kind_of Time, Topic.find(:first, :conditions => ["id = ?", 1]).written_on + assert_kind_of Firm, Company.scoped(:where => ["name = ?", "37signals"]).first + assert_nil Company.scoped(:where => ["name = ?", "37signals!"]).first + assert_nil Company.scoped(:where => ["name = ?", "37signals!' OR 1=1"]).first + assert_kind_of Time, Topic.scoped(:where => ["id = ?", 1]).first.written_on assert_raise(ActiveRecord::PreparedStatementInvalid) { - Company.find(:first, :conditions => ["id=? AND name = ?", 2]) + Company.scoped(:where => ["id=? AND name = ?", 2]).first } assert_raise(ActiveRecord::PreparedStatementInvalid) { - Company.find(:first, :conditions => ["id=?", 2, 3, 4]) + Company.scoped(:where => ["id=?", 2, 3, 4]).first } end def test_bind_variables_with_quotes Company.create("name" => "37signals' go'es agains") - assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"]) + assert Company.scoped(:where => ["name = ?", "37signals' go'es agains"]).first end def test_named_bind_variables_with_quotes Company.create("name" => "37signals' go'es agains") - assert Company.find(:first, :conditions => ["name = :name", {:name => "37signals' go'es agains"}]) + assert Company.scoped(:where => ["name = :name", {:name => "37signals' go'es agains"}]).first end def test_bind_arity @@ -544,10 +466,10 @@ class FinderTest < ActiveRecord::TestCase assert_nothing_raised { bind("'+00:00'", :foo => "bar") } - assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }]) - assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!" }]) - assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!' OR 1=1" }]) - assert_kind_of Time, Topic.find(:first, :conditions => ["id = :id", { :id => 1 }]).written_on + assert_kind_of Firm, Company.scoped(:where => ["name = :name", { :name => "37signals" }]).first + assert_nil Company.scoped(:where => ["name = :name", { :name => "37signals!" }]).first + assert_nil Company.scoped(:where => ["name = :name", { :name => "37signals!' OR 1=1" }]).first + assert_kind_of Time, Topic.scoped(:where => ["id = :id", { :id => 1 }]).first.written_on end class SimpleEnumerable @@ -618,12 +540,6 @@ class FinderTest < ActiveRecord::TestCase assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table") end - def test_count - assert_equal(0, Entrant.count(:conditions => "id > 3")) - assert_equal(1, Entrant.count(:conditions => ["id > ?", 2])) - assert_equal(2, Entrant.count(:conditions => ["id > ?", 1])) - end - def test_count_by_sql assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3")) assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2])) @@ -646,7 +562,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_one_attribute_with_conditions - assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) + assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50) end def test_find_by_one_attribute_that_is_an_aggregate @@ -686,12 +602,12 @@ class FinderTest < ActiveRecord::TestCase def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching # ensure this test can run independently of order class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' } - a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) - assert_equal a, Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) # find_by_credit_limit has been cached + a = Account.where('firm_id = ?', 6).find_by_credit_limit(50) + assert_equal a, Account.where('firm_id = ?', 6).find_by_credit_limit(50) # find_by_credit_limit has been cached end def test_find_by_one_attribute_with_several_options - assert_equal accounts(:unknown), Account.find_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3]) + assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50) end def test_find_by_one_missing_attribute @@ -725,7 +641,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_last_by_one_attribute_with_several_options - assert_equal accounts(:signals37), Account.find_last_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3]) + assert_equal accounts(:signals37), Account.order('id DESC').where('id != ?', 3).find_last_by_credit_limit(50) end def test_find_last_by_one_missing_attribute @@ -1060,21 +976,15 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" } end - def test_find_with_invalid_params - assert_raise(ArgumentError) { Topic.find :first, :join => "It should be `joins'" } - assert_raise(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" } - end - def test_dynamic_finder_with_invalid_params assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } end def test_find_all_with_join - developers_on_project_one = Developer.find( - :all, + developers_on_project_one = Developer.scoped( :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', - :conditions => 'project_id=1' - ) + :where => 'project_id=1' + ).all assert_equal 3, developers_on_project_one.length developer_names = developers_on_project_one.map { |d| d.name } assert developer_names.include?('David') @@ -1082,17 +992,15 @@ class FinderTest < ActiveRecord::TestCase end def test_joins_dont_clobber_id - first = Firm.find( - :first, + first = Firm.scoped( :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id', - :conditions => 'companies.id = 1' - ) + :where => 'companies.id = 1' + ).first assert_equal 1, first.id end def test_joins_with_string_array - person_with_reader_and_post = Post.find( - :all, + person_with_reader_and_post = Post.scoped( :joins => [ "INNER JOIN categorizations ON categorizations.post_id = posts.id", "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'" @@ -1103,8 +1011,7 @@ class FinderTest < ActiveRecord::TestCase def test_find_by_id_with_conditions_with_or assert_nothing_raised do - Post.find([1,2,3], - :conditions => "posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'") + Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3]) end end @@ -1119,13 +1026,13 @@ class FinderTest < ActiveRecord::TestCase end def test_find_by_empty_in_condition - assert_equal [], Post.find(:all, :conditions => ['id in (?)', []]) + assert_equal [], Post.where('id in (?)', []) end def test_find_by_records - p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc') - assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2]], :order => 'id asc') - assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2.id]], :order => 'id asc') + p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').all + assert_equal [p1, p2], Post.scoped(:where => ['id in (?)', [p1, p2]], :order => 'id asc') + assert_equal [p1, p2], Post.scoped(:where => ['id in (?)', [p1, p2.id]], :order => 'id asc') end def test_select_value @@ -1152,16 +1059,15 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct - assert_equal 2, Post.find(:all, :include => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).size + assert_equal 2, Post.scoped(:includes => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).all.size - assert_equal 3, Post.find(:all, :include => { :author => :author_address, :authors => :author_address}, - :order => 'author_addresses_authors.id DESC ', :limit => 3).size + assert_equal 3, Post.scoped(:includes => { :author => :author_address, :authors => :author_address}, + :order => 'author_addresses_authors.id DESC ', :limit => 3).all.size end def test_find_with_nil_inside_set_passed_for_one_attribute - client_of = Company.find( - :all, - :conditions => { + client_of = Company.scoped( + :where => { :client_of => [2, 1, nil], :name => ['37signals', 'Summit', 'Microsoft'] }, :order => 'client_of DESC' @@ -1172,9 +1078,8 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_nil_inside_set_passed_for_attribute - client_of = Company.find( - :all, - :conditions => { :client_of => [nil] }, + client_of = Company.scoped( + :where => { :client_of => [nil] }, :order => 'client_of DESC' ).map { |x| x.client_of } @@ -1182,22 +1087,14 @@ class FinderTest < ActiveRecord::TestCase end def test_with_limiting_with_custom_select - posts = Post.references(:authors).find( - :all, :include => :author, :select => ' posts.*, authors.id as "author_id"', + posts = Post.references(:authors).scoped( + :includes => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id' - ) + ).all assert_equal 3, posts.size assert_equal [0, 1, 1], posts.map(&:author_id).sort end - def test_finder_with_scoped_from - all_topics = Topic.find(:all) - - Topic.send(:with_scope, :find => { :from => 'fake_topics' }) do - assert_equal all_topics, Topic.from('topics').to_a - end - end - def test_find_one_message_with_custom_primary_key Toy.primary_key = :name begin @@ -1208,7 +1105,7 @@ class FinderTest < ActiveRecord::TestCase end def test_finder_with_offset_string - assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.find(:all, :offset => "3") } + assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.scoped(:offset => "3").all } end protected diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index e660b2ca35..c28f8de682 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -716,7 +716,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase end def test_only_generates_a_pk_if_necessary - m = Matey.find(:first) + m = Matey.first m.pirate = pirates(:blackbeard) m.target = pirates(:redbeard) end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 5c3560a33b..345ae0b582 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -36,7 +36,7 @@ def current_adapter?(*types) end def in_memory_db? - current_adapter?(:SQLiteAdapter) && + current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:" end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 02df464469..06de51f5cd 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -15,7 +15,7 @@ class InheritanceTest < ActiveRecord::TestCase end def test_class_with_blank_sti_name - company = Company.find(:first) + company = Company.first company = company.dup company.extend(Module.new { def read_attribute(name) @@ -24,7 +24,7 @@ class InheritanceTest < ActiveRecord::TestCase end }) company.save! - company = Company.find(:all).find { |x| x.id == company.id } + company = Company.all.find { |x| x.id == company.id } assert_equal ' ', company.type end @@ -98,7 +98,7 @@ class InheritanceTest < ActiveRecord::TestCase end def test_inheritance_find_all - companies = Company.find(:all, :order => 'id') + companies = Company.scoped(:order => 'id').all assert_kind_of Firm, companies[0], "37signals should be a firm" assert_kind_of Client, companies[1], "Summit should be a client" end @@ -149,9 +149,9 @@ class InheritanceTest < ActiveRecord::TestCase def test_update_all_within_inheritance Client.update_all "name = 'I am a client'" - assert_equal "I am a client", Client.find(:all).first.name + assert_equal "I am a client", Client.all.first.name # Order by added as otherwise Oracle tests were failing because of different order of results - assert_equal "37signals", Firm.find(:all, :order => "id").first.name + assert_equal "37signals", Firm.scoped(:order => "id").all.first.name end def test_alt_update_all_within_inheritance @@ -173,9 +173,9 @@ class InheritanceTest < ActiveRecord::TestCase end def test_find_first_within_inheritance - assert_kind_of Firm, Company.find(:first, :conditions => "name = '37signals'") - assert_kind_of Firm, Firm.find(:first, :conditions => "name = '37signals'") - assert_nil Client.find(:first, :conditions => "name = '37signals'") + assert_kind_of Firm, Company.scoped(:where => "name = '37signals'").first + assert_kind_of Firm, Firm.scoped(:where => "name = '37signals'").first + assert_nil Client.scoped(:where => "name = '37signals'").first end def test_alt_find_first_within_inheritance @@ -187,10 +187,10 @@ class InheritanceTest < ActiveRecord::TestCase def test_complex_inheritance very_special_client = VerySpecialClient.create("name" => "veryspecial") assert_equal very_special_client, VerySpecialClient.where("name = 'veryspecial'").first - assert_equal very_special_client, SpecialClient.find(:first, :conditions => "name = 'veryspecial'") - assert_equal very_special_client, Company.find(:first, :conditions => "name = 'veryspecial'") - assert_equal very_special_client, Client.find(:first, :conditions => "name = 'veryspecial'") - assert_equal 1, Client.find(:all, :conditions => "name = 'Summit'").size + assert_equal very_special_client, SpecialClient.scoped(:where => "name = 'veryspecial'").first + assert_equal very_special_client, Company.scoped(:where => "name = 'veryspecial'").first + assert_equal very_special_client, Client.scoped(:where => "name = 'veryspecial'").first + assert_equal 1, Client.scoped(:where => "name = 'Summit'").all.size assert_equal very_special_client, Client.find(very_special_client.id) end @@ -201,14 +201,14 @@ class InheritanceTest < ActiveRecord::TestCase end def test_eager_load_belongs_to_something_inherited - account = Account.find(1, :include => :firm) + account = Account.scoped(:includes => :firm).find(1) assert account.association_cache.key?(:firm), "nil proves eager load failed" end def test_eager_load_belongs_to_primary_key_quoting con = Account.connection assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)/) do - Account.find(1, :include => :firm) + Account.scoped(:includes => :firm).find(1) end end @@ -230,7 +230,7 @@ class InheritanceTest < ActiveRecord::TestCase private def switch_to_alt_inheritance_column # we don't want misleading test results, so get rid of the values in the type column - Company.find(:all, :order => 'id').each do |c| + Company.scoped(:order => 'id').all.each do |c| c['type'] = nil c.save end @@ -259,7 +259,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase def test_instantiation_doesnt_try_to_require_corresponding_file ActiveRecord::Base.store_full_sti_class = false - foo = Firm.find(:first).clone + foo = Firm.first.clone foo.ruby_type = foo.type = 'FirmOnTheFly' foo.save! diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb index 98cda010ae..426a350379 100644 --- a/activerecord/test/cases/invalid_date_test.rb +++ b/activerecord/test/cases/invalid_date_test.rb @@ -7,8 +7,6 @@ class InvalidDateTest < ActiveRecord::TestCase invalid_dates = [[2007, 11, 31], [1993, 2, 29], [2007, 2, 29]] - topic = Topic.new - valid_dates.each do |date_src| topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s) # Oracle DATE columns are datetime columns and Oracle adapter returns Time value diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb index 75e5dfa49b..0b78f2e46b 100644 --- a/activerecord/test/cases/lifecycle_test.rb +++ b/activerecord/test/cases/lifecycle_test.rb @@ -137,7 +137,7 @@ class LifecycleTest < ActiveRecord::TestCase def test_auto_observer topic_observer = TopicaAuditor.instance assert_nil TopicaAuditor.observed_class - assert_equal [Topic], TopicaAuditor.instance.observed_classes.to_a + assert_equal [Topic], TopicaAuditor.observed_classes.to_a topic = Topic.find(1) assert_equal topic.title, topic_observer.topic.title diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index cc6baa6153..afb0bd6fd9 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -323,7 +323,7 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase def counter_test(model, expected_count) add_counter_column_to(model) - object = model.find(:first) + object = model.first assert_equal 0, object.test_count assert_equal 0, object.send(model.locking_column) yield object.id @@ -358,18 +358,7 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? def test_sane_find_with_lock assert_nothing_raised do Person.transaction do - Person.find 1, :lock => true - end - end - end - - # Test scoped lock. - def test_sane_find_with_scoped_lock - assert_nothing_raised do - Person.transaction do - Person.send(:with_scope, :find => { :lock => true }) do - Person.find 1 - end + Person.lock.find(1) end end end @@ -380,7 +369,7 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? def test_eager_find_with_lock assert_nothing_raised do Person.transaction do - Person.find 1, :include => :readers, :lock => true + Person.includes(:readers).lock.find(1) end end end diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb deleted file mode 100644 index ebf6e26385..0000000000 --- a/activerecord/test/cases/method_scoping_test.rb +++ /dev/null @@ -1,558 +0,0 @@ -# This file can be removed once with_exclusive_scope and with_scope are removed. -# All the tests were already ported to relation_scoping_test.rb when the new -# relation scoping API was added. - -require "cases/helper" -require 'models/post' -require 'models/author' -require 'models/developer' -require 'models/project' -require 'models/comment' - -class MethodScopingTest < ActiveRecord::TestCase - fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects - - def test_set_conditions - Developer.send(:with_scope, :find => { :conditions => 'just a test...' }) do - assert_match '(just a test...)', Developer.scoped.to_sql - end - end - - def test_scoped_find - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - assert_nothing_raised { Developer.find(1) } - end - end - - def test_scoped_find_first - Developer.send(:with_scope, :find => { :conditions => "salary = 100000" }) do - assert_equal Developer.find(10), Developer.find(:first, :order => 'name') - end - end - - def test_scoped_find_last - highest_salary = Developer.find(:first, :order => "salary DESC") - - Developer.send(:with_scope, :find => { :order => "salary" }) do - assert_equal highest_salary, Developer.last - end - end - - def test_scoped_find_last_preserves_scope - lowest_salary = Developer.find(:first, :order => "salary ASC") - highest_salary = Developer.find(:first, :order => "salary DESC") - - Developer.send(:with_scope, :find => { :order => "salary" }) do - assert_equal highest_salary, Developer.last - assert_equal lowest_salary, Developer.first - end - end - - def test_scoped_find_combines_conditions - Developer.send(:with_scope, :find => { :conditions => "salary = 9000" }) do - assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => "name = 'Jamis'") - end - end - - def test_scoped_find_sanitizes_conditions - Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do - assert_equal developers(:poor_jamis), Developer.find(:first) - end - end - - def test_scoped_find_combines_and_sanitizes_conditions - Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do - assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis']) - end - end - - def test_scoped_find_all - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - assert_equal [developers(:david)], Developer.all - end - end - - def test_scoped_find_select - Developer.send(:with_scope, :find => { :select => "id, name" }) do - developer = Developer.find(:first, :conditions => "name = 'David'") - assert_equal "David", developer.name - assert !developer.has_attribute?(:salary) - end - end - - def test_scope_select_concatenates - Developer.send(:with_scope, :find => { :select => "name" }) do - developer = Developer.find(:first, :select => 'id, salary', :conditions => "name = 'David'") - assert_equal 80000, developer.salary - assert developer.has_attribute?(:id) - assert developer.has_attribute?(:name) - assert developer.has_attribute?(:salary) - end - end - - def test_scoped_count - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - assert_equal 1, Developer.count - end - - Developer.send(:with_scope, :find => { :conditions => 'salary = 100000' }) do - assert_equal 8, Developer.count - assert_equal 1, Developer.count(:conditions => "name LIKE 'fixture_1%'") - end - end - - def test_scoped_find_include - # with the include, will retrieve only developers for the given project - scoped_developers = Developer.send(:with_scope, :find => { :include => :projects }) do - Developer.find(:all, :conditions => { 'projects.id' => 2 }) - end - assert scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) - assert_equal 1, scoped_developers.size - end - - def test_scoped_find_joins - scoped_developers = Developer.send(:with_scope, :find => { :joins => 'JOIN developers_projects ON id = developer_id' } ) do - Developer.find(:all, :conditions => 'developers_projects.project_id = 2') - end - assert scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) - assert_equal 1, scoped_developers.size - assert_equal developers(:david).attributes, scoped_developers.first.attributes - end - - def test_scoped_find_using_new_style_joins - scoped_developers = Developer.send(:with_scope, :find => { :joins => :projects }) do - Developer.find(:all, :conditions => 'projects.id = 2') - end - assert scoped_developers.include?(developers(:david)) - assert !scoped_developers.include?(developers(:jamis)) - assert_equal 1, scoped_developers.size - assert_equal developers(:david).attributes, scoped_developers.first.attributes - end - - def test_scoped_find_merges_old_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id ' }) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'INNER JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_merges_new_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => :comments, :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_merges_new_and_old_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_merges_string_array_style_and_string_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => ["INNER JOIN posts ON posts.author_id = authors.id"]}) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'INNER JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_merges_string_array_style_and_hash_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => :posts}) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN comments ON posts.id = comments.post_id'], :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_merges_joins_and_eliminates_duplicate_string_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON posts.author_id = authors.id'}) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => ["INNER JOIN posts ON posts.author_id = authors.id", "INNER JOIN comments ON posts.id = comments.post_id"], :conditions => 'comments.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_find_strips_spaces_from_string_joins_and_eliminates_duplicate_string_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => ' INNER JOIN posts ON posts.author_id = authors.id '}) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN posts ON posts.author_id = authors.id'], :conditions => 'posts.id = 1') - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_scoped_count_include - # with the include, will retrieve only developers for the given project - Developer.send(:with_scope, :find => { :include => :projects }) do - assert_equal 1, Developer.count(:conditions => { 'projects.id' => 2 }) - end - end - - def test_scope_for_create_only_uses_equal - table = VerySpecialComment.arel_table - relation = VerySpecialComment.scoped - relation.where_values << table[:id].not_eq(1) - assert_equal({:type => "VerySpecialComment"}, relation.send(:scope_for_create)) - end - - def test_scoped_create - new_comment = nil - - VerySpecialComment.send(:with_scope, :create => { :post_id => 1 }) do - assert_equal({:post_id => 1, :type => 'VerySpecialComment' }, VerySpecialComment.scoped.send(:scope_for_create)) - new_comment = VerySpecialComment.create :body => "Wonderful world" - end - - assert Post.find(1).comments.include?(new_comment) - end - - def test_scoped_create_with_join_and_merge - Comment.where(:body => "but Who's Buying?").joins(:post).merge(Post.where(:body => 'Peace Sells...')).with_scope do - assert_equal({:body => "but Who's Buying?"}, Comment.scoped.scope_for_create) - end - end - - def test_immutable_scope - options = { :conditions => "name = 'David'" } - Developer.send(:with_scope, :find => options) do - assert_equal %w(David), Developer.all.map(&:name) - options[:conditions] = "name != 'David'" - assert_equal %w(David), Developer.all.map(&:name) - end - - scope = { :find => { :conditions => "name = 'David'" }} - Developer.send(:with_scope, scope) do - assert_equal %w(David), Developer.all.map(&:name) - scope[:find][:conditions] = "name != 'David'" - assert_equal %w(David), Developer.all.map(&:name) - end - end - - def test_scoped_with_duck_typing - scoping = Struct.new(:current_scope).new(:find => { :conditions => ["name = ?", 'David'] }) - Developer.send(:with_scope, scoping) do - assert_equal %w(David), Developer.all.map(&:name) - end - end - - def test_ensure_that_method_scoping_is_correctly_restored - begin - Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do - raise "an exception" - end - rescue - end - - assert !Developer.scoped.where_values.include?("name = 'Jamis'") - end -end - -class NestedScopingTest < ActiveRecord::TestCase - fixtures :authors, :developers, :projects, :comments, :posts - - def test_merge_options - Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do - Developer.send(:with_scope, :find => { :limit => 10 }) do - devs = Developer.scoped - assert_match '(salary = 80000)', devs.to_sql - assert_equal 10, devs.taken - end - end - end - - def test_merge_inner_scope_has_priority - Developer.send(:with_scope, :find => { :limit => 5 }) do - Developer.send(:with_scope, :find => { :limit => 10 }) do - assert_equal 10, Developer.scoped.taken - end - end - end - - def test_replace_options - Developer.send(:with_scope, :find => { :conditions => {:name => 'David'} }) do - Developer.send(:with_exclusive_scope, :find => { :conditions => {:name => 'Jamis'} }) do - assert_equal 'Jamis', Developer.scoped.send(:scope_for_create)[:name] - end - - assert_equal 'David', Developer.scoped.send(:scope_for_create)[:name] - end - end - - def test_with_exclusive_scope_with_relation - assert_raise(ArgumentError) do - Developer.all_johns - end - end - - def test_append_conditions - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do - devs = Developer.scoped - assert_match "(name = 'David') AND (salary = 80000)", devs.to_sql - assert_equal(1, Developer.count) - end - Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do - assert_equal(0, Developer.count) - end - end - end - - def test_merge_and_append_options - Developer.send(:with_scope, :find => { :conditions => 'salary = 80000', :limit => 10 }) do - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - devs = Developer.scoped - assert_match "(salary = 80000) AND (name = 'David')", devs.to_sql - assert_equal 10, devs.taken - end - end - end - - def test_nested_scoped_find - Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do - Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'David'" }) do - assert_nothing_raised { Developer.find(1) } - assert_equal('David', Developer.find(:first).name) - end - assert_equal('Jamis', Developer.find(:first).name) - end - end - - def test_nested_scoped_find_include - Developer.send(:with_scope, :find => { :include => :projects }) do - Developer.send(:with_scope, :find => { :conditions => { 'projects.id' => 2 } }) do - assert_nothing_raised { Developer.find(1) } - assert_equal('David', Developer.find(:first).name) - end - end - end - - def test_nested_scoped_find_merged_include - # :include's remain unique and don't "double up" when merging - Developer.send(:with_scope, :find => { :include => :projects, :conditions => { 'projects.id' => 2 } }) do - Developer.send(:with_scope, :find => { :include => :projects }) do - assert_equal 1, Developer.scoped.includes_values.uniq.length - assert_equal 'David', Developer.find(:first).name - end - end - - # the nested scope doesn't remove the first :include - Developer.send(:with_scope, :find => { :include => :projects, :conditions => { 'projects.id' => 2 } }) do - Developer.send(:with_scope, :find => { :include => [] }) do - assert_equal 1, Developer.scoped.includes_values.uniq.length - assert_equal('David', Developer.find(:first).name) - end - end - - # mixing array and symbol include's will merge correctly - Developer.send(:with_scope, :find => { :include => [:projects], :conditions => { 'projects.id' => 2 } }) do - Developer.send(:with_scope, :find => { :include => :projects }) do - assert_equal 1, Developer.scoped.includes_values.uniq.length - assert_equal('David', Developer.find(:first).name) - end - end - end - - def test_nested_scoped_find_replace_include - Developer.send(:with_scope, :find => { :include => :projects }) do - Developer.send(:with_exclusive_scope, :find => { :include => [] }) do - assert_equal 0, Developer.scoped.includes_values.length - end - end - end - - def test_three_level_nested_exclusive_scoped_find - Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do - assert_equal('Jamis', Developer.find(:first).name) - - Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'David'" }) do - assert_equal('David', Developer.find(:first).name) - - Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'Maiha'" }) do - assert_equal(nil, Developer.find(:first)) - end - - # ensure that scoping is restored - assert_equal('David', Developer.find(:first).name) - end - - # ensure that scoping is restored - assert_equal('Jamis', Developer.find(:first).name) - end - end - - def test_merged_scoped_find - poor_jamis = developers(:poor_jamis) - Developer.send(:with_scope, :find => { :conditions => "salary < 100000" }) do - Developer.send(:with_scope, :find => { :offset => 1, :order => 'id asc' }) do - # Oracle adapter does not generated space after asc therefore trailing space removed from regex - assert_sql(/ORDER BY\s+id asc/) do - assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc')) - end - end - end - end - - def test_merged_scoped_find_sanitizes_conditions - Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do - Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do - assert_raise(ActiveRecord::RecordNotFound) { developers(:poor_jamis) } - end - end - end - - def test_nested_scoped_find_combines_and_sanitizes_conditions - Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do - Developer.send(:with_exclusive_scope, :find => { :conditions => ['salary = ?', 9000] }) do - assert_equal developers(:poor_jamis), Developer.find(:first) - assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis']) - end - end - end - - def test_merged_scoped_find_combines_and_sanitizes_conditions - Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do - Developer.send(:with_scope, :find => { :conditions => ['salary > ?', 9000] }) do - assert_equal %w(David), Developer.all.map(&:name) - end - end - end - - def test_nested_scoped_create - comment = nil - Comment.send(:with_scope, :create => { :post_id => 1}) do - Comment.send(:with_scope, :create => { :post_id => 2}) do - assert_equal({:post_id => 2}, Comment.scoped.send(:scope_for_create)) - comment = Comment.create :body => "Hey guys, nested scopes are broken. Please fix!" - end - end - assert_equal 2, comment.post_id - end - - def test_nested_exclusive_scope_for_create - comment = nil - - Comment.send(:with_scope, :create => { :body => "Hey guys, nested scopes are broken. Please fix!" }) do - Comment.send(:with_exclusive_scope, :create => { :post_id => 1 }) do - assert_equal({:post_id => 1}, Comment.scoped.send(:scope_for_create)) - assert_blank Comment.new.body - comment = Comment.create :body => "Hey guys" - end - end - assert_equal 1, comment.post_id - assert_equal 'Hey guys', comment.body - end - - def test_merged_scoped_find_on_blank_conditions - [nil, " ", [], {}].each do |blank| - Developer.send(:with_scope, :find => {:conditions => blank}) do - Developer.send(:with_scope, :find => {:conditions => blank}) do - assert_nothing_raised { Developer.find(:first) } - end - end - end - end - - def test_merged_scoped_find_on_blank_bind_conditions - [ [""], ["",{}] ].each do |blank| - Developer.send(:with_scope, :find => {:conditions => blank}) do - Developer.send(:with_scope, :find => {:conditions => blank}) do - assert_nothing_raised { Developer.find(:first) } - end - end - end - end - - def test_immutable_nested_scope - options1 = { :conditions => "name = 'Jamis'" } - options2 = { :conditions => "name = 'David'" } - Developer.send(:with_scope, :find => options1) do - Developer.send(:with_exclusive_scope, :find => options2) do - assert_equal %w(David), Developer.all.map(&:name) - options1[:conditions] = options2[:conditions] = nil - assert_equal %w(David), Developer.all.map(&:name) - end - end - end - - def test_immutable_merged_scope - options1 = { :conditions => "name = 'Jamis'" } - options2 = { :conditions => "salary > 10000" } - Developer.send(:with_scope, :find => options1) do - Developer.send(:with_scope, :find => options2) do - assert_equal %w(Jamis), Developer.all.map(&:name) - options1[:conditions] = options2[:conditions] = nil - assert_equal %w(Jamis), Developer.all.map(&:name) - end - end - end - - def test_ensure_that_method_scoping_is_correctly_restored - Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - begin - Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do - raise "an exception" - end - rescue - end - - assert Developer.scoped.where_values.include?("name = 'David'") - assert !Developer.scoped.where_values.include?("name = 'Maiha'") - end - end - - def test_nested_scoped_find_merges_old_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id' }) do - Author.send(:with_scope, :find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do - Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1') - end - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_nested_scoped_find_merges_new_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do - Author.send(:with_scope, :find => { :joins => :comments }) do - Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1') - end - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end - - def test_nested_scoped_find_merges_new_and_old_style_joins - scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do - Author.send(:with_scope, :find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do - Author.find(:all, :select => 'DISTINCT authors.*', :joins => '', :conditions => 'comments.id = 1') - end - end - assert scoped_authors.include?(authors(:david)) - assert !scoped_authors.include?(authors(:mary)) - assert_equal 1, scoped_authors.size - assert_equal authors(:david).attributes, scoped_authors.first.attributes - end -end diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb index 040445ef12..409a558f5c 100644 --- a/activerecord/test/cases/migration/column_attributes_test.rb +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -64,7 +64,7 @@ module ActiveRecord end # SELECT - row = TestModel.find(:first) + row = TestModel.first assert_kind_of BigDecimal, row.wealth # If this assert fails, that means the SELECT is broken! @@ -79,7 +79,7 @@ module ActiveRecord TestModel.create :wealth => BigDecimal.new("12345678901234567890.0123456789") # SELECT - row = TestModel.find(:first) + row = TestModel.first assert_kind_of BigDecimal, row.wealth # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken! @@ -132,7 +132,7 @@ module ActiveRecord :birthday => 18.years.ago, :favorite_day => 10.days.ago, :moment_of_truth => "1782-10-10 21:40:18", :male => true - bob = TestModel.find(:first) + bob = TestModel.first assert_equal 'bob', bob.first_name assert_equal 'bobsen', bob.last_name assert_equal "I was born ....", bob.bio diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb new file mode 100644 index 0000000000..8ab1c59724 --- /dev/null +++ b/activerecord/test/cases/migration/references_index_test.rb @@ -0,0 +1,99 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class ReferencesIndexTest < ActiveRecord::TestCase + attr_reader :connection, :table_name + + def setup + super + @connection = ActiveRecord::Base.connection + @table_name = :testings + end + + def teardown + super + connection.drop_table :testings rescue nil + end + + def test_creates_index + connection.create_table table_name do |t| + t.references :foo, :index => true + end + + assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_does_not_create_index + connection.create_table table_name do |t| + t.references :foo + end + + refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_does_not_create_index_explicit + connection.create_table table_name do |t| + t.references :foo, :index => false + end + + refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_creates_index_with_options + connection.create_table table_name do |t| + t.references :foo, :index => {:name => :index_testings_on_yo_momma} + t.references :bar, :index => {:unique => true} + end + + assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_yo_momma) + assert connection.index_exists?(table_name, :bar_id, :name => :index_testings_on_bar_id, :unique => true) + end + + def test_creates_polymorphic_index + connection.create_table table_name do |t| + t.references :foo, :polymorphic => true, :index => true + end + + assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type) + end + + def test_creates_index_for_existing_table + connection.create_table table_name + connection.change_table table_name do |t| + t.references :foo, :index => true + end + + assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_does_not_create_index_for_existing_table + connection.create_table table_name + connection.change_table table_name do |t| + t.references :foo + end + + refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_does_not_create_index_for_existing_table_explicit + connection.create_table table_name + connection.change_table table_name do |t| + t.references :foo, :index => false + end + + refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id) + end + + def test_creates_polymorphic_index_for_existing_table + connection.create_table table_name + connection.change_table table_name do |t| + t.references :foo, :polymorphic => true, :index => true + end + + assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type) + end + + end + end +end diff --git a/activerecord/test/cases/migration/rename_column_test.rb b/activerecord/test/cases/migration/rename_column_test.rb index 16e09fd80e..d1a85ee5e4 100644 --- a/activerecord/test/cases/migration/rename_column_test.rb +++ b/activerecord/test/cases/migration/rename_column_test.rb @@ -18,7 +18,7 @@ module ActiveRecord rename_column "test_models", "girlfriend", "exgirlfriend" TestModel.reset_column_information - bob = TestModel.find(:first) + bob = TestModel.first assert_equal "bobette", bob.exgirlfriend end @@ -33,7 +33,7 @@ module ActiveRecord rename_column :test_models, :first_name, :nick_name TestModel.reset_column_information assert TestModel.column_names.include?("nick_name") - assert_equal ['foo'], TestModel.find(:all).map(&:nick_name) + assert_equal ['foo'], TestModel.all.map(&:nick_name) end # FIXME: another integration test. We should decouple this from the @@ -46,7 +46,7 @@ module ActiveRecord rename_column "test_models", "first_name", "nick_name" TestModel.reset_column_information assert TestModel.column_names.include?("nick_name") - assert_equal ['foo'], TestModel.find(:all).map(&:nick_name) + assert_equal ['foo'], TestModel.all.map(&:nick_name) end def test_rename_column_preserves_default_value_not_null diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 92dc150104..e71b84a3a5 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -36,7 +36,7 @@ class MigrationTest < ActiveRecord::TestCase ActiveRecord::Base.connection.initialize_schema_migrations_table ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" - %w(things awesome_things prefix_things_suffix prefix_awesome_things_suffix).each do |table| + %w(things awesome_things prefix_things_suffix p_awesome_things_s ).each do |table| Thing.connection.drop_table(table) rescue nil end Thing.reset_column_information @@ -109,7 +109,7 @@ class MigrationTest < ActiveRecord::TestCase :value_of_e => BigDecimal("2.7182818284590452353602875") ) - b = BigNumber.find(:first) + b = BigNumber.first assert_not_nil b assert_not_nil b.bank_balance @@ -153,7 +153,7 @@ class MigrationTest < ActiveRecord::TestCase end GiveMeBigNumbers.down - assert_raise(ActiveRecord::StatementInvalid) { BigNumber.find(:first) } + assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first } end def test_filtering_migrations @@ -165,13 +165,13 @@ class MigrationTest < ActiveRecord::TestCase Person.reset_column_information assert Person.column_methods_hash.include?(:last_name) - assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } + assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter) Person.reset_column_information assert !Person.column_methods_hash.include?(:last_name) - assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } + assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } end class MockMigration < ActiveRecord::Migration @@ -278,19 +278,19 @@ class MigrationTest < ActiveRecord::TestCase def test_rename_table_with_prefix_and_suffix assert !Thing.table_exists? - ActiveRecord::Base.table_name_prefix = 'prefix_' - ActiveRecord::Base.table_name_suffix = '_suffix' + ActiveRecord::Base.table_name_prefix = 'p_' + ActiveRecord::Base.table_name_suffix = '_s' Thing.reset_table_name Thing.reset_sequence_name WeNeedThings.up assert Thing.create("content" => "hello world") - assert_equal "hello world", Thing.find(:first).content + assert_equal "hello world", Thing.first.content RenameThings.up - Thing.table_name = "prefix_awesome_things_suffix" + Thing.table_name = "p_awesome_things_s" - assert_equal "hello world", Thing.find(:first).content + assert_equal "hello world", Thing.first.content ensure ActiveRecord::Base.table_name_prefix = '' ActiveRecord::Base.table_name_suffix = '' @@ -306,10 +306,10 @@ class MigrationTest < ActiveRecord::TestCase Reminder.reset_sequence_name WeNeedReminders.up assert Reminder.create("content" => "hello world", "remind_at" => Time.now) - assert_equal "hello world", Reminder.find(:first).content + assert_equal "hello world", Reminder.first.content WeNeedReminders.down - assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } + assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } ensure ActiveRecord::Base.table_name_prefix = '' ActiveRecord::Base.table_name_suffix = '' @@ -404,6 +404,7 @@ end class ChangeTableMigrationsTest < ActiveRecord::TestCase def setup @connection = Person.connection + @connection.stubs(:add_index) @connection.create_table :delete_me, :force => true do |t| end end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index e0fdd2871c..a03c4f552e 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -27,19 +27,19 @@ class ModulesTest < ActiveRecord::TestCase end def test_module_spanning_associations - firm = MyApplication::Business::Firm.find(:first) + firm = MyApplication::Business::Firm.first assert !firm.clients.empty?, "Firm should have clients" assert_nil firm.class.table_name.match('::'), "Firm shouldn't have the module appear in its table name" end def test_module_spanning_has_and_belongs_to_many_associations - project = MyApplication::Business::Project.find(:first) + project = MyApplication::Business::Project.first project.developers << MyApplication::Business::Developer.create("name" => "John") assert_equal "John", project.developers.last.name end def test_associations_spanning_cross_modules - account = MyApplication::Billing::Account.find(:first, :order => 'id') + account = MyApplication::Billing::Account.scoped(:order => 'id').first assert_kind_of MyApplication::Business::Firm, account.firm assert_kind_of MyApplication::Billing::Firm, account.qualified_billing_firm assert_kind_of MyApplication::Billing::Firm, account.unqualified_billing_firm @@ -48,7 +48,7 @@ class ModulesTest < ActiveRecord::TestCase end def test_find_account_and_include_company - account = MyApplication::Billing::Account.find(1, :include => :firm) + account = MyApplication::Billing::Account.scoped(:includes => :firm).find(1) assert_kind_of MyApplication::Business::Firm, account.firm end @@ -72,8 +72,8 @@ class ModulesTest < ActiveRecord::TestCase clients = [] assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do - clients << MyApplication::Business::Client.references(:accounts).find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') - clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}) + clients << MyApplication::Business::Client.references(:accounts).scoped(:includes => {:firm => :account}, :where => 'accounts.id IS NOT NULL').find(3) + clients << MyApplication::Business::Client.scoped(:includes => {:firm => :account}).find(3) end clients.each do |client| @@ -123,7 +123,7 @@ class ModulesTest < ActiveRecord::TestCase old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true - collection = Shop::Collection.find(:first) + collection = Shop::Collection.first assert !collection.products.empty?, "Collection should have products" assert_nothing_raised { collection.destroy } ensure @@ -134,7 +134,7 @@ class ModulesTest < ActiveRecord::TestCase old = ActiveRecord::Base.store_full_sti_class ActiveRecord::Base.store_full_sti_class = true - product = Shop::Product.find(:first) + product = Shop::Product.first assert !product.variants.empty?, "Product should have variants" assert_nothing_raised { product.destroy } ensure diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 0d3c0b20a4..f8557259fb 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -10,12 +10,12 @@ class NamedScopeTest < ActiveRecord::TestCase fixtures :posts, :authors, :topics, :comments, :author_addresses def test_implements_enumerable - assert !Topic.find(:all).empty? + assert !Topic.all.empty? - assert_equal Topic.find(:all), Topic.base - assert_equal Topic.find(:all), Topic.base.to_a - assert_equal Topic.find(:first), Topic.base.first - assert_equal Topic.find(:all), Topic.base.map { |i| i } + assert_equal Topic.all, Topic.base + assert_equal Topic.all, Topic.base.to_a + assert_equal Topic.first, Topic.base.first + assert_equal Topic.all, Topic.base.map { |i| i } end def test_found_items_are_cached @@ -38,10 +38,10 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_delegates_finds_and_calculations_to_the_base_class - assert !Topic.find(:all).empty? + assert !Topic.all.empty? - assert_equal Topic.find(:all), Topic.base.find(:all) - assert_equal Topic.find(:first), Topic.base.find(:first) + assert_equal Topic.all, Topic.base.all + assert_equal Topic.first, Topic.base.first assert_equal Topic.count, Topic.base.count assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count) end @@ -58,10 +58,10 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified - assert !Topic.find(:all, :conditions => {:approved => true}).empty? + assert !Topic.scoped(:where => {:approved => true}).all.empty? - assert_equal Topic.find(:all, :conditions => {:approved => true}), Topic.approved - assert_equal Topic.count(:conditions => {:approved => true}), Topic.approved.count + assert_equal Topic.scoped(:where => {:approved => true}).all, Topic.approved + assert_equal Topic.where(:approved => true).count, Topic.approved.count end def test_scopes_with_string_name_can_be_composed @@ -70,13 +70,9 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal Topic.replied.approved, Topic.replied.approved_as_string end - def test_scopes_can_be_specified_with_deep_hash_conditions - assert_equal Topic.replied.approved, Topic.replied.approved_as_hash_condition - end - def test_scopes_are_composable - assert_equal((approved = Topic.find(:all, :conditions => {:approved => true})), Topic.approved) - assert_equal((replied = Topic.find(:all, :conditions => 'replies_count > 0')), Topic.replied) + assert_equal((approved = Topic.scoped(:where => {:approved => true}).all), Topic.approved) + assert_equal((replied = Topic.scoped(:where => 'replies_count > 0').all), Topic.replied) assert !(approved == replied) assert !(approved & replied).empty? @@ -84,8 +80,8 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_procedural_scopes - topics_written_before_the_third = Topic.find(:all, :conditions => ['written_on < ?', topics(:third).written_on]) - topics_written_before_the_second = Topic.find(:all, :conditions => ['written_on < ?', topics(:second).written_on]) + topics_written_before_the_third = Topic.where('written_on < ?', topics(:third).written_on) + topics_written_before_the_second = Topic.where('written_on < ?', topics(:second).written_on) assert_not_equal topics_written_before_the_second, topics_written_before_the_third assert_equal topics_written_before_the_third, Topic.written_before(topics(:third).written_on) @@ -93,30 +89,11 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_procedural_scopes_returning_nil - all_topics = Topic.find(:all) + all_topics = Topic.all assert_equal all_topics, Topic.written_before(nil) end - def test_scopes_with_joins - address = author_addresses(:david_address) - posts_with_authors_at_address = Post.find( - :all, :joins => 'JOIN authors ON authors.id = posts.author_id', - :conditions => [ 'authors.author_address_id = ?', address.id ] - ) - assert_equal posts_with_authors_at_address, Post.with_authors_at_address(address) - end - - def test_scopes_with_joins_respects_custom_select - address = author_addresses(:david_address) - posts_with_authors_at_address_titles = Post.find(:all, - :select => 'title', - :joins => 'JOIN authors ON authors.id = posts.author_id', - :conditions => [ 'authors.author_address_id = ?', address.id ] - ) - assert_equal posts_with_authors_at_address_titles.map(&:title), Post.with_authors_at_address(address).find(:all, :select => 'title').map(&:title) - end - def test_scope_with_object objects = Topic.with_object assert_operator objects.length, :>, 0 @@ -153,20 +130,16 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_active_records_have_scope_named__all__ - assert !Topic.find(:all).empty? + assert !Topic.all.empty? - assert_equal Topic.find(:all), Topic.base + assert_equal Topic.all, Topic.base end def test_active_records_have_scope_named__scoped__ - assert !Topic.find(:all, scope = {:conditions => "content LIKE '%Have%'"}).empty? + scope = Topic.where("content LIKE '%Have%'") + assert !scope.empty? - assert_equal Topic.find(:all, scope), Topic.scoped(scope) - 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') + assert_equal scope, Topic.scoped(where: "content LIKE '%Have%'") end def test_first_and_last_should_allow_integers_for_limit @@ -183,15 +156,6 @@ class NamedScopeTest < ActiveRecord::TestCase end end - def test_first_and_last_find_options_should_use_query_when_results_are_loaded - topics = Topic.base - topics.reload # force load - assert_queries(2) do - topics.first(:order => 'title') - topics.last(:order => 'title') - end - end - def test_empty_should_not_load_results topics = Topic.base assert_queries(2) do @@ -254,9 +218,9 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_many_should_return_false_if_none_or_one - topics = Topic.base.scoped(:conditions => {:id => 0}) + topics = Topic.base.where(:id => 0) assert !topics.many? - topics = Topic.base.scoped(:conditions => {:id => 1}) + topics = Topic.base.where(:id => 1) assert !topics.many? end @@ -334,7 +298,7 @@ class NamedScopeTest < ActiveRecord::TestCase def test_chaining_with_duplicate_joins join = "INNER JOIN comments ON comments.post_id = posts.id" post = Post.find(1) - assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size + assert_equal post.comments.size, Post.joins(join).joins(join).where("posts.id = #{post.id}").size end def test_chaining_should_use_latest_conditions_when_creating @@ -460,26 +424,11 @@ class NamedScopeTest < ActiveRecord::TestCase klass.table_name = 'posts' assert_deprecated do - klass.scope :welcome, { :conditions => { :id => posts(:welcome).id } } - end - assert_equal [posts(:welcome).title], klass.welcome.map(&:title) - - assert_deprecated do klass.scope :welcome_2, klass.where(:id => posts(:welcome).id) end assert_equal [posts(:welcome).title], klass.welcome_2.map(&:title) end - def test_eager_default_scope_hashes_are_deprecated - klass = Class.new(ActiveRecord::Base) - klass.table_name = 'posts' - - assert_deprecated do - klass.send(:default_scope, :conditions => { :id => posts(:welcome).id }) - end - assert_equal [posts(:welcome).title], klass.all.map(&:title) - end - def test_eager_default_scope_relations_are_deprecated klass = Class.new(ActiveRecord::Base) klass.table_name = 'posts' @@ -515,7 +464,7 @@ class DynamicScopeTest < ActiveRecord::TestCase def test_dynamic_scope assert_equal @test_klass.scoped_by_author_id(1).find(1), @test_klass.find(1) - assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"}) + assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.scoped(:where => { :author_id => 1, :title => "Welcome to the weblog"}).first end def test_dynamic_scope_should_create_methods_after_hitting_method_missing diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index bb60aa0a66..0933a4ff3d 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -55,7 +55,7 @@ class PersistencesTest < ActiveRecord::TestCase author = authors(:david) assert_nothing_raised do assert_equal 1, author.posts_sorted_by_id_limited.size - assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size + assert_equal 2, author.posts_sorted_by_id_limited.scoped(:limit => 2).all.size assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ]) assert_equal "bulk update!", posts(:welcome).body assert_not_equal "bulk update!", posts(:thinking).body @@ -94,12 +94,6 @@ class PersistencesTest < ActiveRecord::TestCase assert_equal count, Pet.joins(:toys).where(where_args).delete_all end - def test_update_by_condition - Topic.update_all "content = 'bulk updated!'", ["approved = ?", true] - assert_equal "Have a nice day", Topic.find(1).content - assert_equal "bulk updated!", Topic.find(2).content - end - def test_increment_attribute assert_equal 50, accounts(:signals37).credit_limit accounts(:signals37).increment! :credit_limit @@ -126,7 +120,7 @@ class PersistencesTest < ActiveRecord::TestCase def test_destroy_all conditions = "author_name = 'Mary'" - topics_by_mary = Topic.all(:conditions => conditions, :order => 'id') + topics_by_mary = Topic.scoped(:where => conditions, :order => 'id').to_a assert ! topics_by_mary.empty? assert_difference('Topic.count', -topics_by_mary.size) do @@ -137,7 +131,7 @@ class PersistencesTest < ActiveRecord::TestCase end def test_destroy_many - clients = Client.find([2, 3], :order => 'id') + clients = Client.scoped(:order => 'id').find([2, 3]) assert_difference('Client.count', -2) do destroyed = Client.destroy([2, 3]).sort_by(&:id) @@ -338,7 +332,7 @@ class PersistencesTest < ActiveRecord::TestCase end def test_update_all_with_non_standard_table_name - assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1]) + assert_equal 1, WarehouseThing.where(id: 1).update_all(['value = ?', 0]) assert_equal 0, WarehouseThing.find(1).value end @@ -411,7 +405,6 @@ class PersistencesTest < ActiveRecord::TestCase def test_update_attribute_with_one_updated t = Topic.first - title = t.title t.update_attribute(:title, 'super_title') assert_equal 'super_title', t.title assert !t.changed?, "topic should not have changed" diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index d93478513b..a712e5f689 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -96,7 +96,7 @@ class QueryCacheTest < ActiveRecord::TestCase def test_cache_clear_after_close mw = ActiveRecord::QueryCache.new lambda { |env| - Post.find(:first) + Post.first [200, {}, nil] } body = mw.call({}).last @@ -233,8 +233,8 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase def test_cache_is_expired_by_habtm_update ActiveRecord::Base.connection.expects(:clear_query_cache).times(2) ActiveRecord::Base.cache do - c = Category.find(:first) - p = Post.find(:first) + c = Category.first + p = Post.first p.categories << c end end diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index e21109baae..39b66b3399 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -27,7 +27,7 @@ class ReadOnlyTest < ActiveRecord::TestCase def test_find_with_readonly_option - Developer.find(:all).each { |d| assert !d.readonly? } + Developer.all.each { |d| assert !d.readonly? } Developer.readonly(false).each { |d| assert !d.readonly? } Developer.readonly(true).each { |d| assert d.readonly? } Developer.readonly.each { |d| assert d.readonly? } @@ -48,7 +48,7 @@ class ReadOnlyTest < ActiveRecord::TestCase post = Post.find(1) assert !post.comments.empty? assert !post.comments.any?(&:readonly?) - assert !post.comments.find(:all).any?(&:readonly?) + assert !post.comments.all.any?(&:readonly?) assert post.comments.readonly(true).all?(&:readonly?) end @@ -70,13 +70,13 @@ class ReadOnlyTest < ActiveRecord::TestCase end def test_readonly_scoping - Post.send(:with_scope, :find => { :conditions => '1=1' }) do + Post.where('1=1').scoped do assert !Post.find(1).readonly? assert Post.readonly(true).find(1).readonly? assert !Post.readonly(false).find(1).readonly? end - Post.send(:with_scope, :find => { :joins => ' ' }) do + Post.joins(' ').scoped do assert !Post.find(1).readonly? assert Post.readonly.find(1).readonly? assert !Post.readonly(false).find(1).readonly? @@ -85,14 +85,14 @@ class ReadOnlyTest < ActiveRecord::TestCase # Oracle barfs on this because the join includes unqualified and # conflicting column names unless current_adapter?(:OracleAdapter) - Post.send(:with_scope, :find => { :joins => ', developers' }) do + Post.joins(', developers').scoped do assert Post.find(1).readonly? assert Post.readonly.find(1).readonly? assert !Post.readonly(false).find(1).readonly? end end - Post.send(:with_scope, :find => { :readonly => true }) do + Post.readonly(true).scoped do assert Post.find(1).readonly? assert Post.readonly.find(1).readonly? assert !Post.readonly(false).find(1).readonly? diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 16f05f2198..7dd5698dcf 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -63,7 +63,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_column_null_not_null - subscriber = Subscriber.find(:first) + subscriber = Subscriber.first assert subscriber.column_for_attribute("name").null assert !subscriber.column_for_attribute("nick").null end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index edf38cb7a3..342f7a86fa 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -53,8 +53,8 @@ class RelationScopingTest < ActiveRecord::TestCase end def test_scoped_find_last_preserves_scope - lowest_salary = Developer.first :order => "salary ASC" - highest_salary = Developer.first :order => "salary DESC" + lowest_salary = Developer.order("salary ASC").first + highest_salary = Developer.order("salary DESC").first Developer.order("salary").scoping do assert_equal highest_salary, Developer.last @@ -323,8 +323,8 @@ class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers, :posts def test_default_scope - expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } + expected = Developer.scoped(:order => 'salary DESC').all.collect { |dev| dev.salary } + received = DeveloperOrderedBySalary.all.collect { |dev| dev.salary } assert_equal expected, received end @@ -362,12 +362,12 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_default_scope_with_conditions_string - assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort + assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort assert_equal nil, DeveloperCalledDavid.create!.name end def test_default_scope_with_conditions_hash - assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort + assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort assert_equal 'Jamis', DeveloperCalledJamis.create!.name end @@ -395,23 +395,9 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 50000, wheres[:salary] end - def test_method_scope - expected = Developer.find(:all, :order => 'salary DESC, 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 => 'salary DESC, 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_scope_overwrites_default - expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.name } - received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name } + expected = Developer.scoped(:order => 'salary DESC, name DESC').all.collect { |dev| dev.name } + received = DeveloperOrderedBySalary.by_name.all.collect { |dev| dev.name } assert_equal expected, received end @@ -427,17 +413,9 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end - def test_nested_exclusive_scope - expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do - DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } - end - assert_equal expected, received - end - def test_order_in_default_scope_should_prevail - expected = Developer.find(:all, :order => 'salary desc').collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary } + expected = Developer.scoped(:order => 'salary desc').all.collect { |dev| dev.salary } + received = DeveloperOrderedBySalary.scoped(:order => 'salary').all.collect { |dev| dev.salary } assert_equal expected, received end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 9ef703fd9b..8a7a2441d4 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -130,9 +130,9 @@ module ActiveRecord test 'merging a hash into a relation' do relation = Relation.new :a, :b - relation = relation.merge where: ['lol'], readonly: true + relation = relation.merge where: :lol, readonly: true - assert_equal ['lol'], relation.where_values + assert_equal [:lol], relation.where_values assert_equal true, relation.readonly_value end @@ -156,6 +156,21 @@ module ActiveRecord relation = Relation.new(:a, :b, where: [:foo]) assert_equal [:foo], relation.where_values end + + test 'merging a single where value' do + relation = Relation.new(:a, :b) + relation.merge!(where: :foo) + assert_equal [:foo], relation.where_values + end + + test 'merging a hash interpolates conditions' do + klass = stub + klass.stubs(:sanitize_sql).with(['foo = ?', 'bar']).returns('foo = bar') + + relation = Relation.new(klass, :b) + relation.merge!(where: ['foo = ?', 'bar']) + assert_equal ['foo = bar'], relation.where_values + end end class RelationMutationTest < ActiveSupport::TestCase @@ -221,8 +236,8 @@ module ActiveRecord end test 'merge!' do - assert relation.merge!(where: ['foo']).equal?(relation) - assert_equal ['foo'], relation.where_values + assert relation.merge!(where: :foo).equal?(relation) + assert_equal [:foo], relation.where_values end end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 5e938968a3..4b8b57bac2 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -282,7 +282,7 @@ class RelationTest < ActiveRecord::TestCase end def test_find_on_hash_conditions - assert_equal Topic.find(:all, :conditions => {:approved => false}), Topic.where({ :approved => false }).to_a + assert_equal Topic.scoped(:where => {:approved => false}).all, Topic.where({ :approved => false }).to_a end def test_joins_with_string_array @@ -330,7 +330,6 @@ class RelationTest < ActiveRecord::TestCase end def test_respond_to_class_methods_and_scopes - assert DeveloperOrderedBySalary.scoped.respond_to?(:all_ordered_by_name) assert Topic.scoped.respond_to?(:by_lifo) end @@ -1060,10 +1059,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.all, all_posts.all end - def test_extensions_with_except - assert_equal 2, Topic.named_extension.order(:author_name).except(:order).two - end - def test_only relation = Post.where(:author_id => 1).order('id ASC').limit(1) assert_equal [posts(:welcome)], relation.all @@ -1075,10 +1070,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.limit(1).all.first, all_posts.first end - def test_extensions_with_only - assert_equal 2, Topic.named_extension.order(:author_name).only(:order).two - end - def test_anonymous_extension relation = Post.where(:author_id => 1).order('id ASC').extending do def author @@ -1100,36 +1091,26 @@ class RelationTest < ActiveRecord::TestCase assert_equal Post.order(Post.arel_table[:title]).all, Post.order("title").all end - def test_order_with_find_with_order - assert_equal 'zyke', CoolCar.order('name desc').find(:first, :order => 'id').name - assert_equal 'zyke', FastCar.order('name desc').find(:first, :order => 'id').name - end - def test_default_scope_order_with_scope_order assert_equal 'zyke', CoolCar.order_using_new_style.limit(1).first.name - assert_equal 'zyke', CoolCar.order_using_old_style.limit(1).first.name assert_equal 'zyke', FastCar.order_using_new_style.limit(1).first.name - assert_equal 'zyke', FastCar.order_using_old_style.limit(1).first.name end def test_order_using_scoping car1 = CoolCar.order('id DESC').scoping do - CoolCar.find(:first, :order => 'id asc') + CoolCar.scoped(:order => 'id asc').first end assert_equal 'zyke', car1.name car2 = FastCar.order('id DESC').scoping do - FastCar.find(:first, :order => 'id asc') + FastCar.scoped(:order => 'id asc').first end assert_equal 'zyke', car2.name end def test_unscoped_block_style assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_new_style.limit(1).first.name} - assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_old_style.limit(1).first.name} - assert_equal 'honda', FastCar.unscoped { FastCar.order_using_new_style.limit(1).first.name} - assert_equal 'honda', FastCar.unscoped { FastCar.order_using_old_style.limit(1).first.name} end def test_intersection_with_array @@ -1140,10 +1121,6 @@ class RelationTest < ActiveRecord::TestCase assert_equal [rails_author], relation & [rails_author] end - def test_removing_limit_with_options - assert_not_equal 1, Post.limit(1).all(:limit => nil).count - end - def test_primary_key assert_equal "id", Post.scoped.primary_key end diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index c72b7b35cd..7ac34bc71e 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -86,7 +86,7 @@ class AssociationValidationTest < ActiveRecord::TestCase assert !r.valid? assert r.errors[:topic].any? - r.topic = Topic.find :first + r.topic = Topic.first assert r.valid? end diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index ec09479c95..c173ee9a15 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -189,7 +189,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert t_utf8.save, "Should save t_utf8 as unique" # If database hasn't UTF-8 character set, this test fails - if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!" + if Topic.scoped(:select => 'LOWER(title) AS title').find(t_utf8).title == "я тоже уникальный!" t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!") assert !t2_utf8.valid?, "Shouldn't be valid" assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique" @@ -261,10 +261,10 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert i1.errors[:value].any?, "Should not be empty" end - def test_validates_uniqueness_inside_with_scope + def test_validates_uniqueness_inside_scoping Topic.validates_uniqueness_of(:title) - Topic.send(:with_scope, :find => { :conditions => { :author_name => "David" } }) do + Topic.where(:author_name => "David").scoping do t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary") assert t1.save t2 = Topic.new("title" => "I'm unique!", "author_name" => "David") diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index e575a98170..b11b330374 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -93,30 +93,6 @@ class ValidationsTest < ActiveRecord::TestCase end end - def test_scoped_create_without_attributes - WrongReply.send(:with_scope, :create => {}) do - assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! } - end - end - - def test_create_with_exceptions_using_scope_for_protected_attributes - assert_nothing_raised do - ProtectedPerson.send(:with_scope, :create => { :first_name => "Mary" } ) do - person = ProtectedPerson.create! :addon => "Addon" - assert_equal person.first_name, "Mary", "scope should ignore attr_protected" - end - end - end - - def test_create_with_exceptions_using_scope_and_empty_attributes - assert_nothing_raised do - ProtectedPerson.send(:with_scope, :create => { :first_name => "Mary" } ) do - person = ProtectedPerson.create! - assert_equal person.first_name, "Mary", "should be ok when no attributes are passed to create!" - end - end - end - def test_create_without_validation reply = WrongReply.new assert !reply.save diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 42ac81690f..b4bc0ad5fa 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -15,7 +15,6 @@ class Car < ActiveRecord::Base scope :incl_engines, -> { includes(:engines) } scope :order_using_new_style, -> { order('name asc') } - scope :order_using_old_style, -> { { :order => 'name asc' } } end diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index cf73c28ded..3e9f1b0635 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -19,7 +19,7 @@ class Comment < ActiveRecord::Base end def self.search_by_type(q) - self.find(:all, :conditions => ["#{QUOTED_TYPE} = ?", q]) + self.scoped(:where => ["#{QUOTED_TYPE} = ?", q]).all end def self.all_as_method diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 4cc4947e3b..83482f4d07 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -2,20 +2,20 @@ require 'ostruct' module DeveloperProjectsAssociationExtension def find_most_recent - find(:first, :order => "id DESC") + scoped(:order => "id DESC").first end end module DeveloperProjectsAssociationExtension2 def find_least_recent - find(:first, :order => "id ASC") + scoped(:order => "id ASC").first end end class Developer < ActiveRecord::Base has_and_belongs_to_many :projects do def find_most_recent - find(:first, :order => "id DESC") + scoped(:order => "id DESC").first end end @@ -37,7 +37,7 @@ class Developer < ActiveRecord::Base :association_foreign_key => "project_id", :extend => DeveloperProjectsAssociationExtension do def find_least_recent - find(:first, :order => "id ASC") + scoped(:order => "id ASC").first end end @@ -57,12 +57,6 @@ class Developer < ActiveRecord::Base def log=(message) audit_logs.build :message => message end - - def self.all_johns - self.with_exclusive_scope :find => where(:name => 'John') do - self.all - end - end end class AuditLog < ActiveRecord::Base @@ -102,12 +96,6 @@ class DeveloperOrderedBySalary < ActiveRecord::Base default_scope { order('salary DESC') } scope :by_name, -> { order('name DESC') } - - def self.all_ordered_by_name - with_scope(:find => { :order => 'name DESC' }) do - find(:all) - end - end end class DeveloperCalledDavid < ActiveRecord::Base diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 5002ab9ff8..1aaf9a1b82 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -9,11 +9,6 @@ class Post < ActiveRecord::Base 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' - } - } belongs_to :author do def greeting @@ -32,13 +27,11 @@ class Post < ActiveRecord::Base scope :with_special_comments, -> { joins(:comments).where(: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} } } - } + scope :with_post, ->(post_id) { joins(:comments).where(:comments => { :post_id => post_id }) } has_many :comments do def find_most_recent - find(:first, :order => "id DESC") + scoped(:order => "id DESC").first end def newest @@ -71,8 +64,8 @@ class Post < ActiveRecord::Base has_many :taggings, :as => :taggable has_many :tags, :through => :taggings do def add_joins_and_select - find :all, :select => 'tags.*, authors.id as author_id', - :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id' + scoped(:select => 'tags.*, authors.id as author_id', + :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id').all end end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 785839be75..079e403444 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -2,7 +2,7 @@ class Topic < ActiveRecord::Base scope :base, -> { scoped } scope :written_before, lambda { |time| if time - { :conditions => ['written_on < ?', time] } + where 'written_on < ?', time end } scope :approved, -> { where(:approved => true) } @@ -11,11 +11,7 @@ class Topic < ActiveRecord::Base scope :scope_with_lambda, lambda { scoped } scope :by_lifo, -> { where(:author_name => 'lifo') } - - ActiveSupport::Deprecation.silence do - scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}} - scope :replied, :conditions => ['replies_count > 0'] - end + scope :replied, -> { where 'replies_count > 0' } scope 'approved_as_string', -> { where(:approved => true) } scope :anonymous_extension, -> { scoped } do @@ -35,18 +31,6 @@ class Topic < ActiveRecord::Base 2 end end - module MultipleExtensionOne - def extension_one - 1 - end - end - module MultipleExtensionTwo - def extension_two - 2 - end - end - 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" diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 25b416a906..e51db50ae3 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,8 +1,8 @@ ActiveRecord::Schema.define do %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings - postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones).each do |table_name| - execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" + postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name| + execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" end execute 'DROP SEQUENCE IF EXISTS companies_nonstd_seq CASCADE' @@ -10,6 +10,8 @@ ActiveRecord::Schema.define do execute "ALTER TABLE companies ALTER COLUMN id SET DEFAULT nextval('companies_nonstd_seq')" execute 'DROP SEQUENCE IF EXISTS companies_id_seq' + execute 'DROP FUNCTION IF EXISTS partitioned_insert_trigger()' + %w(accounts_id_seq developers_id_seq projects_id_seq topics_id_seq customers_id_seq orders_id_seq).each do |seq_name| execute "SELECT setval('#{seq_name}', 100)" end @@ -125,6 +127,37 @@ _SQL ); _SQL +begin + execute <<_SQL + CREATE TABLE postgresql_partitioned_table_parent ( + id SERIAL PRIMARY KEY, + number integer + ); + CREATE TABLE postgresql_partitioned_table ( ) + INHERITS (postgresql_partitioned_table_parent); + + CREATE OR REPLACE FUNCTION partitioned_insert_trigger() + RETURNS TRIGGER AS $$ + BEGIN + INSERT INTO postgresql_partitioned_table VALUES (NEW.*); + RETURN NULL; + END; + $$ + LANGUAGE plpgsql; + + CREATE TRIGGER insert_partitioning_trigger + BEFORE INSERT ON postgresql_partitioned_table_parent + FOR EACH ROW EXECUTE PROCEDURE partitioned_insert_trigger(); +_SQL +rescue ActiveRecord::StatementInvalid => e + if e.message =~ /language "plpgsql" does not exist/ + execute "CREATE LANGUAGE 'plpgsql';" + retry + else + raise e + end +end + begin execute <<_SQL CREATE TABLE postgresql_xml_data_type ( diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index b6c3e91db8..d32c0f3aed 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,9 @@ ## Rails 4.0.0 (unreleased) ## +* Inflector no longer applies ice -> ouse to words like slice, police, ets *Wes Morgan* + +* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations *twinturbo* + * Make Module#delegate stop using `send` - can no longer delegate to private methods. *dasch* * AS::Callbacks: deprecate `:rescuable` option. *Bogdan Gusiev* diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index d26d71b615..2c874e932e 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -19,6 +19,6 @@ Gem::Specification.new do |s| s.rdoc_options.concat ['--encoding', 'UTF-8'] s.add_dependency('i18n', '~> 0.6') - s.add_dependency('multi_json', '~> 1.0') + s.add_dependency('multi_json', '~> 1.3') s.add_dependency('tzinfo', '~> 0.3.31') end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 2a569d9a9b..cbeba3139a 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -76,7 +76,8 @@ module ActiveSupport # def run_callbacks(kind, key = nil, &block) #TODO: deprecate key argument - self.class.__run_callbacks(kind, self, &block) + runner_name = self.class.__define_callbacks(kind, self) + send(runner_name, &block) end private @@ -323,18 +324,17 @@ module ActiveSupport method << callbacks method << "halted ? false : (block_given? ? value : true)" - method.flatten.compact.join("\n") + method.join("\n") end end module ClassMethods - # This method runs callback chain for the given kind. - # If this called first time it creates a new callback method for the kind. + # This method defines callback chain method for the given kind + # if it was not yet defined. # This generated method plays caching role. - # - def __run_callbacks(kind, object, &blk) #:nodoc: + def __define_callbacks(kind, object) #:nodoc: name = __callback_runner_name(kind) unless object.respond_to?(name, true) str = object.send("_#{kind}_callbacks").compile @@ -343,7 +343,7 @@ module ActiveSupport protected :#{name} RUBY_EVAL end - object.send(name, &blk) + name end def __reset_runner(symbol) diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 6162f7af27..44d90ef732 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -16,7 +16,7 @@ class Array # %w( a b c d ).to(10) # => %w( a b c d ) # %w().to(0) # => %w() def to(position) - self.first position + 1 + first position + 1 end # Equal to <tt>self[1]</tt>. diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index f3d06ecb2f..24aa28b895 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -9,28 +9,32 @@ class Array # * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ") # * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ") def to_sentence(options = {}) + options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) + + default_connectors = { + :words_connector => ', ', + :two_words_connector => ' and ', + :last_word_connector => ', and ' + } if defined?(I18n) - default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale]) - default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale]) - default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale]) - else - default_words_connector = ", " - default_two_words_connector = " and " - default_last_word_connector = ", and " + namespace = 'support.array.' + default_connectors.each_key do |name| + i18n_key = (namespace + name.to_s).to_sym + default_connectors[name] = I18n.translate i18n_key, :locale => options[:locale] + end end - options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) - options.reverse_merge! :words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector + options.reverse_merge! default_connectors case length - when 0 - "" - when 1 - self[0].to_s.dup - when 2 - "#{self[0]}#{options[:two_words_connector]}#{self[1]}" - else - "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}" + when 0 + '' + when 1 + self[0].to_s.dup + when 2 + "#{self[0]}#{options[:two_words_connector]}#{self[1]}" + else + "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}" end end @@ -45,14 +49,14 @@ class Array # Blog.all.to_formatted_s(:db) # => "1,2,3" def to_formatted_s(format = :default) case format - when :db - if respond_to?(:empty?) && self.empty? - "null" - else - collect { |element| element.id }.join(",") - end + when :db + if empty? + 'null' else - to_default_s + collect { |element| element.id }.join(',') + end + else + to_default_s end end alias_method :to_default_s, :to_s @@ -86,20 +90,20 @@ class Array # </project> # </projects> # - # Otherwise the root element is "records": + # Otherwise the root element is "objects": # # [{:foo => 1, :bar => 2}, {:baz => 3}].to_xml # # <?xml version="1.0" encoding="UTF-8"?> - # <records type="array"> - # <record> + # <objects type="array"> + # <object> # <bar type="integer">2</bar> # <foo type="integer">1</foo> - # </record> - # <record> + # </object> + # <object> # <baz type="integer">3</baz> - # </record> - # </records> + # </object> + # </objects> # # If the collection is empty the root element is "nil-classes" by default: # @@ -139,26 +143,28 @@ class Array options = options.dup options[:indent] ||= 2 options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) - options[:root] ||= if first.class.to_s != "Hash" && all? { |e| e.is_a?(first.class) } - underscored = ActiveSupport::Inflector.underscore(first.class.name) - ActiveSupport::Inflector.pluralize(underscored).tr('/', '_') - else - "objects" - end + options[:root] ||= \ + if first.class != Hash && all? { |e| e.is_a?(first.class) } + underscored = ActiveSupport::Inflector.underscore(first.class.name) + ActiveSupport::Inflector.pluralize(underscored).tr('/', '_') + else + 'objects' + end builder = options[:builder] builder.instruct! unless options.delete(:skip_instruct) root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) children = options.delete(:children) || root.singularize + attributes = options[:skip_types] ? {} : {:type => 'array'} - attributes = options[:skip_types] ? {} : {:type => "array"} - return builder.tag!(root, attributes) if empty? - - builder.__send__(:method_missing, root, attributes) do - each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) } - yield builder if block_given? + if empty? + builder.tag!(root, attributes) + else + builder.__send__(:method_missing, root, attributes) do + each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) } + yield builder if block_given? + end end end - end diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index 2b3f639cb1..ac1ae53db0 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -82,11 +82,9 @@ class Array # # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]] # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]] - def split(value = nil) - using_block = block_given? - + def split(value = nil, &block) inject([[]]) do |results, element| - if (using_block && yield(element)) || (value == element) + if block && block.call(element) || value == element results << [] else results.last << element diff --git a/activesupport/lib/active_support/core_ext/array/uniq_by.rb b/activesupport/lib/active_support/core_ext/array/uniq_by.rb index ac3dedc0e3..3bedfa9a61 100644 --- a/activesupport/lib/active_support/core_ext/array/uniq_by.rb +++ b/activesupport/lib/active_support/core_ext/array/uniq_by.rb @@ -6,8 +6,7 @@ class Array # [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2] # def uniq_by(&block) - ActiveSupport::Deprecation.warn "uniq_by " \ - "is deprecated. Use Array#uniq instead", caller + ActiveSupport::Deprecation.warn 'uniq_by is deprecated. Use Array#uniq instead', caller uniq(&block) end @@ -15,8 +14,7 @@ class Array # # Same as +uniq_by+, but modifies +self+. def uniq_by!(&block) - ActiveSupport::Deprecation.warn "uniq_by! " \ - "is deprecated. Use Array#uniq! instead", caller + ActiveSupport::Deprecation.warn 'uniq_by! is deprecated. Use Array#uniq! instead', caller uniq!(&block) end end diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 305ed4964b..c64685a694 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -109,7 +109,7 @@ class Class end private - def singleton_class? - ancestors.first != self - end + def singleton_class? + ancestors.first != self + end end diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index 95eb94fdf6..5cb528cfe9 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -29,7 +29,7 @@ class Class def cattr_reader(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name") unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) unless defined? @@#{sym} @@#{sym} = nil @@ -53,7 +53,7 @@ class Class def cattr_writer(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name") unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) unless defined? @@#{sym} @@#{sym} = nil @@ -71,7 +71,7 @@ class Class end EOS end - self.send("#{sym}=", yield) if block_given? + send("#{sym}=", yield) if block_given? end end diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb index 0634f20e3c..ff870f5fd1 100644 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb @@ -20,23 +20,21 @@ class Class define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false end -private - - # Take the object being set and store it in a method. This gives us automatic - # inheritance behavior, without having to store the object in an instance - # variable and look up the superclass chain manually. - def _stash_object_in_method(object, method, instance_reader = true) - singleton_class.remove_possible_method(method) - singleton_class.send(:define_method, method) { object } - remove_possible_method(method) - define_method(method) { object } if instance_reader - end - - def _superclass_delegating_accessor(name, options = {}) - singleton_class.send(:define_method, "#{name}=") do |value| - _stash_object_in_method(value, name, options[:instance_reader] != false) + private + # Take the object being set and store it in a method. This gives us automatic + # inheritance behavior, without having to store the object in an instance + # variable and look up the superclass chain manually. + def _stash_object_in_method(object, method, instance_reader = true) + singleton_class.remove_possible_method(method) + singleton_class.send(:define_method, method) { object } + remove_possible_method(method) + define_method(method) { object } if instance_reader end - send("#{name}=", nil) - end + def _superclass_delegating_accessor(name, options = {}) + singleton_class.send(:define_method, "#{name}=") do |value| + _stash_object_in_method(value, name, options[:instance_reader] != false) + end + send("#{name}=", nil) + end end diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 6d4270f8b0..323126fcfa 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -5,7 +5,15 @@ require 'active_support/core_ext/date/zones' require 'active_support/core_ext/time/zones' class Date - DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 } + DAYS_INTO_WEEK = { + :monday => 0, + :tuesday => 1, + :wednesday => 2, + :thursday => 3, + :friday => 4, + :saturday => 5, + :sunday => 6 + } class << self # Returns a new Date representing the date 1 day ago (i.e. yesterday's date). @@ -31,7 +39,7 @@ class Date # Returns true if the Date object's date is today. def today? - self.to_date == ::Date.current # we need the to_date because of DateTime + to_date == ::Date.current # we need the to_date because of DateTime end # Returns true if the Date object's date lies in the future. @@ -105,9 +113,9 @@ class Date # Date.new(2007, 5, 12).change(:year => 2005, :month => 1) # => Date.new(2005, 1, 12) def change(options) ::Date.new( - options[:year] || self.year, - options[:month] || self.month, - options[:day] || self.day + options.fetch(:year, year), + options.fetch(:month, month), + options.fetch(:day, day) ) end @@ -166,7 +174,7 @@ class Date def end_of_week(start_day = :monday) days_to_end = 6 - days_to_week_start(start_day) result = self + days_to_end.days - self.acts_like?(:time) ? result.end_of_day : result + acts_like?(:time) ? result.end_of_day : result end alias :at_end_of_week :end_of_week @@ -180,7 +188,7 @@ class Date # week. Default is +:monday+. +DateTime+ objects have their time set to 0:00. def prev_week(day = :monday) result = (self - 7).beginning_of_week + DAYS_INTO_WEEK[day] - self.acts_like?(:time) ? result.change(:hour => 0) : result + acts_like?(:time) ? result.change(:hour => 0) : result end alias :last_week :prev_week @@ -193,43 +201,57 @@ class Date # Returns a new Date/DateTime representing the start of the given day in next week (default is :monday). def next_week(day = :monday) result = (self + 7).beginning_of_week + DAYS_INTO_WEEK[day] - self.acts_like?(:time) ? result.change(:hour => 0) : result + acts_like?(:time) ? result.change(:hour => 0) : result end # Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00) def beginning_of_month - self.acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1) + acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1) end alias :at_beginning_of_month :beginning_of_month # Returns a new Date/DateTime representing the end of the month (last day of the month; DateTime objects will have time set to 0:00) def end_of_month - last_day = ::Time.days_in_month( self.month, self.year ) - self.acts_like?(:time) ? change(:day => last_day, :hour => 23, :min => 59, :sec => 59) : change(:day => last_day) + last_day = ::Time.days_in_month(month, year) + if acts_like?(:time) + change(:day => last_day, :hour => 23, :min => 59, :sec => 59) + else + change(:day => last_day) + end end alias :at_end_of_month :end_of_month # Returns a new Date/DateTime representing the start of the quarter (1st of january, april, july, october; DateTime objects will have time set to 0:00) def beginning_of_quarter - beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month }) + first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month } + beginning_of_month.change(:month => first_quarter_month) end alias :at_beginning_of_quarter :beginning_of_quarter # Returns a new Date/DateTime representing the end of the quarter (last day of march, june, september, december; DateTime objects will have time set to 23:59:59) def end_of_quarter - beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month + last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month } + beginning_of_month.change(:month => last_quarter_month).end_of_month end alias :at_end_of_quarter :end_of_quarter # Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00) def beginning_of_year - self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0) : change(:month => 1, :day => 1) + if acts_like?(:time) + change(:month => 1, :day => 1, :hour => 0) + else + change(:month => 1, :day => 1) + end end alias :at_beginning_of_year :beginning_of_year # Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59) def end_of_year - self.acts_like?(:time) ? change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31) + if acts_like?(:time) + change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) + else + change(:month => 12, :day => 31) + end end alias :at_end_of_year :end_of_year diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 3262c254f7..97e3c71992 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -5,12 +5,15 @@ require 'active_support/core_ext/module/remove_method' class Date DATE_FORMATS = { - :short => "%e %b", - :long => "%B %e, %Y", - :db => "%Y-%m-%d", - :number => "%Y%m%d", - :long_ordinal => lambda { |date| date.strftime("%B #{ActiveSupport::Inflector.ordinalize(date.day)}, %Y") }, # => "April 25th, 2007" - :rfc822 => "%e %b %Y" + :short => '%e %b', + :long => '%B %e, %Y', + :db => '%Y-%m-%d', + :number => '%Y%m%d', + :long_ordinal => lambda { |date| + day_format = ActiveSupport::Inflector.ordinalize(date.day) + date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007" + }, + :rfc822 => '%e %b %Y' } # Ruby 1.9 has Date#to_time which converts to localtime only. @@ -40,7 +43,7 @@ class Date # or Proc instance that takes a date argument as the value. # # # config/initializers/time_formats.rb - # Date::DATE_FORMATS[:month_and_year] = "%B %Y" + # Date::DATE_FORMATS[:month_and_year] = '%B %Y' # Date::DATE_FORMATS[:short_ordinal] = lambda { |date| date.strftime("%B #{date.day.ordinalize}") } def to_formatted_s(format = :default) if formatter = DATE_FORMATS[format] @@ -58,7 +61,7 @@ class Date # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005" def readable_inspect - strftime("%a, %d %b %Y") + strftime('%a, %d %b %Y') end alias_method :default_inspect, :inspect alias_method :inspect, :readable_inspect diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 6f730e4b6f..0e5aa5af10 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -4,8 +4,8 @@ class DateTime class << self # *DEPRECATED*: Use +DateTime.civil_from_format+ directly. def local_offset - ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. ' \ - 'Use DateTime.civil_from_format directly.', caller + ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. Use DateTime.civil_from_format directly.', caller + ::Time.local(2012).utc_offset.to_r / 86400 end @@ -35,14 +35,14 @@ class DateTime # minute is passed, then sec is set to 0. def change(options) ::DateTime.civil( - options[:year] || year, - options[:month] || month, - options[:day] || day, - options[:hour] || hour, - options[:min] || (options[:hour] ? 0 : min), - options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec), - options[:offset] || offset, - options[:start] || start + options.fetch(:year, year), + options.fetch(:month, month), + options.fetch(:day, day), + options.fetch(:hour, hour), + options.fetch(:min, options[:hour] ? 0 : min), + options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec), + options.fetch(:offset, offset), + options.fetch(:start, start) ) end @@ -53,8 +53,16 @@ class DateTime def advance(options) d = to_date.advance(options) datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) - seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600 - seconds_to_advance == 0 ? datetime_advanced_by_date : datetime_advanced_by_date.since(seconds_to_advance) + seconds_to_advance = \ + options.fetch(:seconds, 0) + + options.fetch(:minutes, 0) * 60 + + options.fetch(:hours, 0) * 3600 + + if seconds_to_advance.zero? + datetime_advanced_by_date + else + datetime_advanced_by_date.since seconds_to_advance + end end # Returns a new DateTime representing the time a number of seconds ago @@ -108,4 +116,5 @@ class DateTime def <=>(other) super other.to_datetime end + end diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index dc55e9c33c..6338dc6397 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -30,7 +30,7 @@ class DateTime # datetime argument as the value. # # # config/initializers/time_formats.rb - # Time::DATE_FORMATS[:month_and_year] = "%B %Y" + # Time::DATE_FORMATS[:month_and_year] = '%B %Y' # Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") } def to_formatted_s(format = :default) if formatter = ::Time::DATE_FORMATS[format] @@ -61,7 +61,11 @@ class DateTime # Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class. # If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time. def to_time - self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000) : self + if offset == 0 + ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000) + else + self + end end # Returns DateTime with local offset for given year if format is local else offset is zero diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb index 6fa55a9255..823735d3e2 100644 --- a/activesupport/lib/active_support/core_ext/date_time/zones.rb +++ b/activesupport/lib/active_support/core_ext/date_time/zones.rb @@ -14,8 +14,10 @@ class DateTime # # DateTime.new(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 def in_time_zone(zone = ::Time.zone) - return self unless zone - - ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone)) + if zone + ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone)) + else + self + end end end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 77a5087981..b9c632e4f5 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -11,7 +11,7 @@ module Enumerable # It can also calculate the sum without the use of a block. # # [5, 15, 10].sum # => 30 - # ["foo", "bar"].sum # => "foobar" + # ['foo', 'bar'].sum # => "foobar" # [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5] # # The default sum of an empty list is zero. You can override this default: @@ -34,8 +34,11 @@ module Enumerable # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...} # def index_by - return to_enum :index_by unless block_given? - Hash[map { |elem| [yield(elem), elem] }] + if block_given? + Hash[map { |elem| [yield(elem), elem] }] + else + to_enum :index_by + end end # Returns true if the enumerable has more than 1 element. Functionally equivalent to enum.to_a.size > 1. @@ -48,7 +51,7 @@ module Enumerable cnt > 1 end else - any?{ (cnt += 1) > 1 } + any? { (cnt += 1) > 1 } end end @@ -62,8 +65,11 @@ class Range #:nodoc: # Optimize range sum to use arithmetic progression if a block is not given and # we have a range of numeric values. def sum(identity = 0) - return super if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer)) - actual_last = exclude_end? ? (last - 1) : last - (actual_last - first + 1) * (actual_last + first) / 2 + if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer)) + super + else + actual_last = exclude_end? ? (last - 1) : last + (actual_last - first + 1) * (actual_last + first) / 2 + end end end diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb index fc3277f4d2..9e504851e7 100644 --- a/activesupport/lib/active_support/core_ext/file/atomic.rb +++ b/activesupport/lib/active_support/core_ext/file/atomic.rb @@ -2,15 +2,15 @@ class File # Write to a file atomically. Useful for situations where you don't # want other processes or threads to see half-written files. # - # File.atomic_write("important.file") do |file| - # file.write("hello") + # File.atomic_write('important.file') do |file| + # file.write('hello') # end # # If your temp directory is not on the same filesystem as the file you're # trying to write, you can provide a different temporary directory. # - # File.atomic_write("/data/something.important", "/data/tmp") do |file| - # file.write("hello") + # File.atomic_write('/data/something.important', '/data/tmp') do |file| + # file.write('hello') # end def self.atomic_write(file_name, temp_dir = Dir.tmpdir) require 'tempfile' unless defined?(Tempfile) @@ -26,8 +26,14 @@ class File old_stat = stat(file_name) rescue Errno::ENOENT # No old permissions, write a temp file to determine the defaults - check_name = join(dirname(file_name), ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}") - open(check_name, "w") { } + temp_file_name = [ + '.permissions_check', + Thread.current.object_id, + Process.pid, + rand(1000000) + ].join('.') + check_name = join(dirname(file_name), temp_file_name) + open(check_name, 'w') { } old_stat = stat(check_name) unlink(check_name) end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 5f07bb4f5a..469dc41f2d 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -8,7 +8,7 @@ require 'active_support/core_ext/string/inflections' class Hash # Returns a string containing an XML representation of its receiver: # - # {"foo" => 1, "bar" => 2}.to_xml + # {'foo' => 1, 'bar' => 2}.to_xml # # => # # <?xml version="1.0" encoding="UTF-8"?> # # <hash> @@ -26,20 +26,20 @@ class Hash # # * 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 <tt>:root</tt>, and +key+ singularized as second argument. The + # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The # callable can add nodes by using <tt>options[:builder]</tt>. # - # "foo".to_xml(lambda { |options, key| options[:builder].b(key) }) + # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) }) # # => "<b>foo</b>" # # * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>. - # + # # class Foo # def to_xml(options) - # options[:builder].bar "fooing!" + # options[:builder].bar 'fooing!' # end # end - # + # # {:foo => Foo.new}.to_xml(:skip_instruct => true) # # => "<hash><bar>fooing!</bar></hash>" # @@ -71,7 +71,7 @@ class Hash options = options.dup options[:indent] ||= 2 - options[:root] ||= "hash" + options[:root] ||= 'hash' options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) builder = options[:builder] @@ -100,24 +100,24 @@ class Hash [] else case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a? - when "Array" + when 'Array' entries.collect { |v| typecast_xml_value(v) } - when "Hash" + when 'Hash' [typecast_xml_value(entries)] else raise "can't typecast #{entries.inspect}" end end - elsif value['type'] == 'file' || - (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?)) - content = value["__content__"] - if parser = ActiveSupport::XmlMini::PARSING[value["type"]] + elsif value['type'] == 'file' || + (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?)) + content = value['__content__'] + if parser = ActiveSupport::XmlMini::PARSING[value['type']] parser.arity == 1 ? parser.call(content) : parser.call(content, value) else content end elsif value['type'] == 'string' && value['nil'] != 'true' - "" + '' # blank or nil parsed values are represented by nil elsif value.blank? || value['nil'] == 'true' nil @@ -131,7 +131,7 @@ class Hash # Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with # how multipart uploaded files from HTML appear - xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value + xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value end when 'Array' value.map! { |i| typecast_xml_value(i) } @@ -145,9 +145,9 @@ class Hash def unrename_keys(params) case params.class.to_s - when "Hash" - Hash[params.map { |k,v| [k.to_s.tr("-", "_"), unrename_keys(v)] } ] - when "Array" + when 'Hash' + Hash[params.map { |k,v| [k.to_s.tr('-', '_'), unrename_keys(v)] } ] + when 'Array' params.map { |v| unrename_keys(v) } else params diff --git a/activesupport/lib/active_support/core_ext/hash/diff.rb b/activesupport/lib/active_support/core_ext/hash/diff.rb index b904f49fa8..855dcb38bc 100644 --- a/activesupport/lib/active_support/core_ext/hash/diff.rb +++ b/activesupport/lib/active_support/core_ext/hash/diff.rb @@ -7,7 +7,9 @@ class Hash # {1 => 2}.diff(1 => 3) # => {1 => 2} # {}.diff(1 => 2) # => {1 => 2} # {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4} - def diff(h2) - dup.delete_if { |k, v| h2[k] == v }.merge!(h2.dup.delete_if { |k, v| has_key?(k) }) + def diff(other) + dup. + delete_if { |k, v| other[k] == v }. + merge!(other.dup.delete_if { |k, v| has_key?(k) }) end end diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb index 89729df258..5a61906222 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -9,7 +9,7 @@ class Hash # for instance: # # {:a => 1}.with_indifferent_access.except(:a) # => {} - # {:a => 1}.with_indifferent_access.except("a") # => {} + # {:a => 1}.with_indifferent_access.except('a') # => {} # def except(*keys) dup.except!(*keys) diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index 65c3736593..230a84dabc 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -25,6 +25,7 @@ class Hash end result end + alias_method :to_options, :symbolize_keys # Destructively convert all keys to symbols, as long as they respond # to +to_sym+. @@ -34,8 +35,6 @@ class Hash end self end - - alias_method :to_options, :symbolize_keys alias_method :to_options!, :symbolize_keys! # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch. @@ -43,13 +42,13 @@ class Hash # as keys, this will fail. # # ==== Examples - # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years" - # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key: name" - # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing + # { :name => 'Rob', :years => '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years" + # { :name => 'Rob', :age => '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name" + # { :name => 'Rob', :age => '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing def assert_valid_keys(*valid_keys) valid_keys.flatten! each_key do |k| - raise(ArgumentError, "Unknown key: #{k}") unless valid_keys.include?(k) + raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k) end end end diff --git a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb index 01863a162b..6074103484 100644 --- a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb @@ -18,6 +18,5 @@ class Hash # right wins if there is no left merge!( other_hash ){|key,left,right| left } end - alias_method :reverse_update, :reverse_merge! end diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index 45181f0e16..fdeea4c148 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -31,6 +31,6 @@ class Hash # Removes and returns the key/value pairs matching the given keys. # {:a => 1, :b => 2, :c => 3, :d => 4}.extract!(:a, :b) # => {:a => 1, :b => 2} def extract!(*keys) - keys.each_with_object({}) {|key, result| result[key] = delete(key) } + keys.each_with_object({}) { |key, result| result[key] = delete(key) } end end diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb index d5b590e9f0..2073cac98d 100644 --- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb +++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb @@ -1,8 +1,8 @@ module Kernel unless respond_to?(:debugger) - # Starts a debugging session if ruby-debug has been loaded (call rails server --debugger to do load it). + # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it). def debugger - message = "\n***** Debugger requested, but was not available (ensure ruby-debug19 is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n" + message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n" defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message) end alias breakpoint debugger unless respond_to?(:breakpoint) diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 526b8378a5..ad3f9ebec9 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -1,4 +1,5 @@ require 'rbconfig' + module Kernel # Sets $VERBOSE to nil for the duration of the block and back to its original value afterwards. # @@ -49,10 +50,10 @@ module Kernel # # suppress(ZeroDivisionError) do # 1/0 - # puts "This code is NOT reached" + # puts 'This code is NOT reached' # end # - # puts "This code gets executed and nothing related to ZeroDivisionError was seen" + # puts 'This code gets executed and nothing related to ZeroDivisionError was seen' def suppress(*exception_classes) begin yield rescue Exception => e @@ -62,7 +63,7 @@ module Kernel # Captures the given stream and returns it: # - # stream = capture(:stdout) { puts "Cool" } + # stream = capture(:stdout) { puts 'Cool' } # stream # => "Cool\n" # def capture(stream) diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb index a51818d2b2..16fce81445 100644 --- a/activesupport/lib/active_support/core_ext/logger.rb +++ b/activesupport/lib/active_support/core_ext/logger.rb @@ -56,8 +56,8 @@ class Logger alias :old_datetime_format= :datetime_format= # Logging date-time format (string passed to +strftime+). Ignored if the formatter # does not respond to datetime_format=. - def datetime_format=(datetime_format) - formatter.datetime_format = datetime_format if formatter.respond_to?(:datetime_format=) + def datetime_format=(format) + formatter.datetime_format = format if formatter.respond_to?(:datetime_format=) end alias :old_datetime_format :datetime_format diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb index ce481f0e84..382156ecd8 100644 --- a/activesupport/lib/active_support/core_ext/module/aliasing.rb +++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb @@ -26,18 +26,19 @@ class Module aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 yield(aliased_target, punctuation) if block_given? - with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}" + with_method = "#{aliased_target}_with_#{feature}#{punctuation}" + without_method = "#{aliased_target}_without_#{feature}#{punctuation}" alias_method without_method, target alias_method target, with_method case - when public_method_defined?(without_method) - public target - when protected_method_defined?(without_method) - protected target - when private_method_defined?(without_method) - private target + when public_method_defined?(without_method) + public target + when protected_method_defined?(without_method) + protected target + when private_method_defined?(without_method) + private target end end diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb index 00db75bfec..db07d549b0 100644 --- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb +++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb @@ -15,7 +15,6 @@ class Module attr_internal_reader(*attrs) attr_internal_writer(*attrs) end - alias_method :attr_internal, :attr_internal_accessor class << self; attr_accessor :attr_internal_naming_format end diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 84acb629ad..f914425827 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -4,7 +4,7 @@ class Module def mattr_reader(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name") unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@#{sym} = nil unless defined? @@#{sym} @@ -26,7 +26,7 @@ class Module def mattr_writer(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name") unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) def self.#{sym}=(obj) @@#{sym} = obj diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index ee8adae1cb..fbef27c76a 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -8,11 +8,11 @@ class Module # # class Greeter < ActiveRecord::Base # def hello - # "hello" + # 'hello' # end # # def goodbye - # "goodbye" + # 'goodbye' # end # end # @@ -62,7 +62,7 @@ class Module # delegate :name, :address, :to => :client, :prefix => true # end # - # john_doe = Person.new("John Doe", "Vimmersvej 13") + # john_doe = Person.new('John Doe', 'Vimmersvej 13') # invoice = Invoice.new(john_doe) # invoice.client_name # => "John Doe" # invoice.client_address # => "Vimmersvej 13" @@ -74,8 +74,8 @@ class Module # end # # invoice = Invoice.new(john_doe) - # invoice.customer_name # => "John Doe" - # invoice.customer_address # => "Vimmersvej 13" + # invoice.customer_name # => 'John Doe' + # invoice.customer_address # => 'Vimmersvej 13' # # If the delegate object is +nil+ an exception is raised, and that happens # no matter whether +nil+ responds to the delegated method. You can get a @@ -104,17 +104,17 @@ class Module def delegate(*methods) options = methods.pop unless options.is_a?(Hash) && to = options[:to] - raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)." + raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter).' end to = to.to_s prefix, allow_nil = options.values_at(:prefix, :allow_nil) if prefix == true && to =~ /^[^a-z_]/ - raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." + raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.' end - method_prefix = + method_prefix = \ if prefix "#{prefix == true ? to : prefix}_" else @@ -129,7 +129,7 @@ class Module # Attribute writer methods only accept one argument. Makes sure []= # methods still accept two arguments. - definition = (method =~ /[^\]]=$/) ? "arg" : "*args, &block" + definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block' if allow_nil module_eval(<<-EOS, file, line - 2) diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index 743db47bac..3c8e811fa4 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -5,10 +5,11 @@ class Module # # M::N.parent_name # => "M" def parent_name - unless defined? @parent_name + if defined? @parent_name + @parent_name + else @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil end - @parent_name end # Returns the module which contains this one according to its name. @@ -73,7 +74,7 @@ class Module # This method is useful for forward compatibility, since Ruby 1.8 returns # constant names as strings, whereas 1.9 returns them as symbols. def local_constant_names - ActiveSupport::Deprecation.warn('Module#local_constant_names is deprecated, use Module#local_constants instead', caller) + ActiveSupport::Deprecation.warn 'Module#local_constant_names is deprecated, use Module#local_constants instead', caller local_constants.map { |c| c.to_s } end end diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb index 8adf050b6b..65525013db 100644 --- a/activesupport/lib/active_support/core_ext/module/qualified_const.rb +++ b/activesupport/lib/active_support/core_ext/module/qualified_const.rb @@ -5,7 +5,7 @@ require 'active_support/core_ext/string/inflections' #++ module QualifiedConstUtils def self.raise_if_absolute(path) - raise NameError, "wrong constant name #$&" if path =~ /\A::[^:]+/ + raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/ end def self.names(path) @@ -20,7 +20,7 @@ end #-- # Qualified names are required to be relative because we are extending existing # methods that expect constant names, ie, relative paths of length 1. For example, -# Object.const_get("::String") raises NameError and so does qualified_const_get. +# Object.const_get('::String') raises NameError and so does qualified_const_get. #++ class Module def qualified_const_defined?(path, search_parents=true) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index d67711f3b8..e238fef5a2 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -2,7 +2,7 @@ class Object # An object is blank if it's false, empty, or a whitespace string. - # For example, "", " ", +nil+, [], and {} are all blank. + # For example, '', ' ', +nil+, [], and {} are all blank. # # This simplifies: # @@ -90,10 +90,10 @@ end class String # A string is blank if it's empty or contains whitespaces only: # - # "".blank? # => true - # " ".blank? # => true - # " ".blank? # => true - # " something here ".blank? # => false + # ''.blank? # => true + # ' '.blank? # => true + # ' '.blank? # => true + # ' something here '.blank? # => false # def blank? self !~ /[^[:space:]]/ diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb index f611cdd606..3fec465ec0 100644 --- a/activesupport/lib/active_support/core_ext/object/inclusion.rb +++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb @@ -2,11 +2,11 @@ class Object # Returns true if this object is included in the argument(s). Argument must be # any object which responds to +#include?+ or optionally, multiple arguments can be passed in. Usage: # - # characters = ["Konata", "Kagami", "Tsukasa"] - # "Konata".in?(characters) # => true - # - # character = "Konata" - # character.in?("Konata", "Kagami", "Tsukasa") # => true + # characters = ['Konata', 'Kagami', 'Tsukasa'] + # 'Konata'.in?(characters) # => true + # + # character = 'Konata' + # character.in?('Konata', 'Kagami', 'Tsukasa') # => true # # This will throw an ArgumentError if a single argument is passed in and it doesn't respond # to +#include?+. @@ -18,7 +18,7 @@ class Object if another_object.respond_to? :include? another_object.include? self else - raise ArgumentError.new("The single parameter passed to #in? must respond to #include?") + raise ArgumentError.new 'The single parameter passed to #in? must respond to #include?' end end end diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index 1397142c04..e058367111 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -29,7 +29,7 @@ class Object # # It can also be used with an explicit receiver: # - # I18n.with_options :locale => user.locale, :scope => "newsletter" do |i18n| + # I18n.with_options :locale => user.locale, :scope => 'newsletter' do |i18n| # subject i18n.t :subject # body i18n.t :body, :user_name => user.name # end diff --git a/activesupport/lib/active_support/core_ext/range/include_range.rb b/activesupport/lib/active_support/core_ext/range/include_range.rb index 684b7cbc4a..3af66aaf2f 100644 --- a/activesupport/lib/active_support/core_ext/range/include_range.rb +++ b/activesupport/lib/active_support/core_ext/range/include_range.rb @@ -5,7 +5,7 @@ class Range # (1..5).include?(2..6) # => false # # The native Range#include? behavior is untouched. - # ("a".."f").include?("c") # => true + # ('a'..'f').include?('c') # => true # (5..9).include?(11) # => false def include_with_range?(value) if value.is_a?(::Range) diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index 9b5266c58c..23aaee9c43 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -1,4 +1,4 @@ -require "active_support/multibyte" +require 'active_support/multibyte' class String def at(position) diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index 541f969faa..9084bbee32 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -4,21 +4,33 @@ require 'active_support/core_ext/time/calculations' class String # Form can be either :utc (default) or :local. def to_time(form = :utc) - return nil if self.blank? - d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset).map { |arg| arg || 0 } - d[6] *= 1000000 - ::Time.send("#{form}_time", *d[0..6]) - d[7] + unless blank? + date_values = ::Date._parse(self, false). + values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset). + map! { |arg| arg || 0 } + date_values[6] *= 1000000 + offset = date_values.pop + + ::Time.send("#{form}_time", *date_values) - offset + end end def to_date - return nil if self.blank? - ::Date.new(*::Date._parse(self, false).values_at(:year, :mon, :mday)) + unless blank? + date_values = ::Date._parse(self, false).values_at(:year, :mon, :mday) + + ::Date.new(*date_values) + end end def to_datetime - return nil if self.blank? - d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction).map { |arg| arg || 0 } - d[5] += d.pop - ::DateTime.civil(*d) + unless blank? + date_values = ::Date._parse(self, false). + values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction). + map! { |arg| arg || 0 } + date_values[5] += date_values.pop + + ::DateTime.civil(*date_values) + end end end diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index 1a34e88a87..32a37296d5 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -22,26 +22,33 @@ class String # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>: # - # "Once upon a time in a world far far away".truncate(27) + # 'Once upon a time in a world far far away'.truncate(27) # # => "Once upon a time in a wo..." # - # Pass a <tt>:separator</tt> to truncate +text+ at a natural break: + # Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural break: # - # "Once upon a time in a world far far away".truncate(27, :separator => ' ') + # 'Once upon a time in a world far far away'.truncate(27, :separator => ' ') + # # => "Once upon a time in a..." + # + # 'Once upon a time in a world far far away'.truncate(27, :separator => /\s/) # # => "Once upon a time in a..." # # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...") # for a total length not exceeding <tt>:length</tt>: # - # "And they found that many people were sleeping better.".truncate(25, :omission => "... (continued)") + # 'And they found that many people were sleeping better.'.truncate(25, :omission => '... (continued)') # # => "And they f... (continued)" - def truncate(length, options = {}) - return self.dup unless self.length > length + def truncate(truncate_at, options = {}) + return dup unless length > truncate_at - options[:omission] ||= "..." - length_with_room_for_omission = length - options[:omission].length - stop = options[:separator] ? - (rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission) : length_with_room_for_omission + options[:omission] ||= '...' + length_with_room_for_omission = truncate_at - options[:omission].length + stop = \ + if options[:separator] + rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission + else + length_with_room_for_omission + end self[0...stop] + options[:omission] end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 2194dafe4d..049ffe7986 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -4,7 +4,7 @@ require 'active_support/inflector/transliterate' # String inflections define new methods on the String class to transform names for different purposes. # For instance, you can figure out the name of a table from the name of a class. # -# "ScaleScore".tableize # => "scale_scores" +# 'ScaleScore'.tableize # => "scale_scores" # class String # Returns the plural form of the word in the string. @@ -14,14 +14,14 @@ class String # For any other value of +count+ the plural will be returned. # # ==== Examples - # "post".pluralize # => "posts" - # "octopus".pluralize # => "octopi" - # "sheep".pluralize # => "sheep" - # "words".pluralize # => "words" - # "the blue mailman".pluralize # => "the blue mailmen" - # "CamelOctopus".pluralize # => "CamelOctopi" - # "apple".pluralize(1) # => "apple" - # "apple".pluralize(2) # => "apples" + # 'post'.pluralize # => "posts" + # 'octopus'.pluralize # => "octopi" + # 'sheep'.pluralize # => "sheep" + # 'words'.pluralize # => "words" + # 'the blue mailman'.pluralize # => "the blue mailmen" + # 'CamelOctopus'.pluralize # => "CamelOctopi" + # 'apple'.pluralize(1) # => "apple" + # 'apple'.pluralize(2) # => "apples" def pluralize(count = nil) if count == 1 self @@ -32,12 +32,12 @@ class String # The reverse of +pluralize+, returns the singular form of a word in a string. # - # "posts".singularize # => "post" - # "octopi".singularize # => "octopus" - # "sheep".singularize # => "sheep" - # "word".singularize # => "word" - # "the blue mailmen".singularize # => "the blue mailman" - # "CamelOctopi".singularize # => "CamelOctopus" + # 'posts'.singularize # => "post" + # 'octopi'.singularize # => "octopus" + # 'sheep'.singularize # => "sheep" + # 'word'.singularize # => "word" + # 'the blue mailmen'.singularize # => "the blue mailman" + # 'CamelOctopi'.singularize # => "CamelOctopus" def singularize ActiveSupport::Inflector.singularize(self) end @@ -47,9 +47,9 @@ class String # or is not initialized. See ActiveSupport::Inflector.constantize # # Examples - # "Module".constantize # => Module - # "Class".constantize # => Class - # "blargle".constantize # => NameError: wrong constant name blargle + # 'Module'.constantize # => Module + # 'Class'.constantize # => Class + # 'blargle'.constantize # => NameError: wrong constant name blargle def constantize ActiveSupport::Inflector.constantize(self) end @@ -59,9 +59,9 @@ class String # or is not initialized. See ActiveSupport::Inflector.safe_constantize # # Examples - # "Module".safe_constantize # => Module - # "Class".safe_constantize # => Class - # "blargle".safe_constantize # => nil + # 'Module'.safe_constantize # => Module + # 'Class'.safe_constantize # => Class + # 'blargle'.safe_constantize # => nil def safe_constantize ActiveSupport::Inflector.safe_constantize(self) end @@ -71,14 +71,16 @@ class String # # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces. # - # "active_record".camelize # => "ActiveRecord" - # "active_record".camelize(:lower) # => "activeRecord" - # "active_record/errors".camelize # => "ActiveRecord::Errors" - # "active_record/errors".camelize(:lower) # => "activeRecord::Errors" + # 'active_record'.camelize # => "ActiveRecord" + # 'active_record'.camelize(:lower) # => "activeRecord" + # 'active_record/errors'.camelize # => "ActiveRecord::Errors" + # 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors" def camelize(first_letter = :upper) case first_letter - when :upper then ActiveSupport::Inflector.camelize(self, true) - when :lower then ActiveSupport::Inflector.camelize(self, false) + when :upper + ActiveSupport::Inflector.camelize(self, true) + when :lower + ActiveSupport::Inflector.camelize(self, false) end end alias_method :camelcase, :camelize @@ -89,8 +91,8 @@ class String # # +titleize+ is also aliased as +titlecase+. # - # "man from the boondocks".titleize # => "Man From The Boondocks" - # "x-men: the last stand".titleize # => "X Men: The Last Stand" + # 'man from the boondocks'.titleize # => "Man From The Boondocks" + # 'x-men: the last stand'.titleize # => "X Men: The Last Stand" def titleize ActiveSupport::Inflector.titleize(self) end @@ -100,23 +102,23 @@ class String # # +underscore+ will also change '::' to '/' to convert namespaces to paths. # - # "ActiveModel".underscore # => "active_model" - # "ActiveModel::Errors".underscore # => "active_model/errors" + # 'ActiveModel'.underscore # => "active_model" + # 'ActiveModel::Errors'.underscore # => "active_model/errors" def underscore ActiveSupport::Inflector.underscore(self) end # Replaces underscores with dashes in the string. # - # "puni_puni" # => "puni-puni" + # 'puni_puni' # => "puni-puni" def dasherize ActiveSupport::Inflector.dasherize(self) end # Removes the module part from the constant expression in the string. # - # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections" - # "Inflections".demodulize # => "Inflections" + # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" + # 'Inflections'.demodulize # => "Inflections" # # See also +deconstantize+. def demodulize @@ -125,11 +127,11 @@ class String # Removes the rightmost segment from the constant expression in the string. # - # "Net::HTTP".deconstantize # => "Net" - # "::Net::HTTP".deconstantize # => "::Net" - # "String".deconstantize # => "" - # "::String".deconstantize # => "" - # "".deconstantize # => "" + # 'Net::HTTP'.deconstantize # => "Net" + # '::Net::HTTP'.deconstantize # => "::Net" + # 'String'.deconstantize # => "" + # '::String'.deconstantize # => "" + # ''.deconstantize # => "" # # See also +demodulize+. def deconstantize @@ -158,9 +160,9 @@ class String # Creates the name of a table like Rails does for models to table names. This method # uses the +pluralize+ method on the last word in the string. # - # "RawScaledScorer".tableize # => "raw_scaled_scorers" - # "egg_and_ham".tableize # => "egg_and_hams" - # "fancyCategory".tableize # => "fancy_categories" + # 'RawScaledScorer'.tableize # => "raw_scaled_scorers" + # 'egg_and_ham'.tableize # => "egg_and_hams" + # 'fancyCategory'.tableize # => "fancy_categories" def tableize ActiveSupport::Inflector.tableize(self) end @@ -169,12 +171,12 @@ class String # Note that this returns a string and not a class. (To convert to an actual class # follow +classify+ with +constantize+.) # - # "egg_and_hams".classify # => "EggAndHam" - # "posts".classify # => "Post" + # 'egg_and_hams'.classify # => "EggAndHam" + # 'posts'.classify # => "Post" # # Singular names are not handled correctly. # - # "business".classify # => "Busines" + # 'business'.classify # => "Busines" def classify ActiveSupport::Inflector.classify(self) end @@ -182,8 +184,8 @@ class String # Capitalizes the first word, turns underscores into spaces, and strips '_id'. # Like +titleize+, this is meant for creating pretty output. # - # "employee_salary" # => "Employee salary" - # "author_id" # => "Author" + # 'employee_salary' # => "Employee salary" + # 'author_id' # => "Author" def humanize ActiveSupport::Inflector.humanize(self) end @@ -193,9 +195,9 @@ class String # the method should put '_' between the name and 'id'. # # Examples - # "Message".foreign_key # => "message_id" - # "Message".foreign_key(false) # => "messageid" - # "Admin::Post".foreign_key # => "post_id" + # 'Message'.foreign_key # => "message_id" + # 'Message'.foreign_key(false) # => "messageid" + # 'Admin::Post'.foreign_key # => "post_id" def foreign_key(separate_class_name_and_id_with_underscore = true) ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore) end diff --git a/activesupport/lib/active_support/core_ext/string/inquiry.rb b/activesupport/lib/active_support/core_ext/string/inquiry.rb index 5f0a017de6..2562a7cef6 100644 --- a/activesupport/lib/active_support/core_ext/string/inquiry.rb +++ b/activesupport/lib/active_support/core_ext/string/inquiry.rb @@ -4,7 +4,7 @@ class String # Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class, # which gives you a prettier way to test for equality. Example: # - # env = "production".inquiry + # env = 'production'.inquiry # env.production? # => true # env.development? # => false def inquiry diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 4903687b73..215ba87ca9 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -15,7 +15,7 @@ class ERB # <%=h @person.name %> # # ==== Example: - # puts html_escape("is a > 0 & a < 10?") + # puts html_escape('is a > 0 & a < 10?') # # => is a > 0 & a < 10? def html_escape(s) s = s.to_s @@ -38,10 +38,10 @@ class ERB # A utility method for escaping HTML without affecting existing escaped entities. # # ==== Examples - # html_escape_once("1 < 2 & 3") + # html_escape_once('1 < 2 & 3') # # => "1 < 2 & 3" # - # html_escape_once("<< Accept & Checkout") + # html_escape_once('<< Accept & Checkout') # # => "<< Accept & Checkout" def html_escape_once(s) result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP) { |special| HTML_ESCAPE[special] } @@ -53,7 +53,7 @@ class ERB # A utility method for escaping HTML entities in JSON strings # using \uXXXX JavaScript escape sequences for string literals: # - # json_escape("is a > 0 & a < 10?") + # json_escape('is a > 0 & a < 10?') # # => is a \u003E 0 \u0026 a \u003C 10? # # Note that after this operation is performed the output is not @@ -92,26 +92,31 @@ end module ActiveSupport #:nodoc: class SafeBuffer < String - UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase", "prepend"].freeze + UNSAFE_STRING_METHODS = %w( + capitalize chomp chop delete downcase gsub lstrip next reverse rstrip + slice squeeze strip sub succ swapcase tr tr_s upcase prepend + ) alias_method :original_concat, :concat private :original_concat class SafeConcatError < StandardError def initialize - super "Could not concatenate to the buffer because it is not html safe." + super 'Could not concatenate to the buffer because it is not html safe.' end end def [](*args) - return super if args.size < 2 - - if html_safe? - new_safe_buffer = super - new_safe_buffer.instance_eval { @html_safe = true } - new_safe_buffer + if args.size < 2 + super else - to_str[*args] + if html_safe? + new_safe_buffer = super + new_safe_buffer.instance_eval { @html_safe = true } + new_safe_buffer + else + to_str[*args] + end end end diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 5076697c04..1434e186c3 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -1,10 +1,17 @@ require 'active_support/duration' -require 'active_support/core_ext/time/zones' require 'active_support/core_ext/time/conversions' class Time COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 } + DAYS_INTO_WEEK = { + :monday => 0, + :tuesday => 1, + :wednesday => 2, + :thursday => 3, + :friday => 4, + :saturday => 5, + :sunday => 6 + } class << self # Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances @@ -15,8 +22,11 @@ class Time # Return the number of days in the given month. # If no year is specified, it will use the current year. def days_in_month(month, year = now.year) - return 29 if month == 2 && ::Date.gregorian_leap?(year) - COMMON_YEAR_DAYS_IN_MONTH[month] + if month == 2 && ::Date.gregorian_leap?(year) + 29 + else + COMMON_YEAR_DAYS_IN_MONTH[month] + end end # Returns a new Time if requested year can be accommodated by Ruby's Time class @@ -24,8 +34,13 @@ class Time # otherwise returns a DateTime. def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0) time = ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec) + # This check is needed because Time.utc(y) returns a time object in the 2000s for 0 <= y <= 138. - time.year == year ? time : ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec) + if time.year == year + time + else + ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec) + end rescue ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec) end @@ -72,13 +87,13 @@ class Time def change(options) ::Time.send( utc? ? :utc_time : :local_time, - options[:year] || year, - options[:month] || month, - options[:day] || day, - options[:hour] || hour, - options[:min] || (options[:hour] ? 0 : min), - options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec), - options[:usec] || ((options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + options.fetch(:year, year), + options.fetch(:month, month), + options.fetch(:day, day), + options.fetch(:hour, hour), + options.fetch(:min, options[:hour] ? 0 : min), + options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec), + options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) ) end @@ -89,18 +104,26 @@ class Time def advance(options) unless options[:weeks].nil? options[:weeks], partial_weeks = options[:weeks].divmod(1) - options[:days] = (options[:days] || 0) + 7 * partial_weeks + options[:days] = options.fetch(:days, 0) + 7 * partial_weeks end unless options[:days].nil? options[:days], partial_days = options[:days].divmod(1) - options[:hours] = (options[:hours] || 0) + 24 * partial_days + options[:hours] = options.fetch(:hours, 0) + 24 * partial_days end d = to_date.advance(options) time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) - seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600 - seconds_to_advance == 0 ? time_advanced_by_date : time_advanced_by_date.since(seconds_to_advance) + seconds_to_advance = \ + options.fetch(:seconds, 0) + + options.fetch(:minutes, 0) * 60 + + options.fetch(:hours, 0) * 3600 + + if seconds_to_advance.zero? + time_advanced_by_date + else + time_advanced_by_date.since(seconds_to_advance) + end end # Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension @@ -168,6 +191,7 @@ class Time start_day_number = DAYS_INTO_WEEK[start_day] current_day_number = wday != 0 ? wday - 1 : 6 days_span = current_day_number - start_day_number + days_span >= 0 ? days_span : 7 + days_span end @@ -199,13 +223,19 @@ class Time # Returns a new Time representing the start of the given day in the previous week (default is :monday). def prev_week(day = :monday) - ago(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0) + ago(1.week). + beginning_of_week. + since(DAYS_INTO_WEEK[day].day). + change(:hour => 0) end alias_method :last_week, :prev_week # Returns a new Time representing the start of the given day in next week (default is :monday). def next_week(day = :monday) - since(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0) + since(1.week). + beginning_of_week. + since(DAYS_INTO_WEEK[day].day). + change(:hour => 0) end # Returns a new Time representing the start of the day (0:00) @@ -219,7 +249,12 @@ class Time # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9) def end_of_day - change(:hour => 23, :min => 59, :sec => 59, :usec => 999999.999) + change( + :hour => 23, + :min => 59, + :sec => 59, + :usec => 999999.999 + ) end # Returns a new Time representing the start of the month (1st of the month, 0:00) @@ -233,19 +268,27 @@ class Time def end_of_month #self - ((self.mday-1).days + self.seconds_since_midnight) last_day = ::Time.days_in_month(month, year) - change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999) + change( + :day => last_day, + :hour => 23, + :min => 59, + :sec => 59, + :usec => 999999.999 + ) end alias :at_end_of_month :end_of_month # Returns a new Time representing the start of the quarter (1st of january, april, july, october, 0:00) def beginning_of_quarter - beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= month }) + first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month } + beginning_of_month.change(:month => first_quarter_month) end alias :at_beginning_of_quarter :beginning_of_quarter # Returns a new Time representing the end of the quarter (end of the last day of march, june, september, december) def end_of_quarter - beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= month }).end_of_month + last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month } + beginning_of_month.change(:month => last_quarter_month).end_of_month end alias :at_end_of_quarter :end_of_quarter @@ -257,7 +300,14 @@ class Time # Returns a new Time representing the end of the year (end of the 31st of december) def end_of_year - change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999) + change( + :month => 12, + :day => 31, + :hour => 23, + :min => 59, + :sec => 59, + :usec => 999999.999 + ) end alias :at_end_of_year :end_of_year @@ -330,7 +380,11 @@ class Time # can be chronologically compared with a Time def compare_with_coercion(other) # we're avoiding Time#to_datetime cause it's expensive - other.is_a?(Time) ? compare_without_coercion(other.to_time) : to_datetime <=> other + if other.is_a?(Time) + compare_without_coercion(other.to_time) + else + to_datetime <=> other + end end alias_method :compare_without_coercion, :<=> alias_method :<=>, :compare_with_coercion @@ -344,4 +398,5 @@ class Time end alias_method :eql_without_coercion, :eql? alias_method :eql?, :eql_with_coercion + end diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index 0fdcd383f0..4f852fd780 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -3,13 +3,19 @@ require 'active_support/values/time_zone' class Time DATE_FORMATS = { - :db => "%Y-%m-%d %H:%M:%S", - :number => "%Y%m%d%H%M%S", - :time => "%H:%M", - :short => "%d %b %H:%M", - :long => "%B %d, %Y %H:%M", - :long_ordinal => lambda { |time| time.strftime("%B #{ActiveSupport::Inflector.ordinalize(time.day)}, %Y %H:%M") }, - :rfc822 => lambda { |time| time.strftime("%a, %d %b %Y %H:%M:%S #{time.formatted_offset(false)}") } + :db => '%Y-%m-%d %H:%M:%S', + :number => '%Y%m%d%H%M%S', + :time => '%H:%M', + :short => '%d %b %H:%M', + :long => '%B %d, %Y %H:%M', + :long_ordinal => lambda { |time| + day_format = ActiveSupport::Inflector.ordinalize(time.day) + time.strftime("%B #{day_format}, %Y %H:%M") + }, + :rfc822 => lambda { |time| + offset_format = time.formatted_offset(false) + time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}") + } } # Converts to a formatted string. See DATE_FORMATS for builtin formats. @@ -34,7 +40,7 @@ class Time # or Proc instance that takes a time argument as the value. # # # config/initializers/time_formats.rb - # Time::DATE_FORMATS[:month_and_year] = "%B %Y" + # Time::DATE_FORMATS[:month_and_year] = '%B %Y' # Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") } def to_formatted_s(format = :default) if formatter = DATE_FORMATS[format] diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 0c5962858e..e48866abe3 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/time/calculations' require 'active_support/time_with_zone' class Time @@ -50,13 +51,21 @@ class Time # Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones. def find_zone!(time_zone) - return time_zone if time_zone.nil? || time_zone.is_a?(ActiveSupport::TimeZone) - # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone) - unless time_zone.respond_to?(:period_for_local) - time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone) + if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone) + time_zone + else + # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone) + unless time_zone.respond_to?(:period_for_local) + time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone) + end + + # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone + if time_zone.is_a?(ActiveSupport::TimeZone) + time_zone + else + ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone) + end end - # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone - time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone) rescue TZInfo::InvalidTimezoneIdentifier raise ArgumentError, "Invalid Timezone: #{time_zone}" end @@ -79,8 +88,10 @@ class Time # # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 def in_time_zone(zone = ::Time.zone) - return self unless zone - - ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone)) + if zone + ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone)) + else + self + end end end diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 94f8d7133e..73b6a42505 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -13,6 +13,13 @@ module ActiveSupport # Sets the behavior to the specified value. Can be a single value or an array. # + # Available behaviors: + # + # [+:stderr+] Print deprecations to +$stderror+ + # [+:log+] Send to +Rails.logger+ + # [+:notify+] Instrument using +ActiveSupport::Notifications+ + # [+:silence+] Do nothing + # # Examples # # ActiveSupport::Deprecation.behavior = :stderr @@ -41,8 +48,9 @@ module ActiveSupport }, :notify => Proc.new { |message, callstack| ActiveSupport::Notifications.instrument("deprecation.rails", - :message => message, :callstack => callstack) - } + :message => message, :callstack => callstack) + }, + :silence => Proc.new { |message, callstack| } } end end diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index b3eb1333ca..7eb61cd1a0 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -16,8 +16,8 @@ module ActiveSupport inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies') inflect.plural(/(x|ch|ss|sh)$/i, '\1es') inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices') - inflect.plural(/(m|l)ouse$/i, '\1ice') - inflect.plural(/(m|l)ice$/i, '\1ice') + inflect.plural(/^(m|l)ouse$/i, '\1ice') + inflect.plural(/^(m|l)ice$/i, '\1ice') inflect.plural(/^(ox)$/i, '\1en') inflect.plural(/^(oxen)$/i, '\1') inflect.plural(/(quiz)$/i, '\1zes') @@ -36,7 +36,7 @@ module ActiveSupport inflect.singular(/(s)eries$/i, '\1eries') inflect.singular(/(m)ovies$/i, '\1ovie') inflect.singular(/(x|ch|ss|sh)es$/i, '\1') - inflect.singular(/(m|l)ice$/i, '\1ouse') + inflect.singular(/^(m|l)ice$/i, '\1ouse') inflect.singular(/(bus)(es)?$/i, '\1') inflect.singular(/(o)es$/i, '\1') inflect.singular(/(shoe)s$/i, '\1') @@ -58,6 +58,6 @@ module ActiveSupport inflect.irregular('cow', 'kine') inflect.irregular('zombie', 'zombies') - inflect.uncountable(%w(equipment information rice money species series fish sheep jeans)) + inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police)) end end diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index cbeb6c0a28..986a764479 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -9,7 +9,7 @@ module ActiveSupport module JSON class << self def decode(json, options ={}) - data = MultiJson.decode(json, options) + data = MultiJson.load(json, options) if ActiveSupport.parse_json_times convert_dates_from(data) else @@ -18,12 +18,12 @@ module ActiveSupport end def engine - MultiJson.engine + MultiJson.adapter end alias :backend :engine def engine=(name) - MultiJson.engine = name + MultiJson.use(name) end alias :backend= :engine= diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 8cf7bdafda..6735c561d3 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -4,7 +4,7 @@ require 'active_support/notifications/fanout' module ActiveSupport # = Notifications # - # +ActiveSupport::Notifications+ provides an instrumentation API for Ruby. + # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for Ruby. # # == Instrumenters # @@ -44,26 +44,53 @@ module ActiveSupport # event.duration # => 10 (in milliseconds) # event.payload # => { :extra => :information } # - # The block in the +subscribe+ call gets the name of the event, start + # The block in the <tt>subscribe</tt> call gets the name of the event, start # timestamp, end timestamp, a string with a unique identifier for that event # (something like "535801666f04d0298cd6"), and a hash with the payload, in # that order. # # If an exception happens during that particular instrumentation the payload will - # have a key +:exception+ with an array of two elements as value: a string with + # have a key <tt>:exception</tt> with an array of two elements as value: a string with # the name of the exception class, and the exception message. # - # As the previous example depicts, the class +ActiveSupport::Notifications::Event+ + # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt> # is able to take the arguments as they come and provide an object-oriented # interface to that data. # + # It is also possible to pass an object as the second parameter passed to the + # <tt>subscribe</tt> method instead of a block: + # + # module ActionController + # class PageRequest + # def call(name, started, finished, unique_id, payload) + # Rails.logger.debug ["notification:", name, started, finished, unique_id, payload].join(" ") + # end + # end + # end + # + # ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new) + # + # resulting in the following output within the logs including a hash with the payload: + # + # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 { + # :controller=>"Devise::SessionsController", + # :action=>"new", + # :params=>{"action"=>"new", "controller"=>"devise/sessions"}, + # :format=>:html, + # :method=>"GET", + # :path=>"/login/sign_in", + # :status=>200, + # :view_runtime=>279.3080806732178, + # :db_runtime=>40.053 + # } + # # You can also subscribe to all events whose name matches a certain regexp: # # ActiveSupport::Notifications.subscribe(/render/) do |*args| # ... # end # - # and even pass no argument to +subscribe+, in which case you are subscribing + # and even pass no argument to <tt>subscribe</tt>, in which case you are subscribing # to all events. # # == Temporary Subscriptions diff --git a/activesupport/lib/active_support/testing/performance/jruby.rb b/activesupport/lib/active_support/testing/performance/jruby.rb index b347539f13..34e3f9f45f 100644 --- a/activesupport/lib/active_support/testing/performance/jruby.rb +++ b/activesupport/lib/active_support/testing/performance/jruby.rb @@ -42,7 +42,7 @@ module ActiveSupport klasses.each do |klass| fname = output_filename(klass) FileUtils.mkdir_p(File.dirname(fname)) - file = File.open(fname, 'wb') do |file| + File.open(fname, 'wb') do |file| klass.new(@data).printProfile(file) end end diff --git a/activesupport/lib/active_support/time/autoload.rb b/activesupport/lib/active_support/time/autoload.rb deleted file mode 100644 index c9a7731b39..0000000000 --- a/activesupport/lib/active_support/time/autoload.rb +++ /dev/null @@ -1,5 +0,0 @@ -module ActiveSupport - autoload :Duration, 'active_support/duration' - autoload :TimeWithZone, 'active_support/time_with_zone' - autoload :TimeZone, 'active_support/values/time_zone' -end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 6c2828b74e..9010a4a716 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -279,6 +279,12 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => ' ') end + def test_truncate_with_omission_and_regexp_seperator + assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => /\s/) + assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => /\s/) + assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => /\s/) + end + def test_truncate_multibyte assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'), "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10) diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index e821a285d7..e21f3efe36 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -93,6 +93,26 @@ class DeprecationTest < ActiveSupport::TestCase assert_match(/foo=nil/, @b) end + def test_default_stderr_behavior + ActiveSupport::Deprecation.behavior = :stderr + behavior = ActiveSupport::Deprecation.behavior.first + + content = capture(:stderr) { + assert_nil behavior.call('Some error!', ['call stack!']) + } + assert_match(/Some error!/, content) + assert_match(/call stack!/, content) + end + + def test_default_silence_behavior + ActiveSupport::Deprecation.behavior = :silence + behavior = ActiveSupport::Deprecation.behavior.first + + assert_blank capture(:stderr) { + assert_nil behavior.call('Some error!', ['call stack!']) + } + end + def test_deprecated_instance_variable_proxy assert_not_deprecated { @dtc.request.size } diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 86c5e1f135..4d10cfca25 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -109,7 +109,9 @@ module InflectorTestCases # regression tests against improper inflection regexes "|ice" => "|ices", - "|ouse" => "|ouses" + "|ouse" => "|ouses", + "slice" => "slices", + "police" => "police" } CamelToUnderscore = { diff --git a/guides/assets/images/favicon.ico b/guides/assets/images/favicon.ico Binary files differnew file mode 100644 index 0000000000..e0e80cf8f1 --- /dev/null +++ b/guides/assets/images/favicon.ico diff --git a/guides/assets/images/getting_started/confirm_dialog.png b/guides/assets/images/getting_started/confirm_dialog.png Binary files differnew file mode 100644 index 0000000000..a26c09ef2d --- /dev/null +++ b/guides/assets/images/getting_started/confirm_dialog.png diff --git a/guides/assets/images/getting_started/form_with_errors.png b/guides/assets/images/getting_started/form_with_errors.png Binary files differnew file mode 100644 index 0000000000..badefe6ea6 --- /dev/null +++ b/guides/assets/images/getting_started/form_with_errors.png diff --git a/guides/assets/images/getting_started/index_action_with_edit_link.png b/guides/assets/images/getting_started/index_action_with_edit_link.png Binary files differnew file mode 100644 index 0000000000..6e58a13756 --- /dev/null +++ b/guides/assets/images/getting_started/index_action_with_edit_link.png diff --git a/guides/assets/images/getting_started/show_action_for_posts.png b/guides/assets/images/getting_started/show_action_for_posts.png Binary files differnew file mode 100644 index 0000000000..5c8c4d8e5e --- /dev/null +++ b/guides/assets/images/getting_started/show_action_for_posts.png diff --git a/guides/assets/images/getting_started/undefined_method_post_path.png b/guides/assets/images/getting_started/undefined_method_post_path.png Binary files differnew file mode 100644 index 0000000000..f568bf315c --- /dev/null +++ b/guides/assets/images/getting_started/undefined_method_post_path.png diff --git a/guides/assets/images/posts_index.png b/guides/assets/images/posts_index.png Binary files differdeleted file mode 100644 index f6cd2f9b80..0000000000 --- a/guides/assets/images/posts_index.png +++ /dev/null diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile index 768985070c..670a8523b0 100644 --- a/guides/code/getting_started/Gemfile +++ b/guides/code/getting_started/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -gem 'rails', '3.2.0' +gem 'rails', '3.2.3' # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' @@ -15,7 +15,7 @@ group :assets do gem 'coffee-rails', '~> 3.2.1' # See https://github.com/sstephenson/execjs#readme for more supported runtimes - # gem 'therubyracer' + # gem 'therubyracer', :platform => :ruby gem 'uglifier', '>= 1.0.3' end @@ -28,11 +28,11 @@ gem 'jquery-rails' # To use Jbuilder templates for JSON # gem 'jbuilder' -# Use unicorn as the web server +# Use unicorn as the app server # gem 'unicorn' # Deploy with Capistrano # gem 'capistrano' # To use debugger -# gem 'ruby-debug19', :require => 'ruby-debug' +# gem 'debugger' diff --git a/guides/code/getting_started/README.rdoc b/guides/code/getting_started/README.rdoc index d2014bd35f..b5d7b6436b 100644 --- a/guides/code/getting_started/README.rdoc +++ b/guides/code/getting_started/README.rdoc @@ -86,8 +86,8 @@ 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! You need to install ruby-debug19 to run the server in debugging -mode. With gems, use <tt>sudo gem install ruby-debug19</tt>. Example: +resume execution! You need to install the 'debugger' gem to run the server in debugging +mode. Add gem 'debugger' to your Gemfile and run <tt>bundle</tt> to install it. Example: class WeblogController < ActionController::Base def index diff --git a/guides/code/getting_started/app/assets/javascripts/comments.js.coffee b/guides/code/getting_started/app/assets/javascripts/comments.js.coffee deleted file mode 100644 index 761567942f..0000000000 --- a/guides/code/getting_started/app/assets/javascripts/comments.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/guides/code/getting_started/app/assets/javascripts/home.js.coffee b/guides/code/getting_started/app/assets/javascripts/home.js.coffee deleted file mode 100644 index 761567942f..0000000000 --- a/guides/code/getting_started/app/assets/javascripts/home.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee b/guides/code/getting_started/app/assets/javascripts/posts.js.coffee deleted file mode 100644 index 761567942f..0000000000 --- a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/guides/code/getting_started/app/assets/stylesheets/comments.css.scss b/guides/code/getting_started/app/assets/stylesheets/comments.css.scss deleted file mode 100644 index e730912783..0000000000 --- a/guides/code/getting_started/app/assets/stylesheets/comments.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the Comments controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/guides/code/getting_started/app/assets/stylesheets/home.css.scss b/guides/code/getting_started/app/assets/stylesheets/home.css.scss deleted file mode 100644 index f0ddc6846a..0000000000 --- a/guides/code/getting_started/app/assets/stylesheets/home.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the home controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss b/guides/code/getting_started/app/assets/stylesheets/posts.css.scss deleted file mode 100644 index ed4dfd10f2..0000000000 --- a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the Posts controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss b/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss deleted file mode 100644 index 05188f08ed..0000000000 --- a/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss +++ /dev/null @@ -1,56 +0,0 @@ -body { - background-color: #fff; - color: #333; - font-family: verdana, arial, helvetica, sans-serif; - font-size: 13px; - line-height: 18px; } - -p, ol, ul, td { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 13px; - line-height: 18px; } - -pre { - background-color: #eee; - padding: 10px; - font-size: 11px; } - -a { - color: #000; - &:visited { - color: #666; } - &:hover { - color: #fff; - background-color: #000; } } - -div { - &.field, &.actions { - margin-bottom: 10px; } } - -#notice { - color: green; } - -.field_with_errors { - padding: 2px; - background-color: red; - display: table; } - -#error_explanation { - width: 450px; - border: 2px solid red; - padding: 7px; - padding-bottom: 0; - margin-bottom: 20px; - background-color: #f0f0f0; - h2 { - text-align: left; - font-weight: bold; - padding: 5px 5px 5px 15px; - font-size: 12px; - margin: -7px; - margin-bottom: 0px; - background-color: #c00; - color: #fff; } - ul li { - font-size: 12px; - list-style: square; } } diff --git a/guides/code/getting_started/app/controllers/home_controller.rb b/guides/code/getting_started/app/controllers/home_controller.rb index 6cc31c1ca3..309b70441e 100644 --- a/guides/code/getting_started/app/controllers/home_controller.rb +++ b/guides/code/getting_started/app/controllers/home_controller.rb @@ -1,4 +1,4 @@ -class HomeController < ApplicationController +class WelcomeController < ApplicationController def index end diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb index 1581d4eb16..85d2c1de47 100644 --- a/guides/code/getting_started/app/controllers/posts_controller.rb +++ b/guides/code/getting_started/app/controllers/posts_controller.rb @@ -1,84 +1,45 @@ class PostsController < ApplicationController - http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index - # GET /posts - # GET /posts.json + def index @posts = Post.all - - respond_to do |format| - format.html # index.html.erb - format.json { render json: @posts } - end end - # GET /posts/1 - # GET /posts/1.json def show @post = Post.find(params[:id]) - - respond_to do |format| - format.html # show.html.erb - format.json { render json: @post } - end end - # GET /posts/new - # GET /posts/new.json def new @post = Post.new - - respond_to do |format| - format.html # new.html.erb - format.json { render json: @post } - end - end - - # GET /posts/1/edit - def edit - @post = Post.find(params[:id]) end - # POST /posts - # POST /posts.json def create @post = Post.new(params[:post]) - respond_to do |format| - if @post.save - format.html { redirect_to @post, notice: 'Post was successfully created.' } - format.json { render json: @post, status: :created, location: @post } - else - format.html { render action: "new" } - format.json { render json: @post.errors, status: :unprocessable_entity } - end + if @post.save + redirect_to :action => :show, :id => @post.id + else + render 'new' end end - # PUT /posts/1 - # PUT /posts/1.json + def edit + @post = Post.find(params[:id]) + end + def update @post = Post.find(params[:id]) - respond_to do |format| - if @post.update_attributes(params[:post]) - format.html { redirect_to @post, notice: 'Post was successfully updated.' } - format.json { head :no_content } - else - format.html { render action: "edit" } - format.json { render json: @post.errors, status: :unprocessable_entity } - end + if @post.update_attributes(params[:post]) + redirect_to :action => :show, :id => @post.id + else + render 'edit' end end - # DELETE /posts/1 - # DELETE /posts/1.json def destroy @post = Post.find(params[:id]) @post.destroy - respond_to do |format| - format.html { redirect_to posts_url } - format.json { head :no_content } - end + redirect_to :action => :index end end diff --git a/guides/code/getting_started/app/helpers/home_helper.rb b/guides/code/getting_started/app/helpers/home_helper.rb deleted file mode 100644 index 23de56ac60..0000000000 --- a/guides/code/getting_started/app/helpers/home_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module HomeHelper -end diff --git a/guides/code/getting_started/app/helpers/posts_helper.rb b/guides/code/getting_started/app/helpers/posts_helper.rb index b6e8e67894..a7b8cec898 100644 --- a/guides/code/getting_started/app/helpers/posts_helper.rb +++ b/guides/code/getting_started/app/helpers/posts_helper.rb @@ -1,5 +1,2 @@ module PostsHelper - def join_tags(post) - post.tags.map { |t| t.name }.join(", ") - end end diff --git a/guides/code/getting_started/app/helpers/welcome_helper.rb b/guides/code/getting_started/app/helpers/welcome_helper.rb new file mode 100644 index 0000000000..eeead45fc9 --- /dev/null +++ b/guides/code/getting_started/app/helpers/welcome_helper.rb @@ -0,0 +1,2 @@ +module WelcomeHelper +end diff --git a/guides/code/getting_started/app/models/post.rb b/guides/code/getting_started/app/models/post.rb index 61c2b5ae44..21387340b0 100644 --- a/guides/code/getting_started/app/models/post.rb +++ b/guides/code/getting_started/app/models/post.rb @@ -1,11 +1,6 @@ class Post < ActiveRecord::Base - validates :name, :presence => true validates :title, :presence => true, :length => { :minimum => 5 } - + has_many :comments, :dependent => :destroy - has_many :tags - - accepts_nested_attributes_for :tags, :allow_destroy => :true, - :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } } end diff --git a/guides/code/getting_started/app/models/tag.rb b/guides/code/getting_started/app/models/tag.rb deleted file mode 100644 index 30992e8ba9..0000000000 --- a/guides/code/getting_started/app/models/tag.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Tag < ActiveRecord::Base - belongs_to :post -end diff --git a/guides/code/getting_started/app/views/home/index.html.erb b/guides/code/getting_started/app/views/home/index.html.erb deleted file mode 100644 index bb4f3dcd1f..0000000000 --- a/guides/code/getting_started/app/views/home/index.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -<h1>Hello, Rails!</h1> -<%= link_to "My Blog", posts_path %> diff --git a/guides/code/getting_started/app/views/posts/_form.html.erb b/guides/code/getting_started/app/views/posts/_form.html.erb index e27da7f413..b35ea2f237 100644 --- a/guides/code/getting_started/app/views/posts/_form.html.erb +++ b/guides/code/getting_started/app/views/posts/_form.html.erb @@ -1,32 +1,25 @@ -<% @post.tags.build %> -<%= form_for(@post) do |post_form| %> +<%= form_for @post do |f| %> <% if @post.errors.any? %> - <div id="errorExplanation"> - <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> - <ul> - <% @post.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> + <div id="errorExplanation"> + <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> + <ul> + <% @post.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> + </div> <% end %> - - <div class="field"> - <%= post_form.label :name %><br /> - <%= post_form.text_field :name %> - </div> - <div class="field"> - <%= post_form.label :title %><br /> - <%= post_form.text_field :title %> - </div> - <div class="field"> - <%= post_form.label :content %><br /> - <%= post_form.text_area :content %> - </div> - <h2>Tags</h2> - <%= render :partial => 'tags/form', - :locals => {:form => post_form} %> - <div class="actions"> - <%= post_form.submit %> - </div> + <p> + <%= f.label :title %><br> + <%= f.text_field :title %> + </p> + + <p> + <%= f.label :text %><br> + <%= f.text_area :text %> + </p> + + <p> + <%= f.submit %> + </p> <% end %> diff --git a/guides/code/getting_started/app/views/posts/edit.html.erb b/guides/code/getting_started/app/views/posts/edit.html.erb index 720580236b..911a48569d 100644 --- a/guides/code/getting_started/app/views/posts/edit.html.erb +++ b/guides/code/getting_started/app/views/posts/edit.html.erb @@ -2,5 +2,4 @@ <%= render 'form' %> -<%= link_to 'Show', @post %> | -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', :action => :index %> diff --git a/guides/code/getting_started/app/views/posts/index.html.erb b/guides/code/getting_started/app/views/posts/index.html.erb index 45dee1b25f..7b72720d50 100644 --- a/guides/code/getting_started/app/views/posts/index.html.erb +++ b/guides/code/getting_started/app/views/posts/index.html.erb @@ -1,10 +1,11 @@ <h1>Listing posts</h1> +<%= link_to 'New post', :action => :new %> + <table> <tr> - <th>Name</th> <th>Title</th> - <th>Content</th> + <th>Text</th> <th></th> <th></th> <th></th> @@ -12,16 +13,11 @@ <% @posts.each do |post| %> <tr> - <td><%= post.name %></td> <td><%= post.title %></td> - <td><%= post.content %></td> - <td><%= link_to 'Show', post %></td> - <td><%= link_to 'Edit', edit_post_path(post) %></td> - <td><%= link_to 'Destroy', post, confirm: 'Are you sure?', method: :delete %></td> + <td><%= post.text %></td> + <td><%= link_to 'Show', :action => :show, :id => post.id %> + <td><%= link_to 'Edit', :action => :edit, :id => post.id %> + <td><%= link_to 'Destroy', { :action => :destroy, :id => post.id }, :method => :delete, :confirm => 'Are you sure?' %> </tr> <% end %> </table> - -<br /> - -<%= link_to 'New Post', new_post_path %> diff --git a/guides/code/getting_started/app/views/posts/new.html.erb b/guides/code/getting_started/app/views/posts/new.html.erb index 36ad7421f9..ce9523a721 100644 --- a/guides/code/getting_started/app/views/posts/new.html.erb +++ b/guides/code/getting_started/app/views/posts/new.html.erb @@ -2,4 +2,4 @@ <%= render 'form' %> -<%= link_to 'Back', posts_path %> +<%= link_to 'Back', :action => :index %> diff --git a/guides/code/getting_started/app/views/posts/show.html.erb b/guides/code/getting_started/app/views/posts/show.html.erb index da78a9527b..aea28cd5a2 100644 --- a/guides/code/getting_started/app/views/posts/show.html.erb +++ b/guides/code/getting_started/app/views/posts/show.html.erb @@ -1,31 +1,12 @@ -<p class="notice"><%= notice %></p> - <p> - <b>Name:</b> - <%= @post.name %> -</p> - -<p> - <b>Title:</b> + <strong>Title:</strong> <%= @post.title %> </p> - -<p> - <b>Content:</b> - <%= @post.content %> -</p> - + <p> - <b>Tags:</b> - <%= join_tags(@post) %> + <strong>Text:</strong> + <%= @post.text %> </p> - -<h2>Comments</h2> -<%= render @post.comments %> - -<h2>Add a comment:</h2> -<%= render "comments/form" %> - - -<%= link_to 'Edit Post', edit_post_path(@post) %> | -<%= link_to 'Back to Posts', posts_path %> | + +<%= link_to 'Back', :action => :index %> +| <%= link_to 'Edit', :action => :edit, :id => @post.id %> diff --git a/guides/code/getting_started/app/views/tags/_form.html.erb b/guides/code/getting_started/app/views/tags/_form.html.erb deleted file mode 100644 index 7e424b0e20..0000000000 --- a/guides/code/getting_started/app/views/tags/_form.html.erb +++ /dev/null @@ -1,12 +0,0 @@ -<%= form.fields_for :tags do |tag_form| %> - <div class="field"> - <%= tag_form.label :name, 'Tag:' %> - <%= tag_form.text_field :name %> - </div> - <% unless tag_form.object.nil? || tag_form.object.new_record? %> - <div class="field"> - <%= tag_form.label :_destroy, 'Remove:' %> - <%= tag_form.check_box :_destroy %> - </div> - <% end %> -<% end %> diff --git a/guides/code/getting_started/app/views/welcome/index.html.erb b/guides/code/getting_started/app/views/welcome/index.html.erb new file mode 100644 index 0000000000..e04680ea7e --- /dev/null +++ b/guides/code/getting_started/app/views/welcome/index.html.erb @@ -0,0 +1,2 @@ +<h1>Hello, Rails!</h1> +<%= link_to "My Blog", :controller => "posts" %> diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb index b048ac68f1..6095a05a58 100644 --- a/guides/code/getting_started/config/routes.rb +++ b/guides/code/getting_started/config/routes.rb @@ -1,9 +1,15 @@ Blog::Application.routes.draw do - resources :posts do - resources :comments - end + # resources :posts do + # resources :comments + # end - get "home/index" + get "posts" => "posts#index" + get "posts/new" + post "posts/create" + get "posts/:id" => "posts#show", :as => :post + get "posts/:id/edit" => "posts#edit" + put "posts/:id" => "posts#update" + delete "posts/:id" => "posts#destroy" # The priority is based upon order of creation: # first created -> highest priority. @@ -54,8 +60,8 @@ Blog::Application.routes.draw do # You can have the root of your site routed with "root" # just remember to delete public/index.html. - root :to => "home#index" - + root :to => "welcome#index" + # See how all your routes lay out with "rake routes" # This is a legacy wild controller route that's not recommended for RESTful applications. diff --git a/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb b/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb deleted file mode 100644 index cf95b1c3d0..0000000000 --- a/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateTags < ActiveRecord::Migration - def change - create_table :tags do |t| - t.string :name - t.references :post - - t.timestamps - end - add_index :tags, :post_id - end -end diff --git a/guides/code/getting_started/db/migrate/20110901012504_create_posts.rb b/guides/code/getting_started/db/migrate/20120420083127_create_posts.rb index d45a961523..602bef31ab 100644 --- a/guides/code/getting_started/db/migrate/20110901012504_create_posts.rb +++ b/guides/code/getting_started/db/migrate/20120420083127_create_posts.rb @@ -1,9 +1,8 @@ class CreatePosts < ActiveRecord::Migration def change create_table :posts do |t| - t.string :name t.string :title - t.text :content + t.text :text t.timestamps end diff --git a/guides/code/getting_started/db/schema.rb b/guides/code/getting_started/db/schema.rb index 9db4fbe4b6..cfb56ca9b9 100644 --- a/guides/code/getting_started/db/schema.rb +++ b/guides/code/getting_started/db/schema.rb @@ -11,31 +11,30 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110901013701) do +ActiveRecord::Schema.define(:version => 20120420083127) do create_table "comments", :force => true do |t| t.string "commenter" t.text "body" t.integer "post_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end add_index "comments", ["post_id"], :name => "index_comments_on_post_id" create_table "posts", :force => true do |t| - t.string "name" t.string "title" - t.text "content" - t.datetime "created_at" - t.datetime "updated_at" + t.text "text" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "tags", :force => true do |t| t.string "name" t.integer "post_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end add_index "tags", ["post_id"], :name => "index_tags_on_post_id" diff --git a/guides/code/getting_started/test/fixtures/posts.yml b/guides/code/getting_started/test/fixtures/posts.yml index 8b0f75a33d..e1edfd385e 100644 --- a/guides/code/getting_started/test/fixtures/posts.yml +++ b/guides/code/getting_started/test/fixtures/posts.yml @@ -1,11 +1,9 @@ # Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html one: - name: MyString title: MyString - content: MyText + text: MyText two: - name: MyString title: MyString - content: MyText + text: MyText diff --git a/guides/code/getting_started/test/functional/home_controller_test.rb b/guides/code/getting_started/test/functional/home_controller_test.rb index 0d9bb47c3e..dff8e9d2c5 100644 --- a/guides/code/getting_started/test/functional/home_controller_test.rb +++ b/guides/code/getting_started/test/functional/home_controller_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class HomeControllerTest < ActionController::TestCase +class WelcomeControllerTest < ActionController::TestCase test "should get index" do get :index assert_response :success diff --git a/guides/code/getting_started/vendor/assets/stylesheets/.gitkeep b/guides/code/getting_started/vendor/assets/stylesheets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 --- a/guides/code/getting_started/vendor/assets/stylesheets/.gitkeep +++ /dev/null diff --git a/guides/source/action_view_overview.textile b/guides/source/action_view_overview.textile index 6649974eea..fd1b6c5fc2 100644 --- a/guides/source/action_view_overview.textile +++ b/guides/source/action_view_overview.textile @@ -833,7 +833,7 @@ Reports the approximate distance in time between two Time or Date objects or int <ruby> 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 +distance_of_time_in_words(Time.now, Time.now + 15.seconds, :include_seconds => true) # => less than 20 seconds </ruby> h5. select_date diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile index de55401c1f..902ceeb78b 100644 --- a/guides/source/active_record_querying.textile +++ b/guides/source/active_record_querying.textile @@ -356,20 +356,6 @@ Client.where("created_at >= :start_date AND created_at <= :end_date", This makes for clearer readability if you have a large number of variable conditions. -h5(#array-range_conditions). Range Conditions - -If you're looking for a range inside of a table (for example, users created in a certain timeframe) you can use the conditions option coupled with the +IN+ SQL statement for this. If you had two dates coming in from a controller you could do something like this to look for a range: - -<ruby> -Client.where(:created_at => (params[:start_date].to_date)..(params[:end_date].to_date)) -</ruby> - -This query will generate something similar to the following SQL: - -<sql> - SELECT "clients".* FROM "clients" WHERE ("clients"."created_at" BETWEEN '2010-09-29' AND '2010-11-30') -</sql> - h4. Hash Conditions Active Record also allows you to pass in hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them: @@ -388,9 +374,9 @@ The field name can also be a string: Client.where('locked' => true) </ruby> -h5(#hash-range_conditions). Range Conditions +NOTE: The values cannot be symbols. For example, you cannot do +Client.where(:status => :active)+. -The good thing about this is that we can pass in a range for our fields without it generating a large query as shown in the preamble of this section. +h5(#hash-range_conditions). Range Conditions <ruby> Client.where(:created_at => (Time.now.midnight - 1.day)..Time.now.midnight) @@ -539,7 +525,9 @@ And this will give you a single +Order+ object for each date where there are ord The SQL that would be executed would be something like this: <sql> -SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at) +SELECT date(created_at) as ordered_date, sum(price) as total_price +FROM orders +GROUP BY date(created_at) </sql> h3. Having @@ -555,7 +543,10 @@ Order.select("date(created_at) as ordered_date, sum(price) as total_price").grou The SQL that would be executed would be something like this: <sql> -SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at) HAVING sum(price) > 100 +SELECT date(created_at) as ordered_date, sum(price) as total_price +FROM orders +GROUP BY date(created_at) +HAVING sum(price) > 100 </sql> This will return single order objects for each day, but only those that are ordered more than $100 in a day. @@ -829,7 +820,7 @@ SELECT categories.* FROM categories INNER JOIN posts ON posts.category_id = categories.id </sql> -Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use Category.joins(:post).select("distinct(categories.id)"). +Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use Category.joins(:posts).select("distinct(categories.id)"). h5. Joining Multiple Associations diff --git a/guides/source/active_record_validations_callbacks.textile b/guides/source/active_record_validations_callbacks.textile index 88c4481e5e..f49d91fd3c 100644 --- a/guides/source/active_record_validations_callbacks.textile +++ b/guides/source/active_record_validations_callbacks.textile @@ -1064,6 +1064,7 @@ Additionally, the +after_find+ callback is triggered by the following finder met * +find_all_by_<em>attribute</em>+ * +find_by_<em>attribute</em>+ * +find_by_<em>attribute</em>!+ +* +find_by_sql+ * +last+ The +after_initialize+ callback is triggered every time a new object of the class is initialized. @@ -1076,7 +1077,6 @@ Just as with validations, it is also possible to skip callbacks. These methods s * +decrement_counter+ * +delete+ * +delete_all+ -* +find_by_sql+ * +increment+ * +increment_counter+ * +toggle+ diff --git a/guides/source/active_support_core_extensions.textile b/guides/source/active_support_core_extensions.textile index 5d0a3f82e8..e4a6e145b9 100644 --- a/guides/source/active_support_core_extensions.textile +++ b/guides/source/active_support_core_extensions.textile @@ -1131,7 +1131,7 @@ h4. Output Safety h5. Motivation -Inserting data into HTML templates needs extra care. For example you can't just interpolate +@review.title+ verbatim into an HTML page. On one hand if the review title is "Flanagan & Matz rules!" the output won't be well-formed because an ampersand has to be escaped as "&amp;". On the other hand, depending on the application that may be a big security hole because users can inject malicious HTML setting a hand-crafted review title. Check out the "section about cross-site scripting in the Security guide":security.html#cross-site-scripting-xss for further information about the risks. +Inserting data into HTML templates needs extra care. For example, you can't just interpolate +@review.title+ verbatim into an HTML page. For one thing, if the review title is "Flanagan & Matz rules!" the output won't be well-formed because an ampersand has to be escaped as "&amp;". What's more, depending on the application, that may be a big security hole because users can inject malicious HTML setting a hand-crafted review title. Check out the "section about cross-site scripting in the Security guide":security.html#cross-site-scripting-xss for further information about the risks. h5. Safe Strings diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile index 717654d5d8..68426221bf 100644 --- a/guides/source/configuring.textile +++ b/guides/source/configuring.textile @@ -362,7 +362,7 @@ h4. Configuring Action View Proc.new { |html_tag, instance| %Q(<div class="field_with_errors">#{html_tag}</div>).html_safe } </ruby> -* +config.action_view.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+. If you want your form builder class to be loaded after initialization (so it's reloaded on each request in development), you can pass it as a +String+ * +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action View. Set to +nil+ to disable logging. @@ -525,6 +525,14 @@ development: password: </yaml> +Prepared Statements can be disabled thus: + +<yaml> +production: + adapter: postgresql + prepared_statements: false +</yaml> + h5. Configuring an SQLite3 Database for JRuby Platform If you choose to use SQLite3 and are using JRuby, your +config/database.yml+ will look a little different. Here's the development section: diff --git a/guides/source/debugging_rails_applications.textile b/guides/source/debugging_rails_applications.textile index 57c7786636..903ed59e7b 100644 --- a/guides/source/debugging_rails_applications.textile +++ b/guides/source/debugging_rails_applications.textile @@ -191,7 +191,7 @@ Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localh Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels, to avoid filling your production logs with useless trivia. -h3. Debugging with +ruby-debug+ +h3. Debugging with the +debugger+ gem When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion. @@ -199,17 +199,13 @@ The debugger can also help you if you want to learn about the Rails source code h4. Setup -The debugger used by Rails, +ruby-debug+, comes as a gem. To install it, just run: +Rails uses the +debugger+ gem to set breakpoints and step through live code. To install it, just run: <shell> -$ sudo gem install ruby-debug +$ gem install debugger </shell> -TIP: If you are using Ruby 1.9, you can install a compatible version of +ruby-debug+ by running +sudo gem install ruby-debug19+ - -In case you want to download a particular version or get the source code, refer to the "project's page on rubyforge":http://rubyforge.org/projects/ruby-debug/. - -Rails has had built-in support for ruby-debug since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the +debugger+ method. +Rails has had built-in support for debugging since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the +debugger+ method. Here's an example: @@ -238,11 +234,11 @@ $ rails server --debugger ... </shell> -TIP: In development mode, you can dynamically +require \'ruby-debug\'+ instead of restarting the server, if it was started without +--debugger+. +TIP: In development mode, you can dynamically +require \'debugger\'+ instead of restarting the server, if it was started without +--debugger+. h4. The Shell -As soon as your application calls the +debugger+ method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at ruby-debug's prompt +(rdb:n)+. The _n_ is the thread number. The prompt will also show you the next line of code that is waiting to run. +As soon as your application calls the +debugger+ method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at the debugger's prompt +(rdb:n)+. The _n_ is the thread number. The prompt will also show you the next line of code that is waiting to run. If you got there by a browser request, the browser tab containing the request will be hung until the debugger has finished and the trace has finished processing the entire request. @@ -270,7 +266,7 @@ continue edit frame method putl set tmate where TIP: To view the help menu for any command use +help <command-name>+ in active debug mode. For example: _+help var+_ -The next command to learn is one of the most useful: +list+. You can also abbreviate ruby-debug commands by supplying just enough letters to distinguish them from other commands, so you can also use +l+ for the +list+ command. +The next command to learn is one of the most useful: +list+. You can abbreviate any debugging command by supplying just enough letters to distinguish them from other commands, so you can also use +l+ for the +list+ command. This command shows you where you are in the code by printing 10 lines centered around the current line; the current line in this particular case is line 6 and is marked by +=>+. @@ -347,7 +343,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 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. +The debugger 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. @@ -463,7 +459,7 @@ h4. Step by Step Now you should know where you are in the running trace and be able to print the available variables. But lets continue and move on with the application execution. -Use +step+ (abbreviated +s+) to continue running your program until the next logical stopping point and return control to ruby-debug. +Use +step+ (abbreviated +s+) to continue running your program until the next logical stopping point and return control to the debugger. TIP: You can also use <tt>step<plus> n</tt> and <tt>step- n</tt> to move forward or backward +n+ steps respectively. @@ -485,12 +481,12 @@ class Author < ActiveRecord::Base end </ruby> -TIP: You can use ruby-debug while using +rails console+. Just remember to +require "ruby-debug"+ before calling the +debugger+ method. +TIP: You can use the debugger while using +rails console+. Just remember to +require "debugger"+ before calling the +debugger+ method. <shell> $ rails console Loading development environment (Rails 3.1.0) ->> require "ruby-debug" +>> require "debugger" => [] >> author = Author.first => #<Author id: 1, first_name: "Bob", last_name: "Smith", created_at: "2008-07-31 12:46:10", updated_at: "2008-07-31 12:46:10"> @@ -603,7 +599,7 @@ A simple quit tries to terminate all threads in effect. Therefore your server wi h4. Settings -There are some settings that can be configured in ruby-debug to make it easier to debug your code. Here are a few of the available options: +The +debugger+ gem can automatically show the code you're stepping through and reload it when you change it in an editor. Here are a few of the available options: * +set reload+: Reload source code when changed. * +set autolist+: Execute +list+ command on every breakpoint. @@ -612,7 +608,7 @@ There are some settings that can be configured in ruby-debug to make it easier t You can see the full list by using +help set+. Use +help set _subcommand_+ to learn about a particular +set+ command. -TIP: You can include any number of these configuration lines inside a +.rdebugrc+ file in your HOME directory. ruby-debug will read this file every time it is loaded and configure itself accordingly. +TIP: You can save these settings in an +.rdebugrc+ file in your home directory. The debugger reads these global settings when it starts. Here's a good start for an +.rdebugrc+: @@ -637,7 +633,7 @@ If a Ruby object does not go out of scope, the Ruby Garbage Collector won't swee To install it run: <shell> -$ sudo gem install bleak_house +$ gem install bleak_house </shell> Then setup your application for profiling. Then add the following at the bottom of config/environment.rb: @@ -703,11 +699,12 @@ There are some Rails plugins to help you to find errors and debug your applicati h3. References * "ruby-debug Homepage":http://www.datanoise.com/ruby-debug +* "debugger Homepage":http://github.com/cldwalker/debugger * "Article: Debugging a Rails application with ruby-debug":http://www.sitepoint.com/article/debug-rails-app-ruby-debug/ * "ruby-debug Basics screencast":http://brian.maybeyoureinsane.net/blog/2007/05/07/ruby-debug-basics-screencast/ -* "Ryan Bate's ruby-debug screencast":http://railscasts.com/episodes/54-debugging-with-ruby-debug -* "Ryan Bate's stack trace screencast":http://railscasts.com/episodes/24-the-stack-trace -* "Ryan Bate's logger screencast":http://railscasts.com/episodes/56-the-logger +* "Ryan Bates' debugging ruby (revised) screencast":http://railscasts.com/episodes/54-debugging-ruby-revised +* "Ryan Bates' stack trace screencast":http://railscasts.com/episodes/24-the-stack-trace +* "Ryan Bates' logger screencast":http://railscasts.com/episodes/56-the-logger * "Debugging with ruby-debug":http://bashdb.sourceforge.net/ruby-debug.html * "ruby-debug cheat sheet":http://cheat.errtheblog.com/s/rdebug/ * "Ruby on Rails Wiki: How to Configure Logging":http://wiki.rubyonrails.org/rails/pages/HowtoConfigureLogging diff --git a/guides/source/engines.textile b/guides/source/engines.textile index 36210aedb0..71bcf6b713 100644 --- a/guides/source/engines.textile +++ b/guides/source/engines.textile @@ -695,7 +695,7 @@ If a template is rendered from within an engine and it's attempting to use one o h4. Assets -Assets within an engine work in an identical way to a full application. Because the engine class inherits from +Rails::Engine+, the application will know to look up in the engine's +app/assets+ directory for potential assets. +Assets within an engine work in an identical way to a full application. Because the engine class inherits from +Rails::Engine+, the application will know to look up in the engine's +app/assets+ and +lib/assets+ directories for potential assets. Much like all the other components of an engine, the assets should also be namespaced. This means if you have an asset called +style.css+, it should be placed at +app/assets/stylesheets/[engine name]/style.css+, rather than +app/assets/stylesheets/style.css+. If this asset wasn't namespaced, then there is a possibility that the host application could have an asset named identically, in which case the application's asset would take precedence and the engine's one would be all but ignored. diff --git a/guides/source/form_helpers.textile b/guides/source/form_helpers.textile index 8934667c5e..b6420db798 100644 --- a/guides/source/form_helpers.textile +++ b/guides/source/form_helpers.textile @@ -89,7 +89,7 @@ form_tag(:controller => "people", :action => "search", :method => "get", :class # => '<form accept-charset="UTF-8" action="/people/search?method=get&class=nifty_form" method="post">' </ruby> -Here, +method+ and +class+ are appended to the query string of the generated URL because you even though you mean to write two hashes, you really only specified one. So you need to tell Ruby which is which by delimiting the first hash (or both) with curly brackets. This will generate the HTML you expect: +Here, +method+ and +class+ are appended to the query string of the generated URL because even though you mean to write two hashes, you really only specified one. So you need to tell Ruby which is which by delimiting the first hash (or both) with curly brackets. This will generate the HTML you expect: <ruby> form_tag({:controller => "people", :action => "search"}, :method => "get", :class => "nifty_form") diff --git a/guides/source/generators.textile b/guides/source/generators.textile index 920ff997ae..2e9ab0526d 100644 --- a/guides/source/generators.textile +++ b/guides/source/generators.textile @@ -451,6 +451,27 @@ Adds a specified source to +Gemfile+: add_source "http://gems.github.com" </ruby> +h4. +inject_into_file+ + +Injects a block of code into a defined position in your file. + +<ruby> +inject_into_file 'name_of_file.rb', :after => "#The code goes below this line. Don't forget the Line break at the end\n" do <<-'RUBY' + puts "Hello World" +RUBY +end +</ruby> + +h4. +gsub_file+ + +Replaces text inside a file. + +<ruby> +gsub_file 'name_of_file.rb', 'method.to_be_replaced', 'method.the_replacing_code' +</ruby> + +Regular Expressions can be used to make this method more precise. You can also use append_file and prepend_file in the same way to place code at the beginning and end of a file respectively. + h4. +application+ Adds a line to +config/application.rb+ directly after the application class definition. diff --git a/guides/source/getting_started.textile b/guides/source/getting_started.textile index 59b12b49e5..b48ebbceb2 100644 --- a/guides/source/getting_started.textile +++ b/guides/source/getting_started.textile @@ -10,7 +10,7 @@ you should be familiar with: endprologue. -WARNING. This Guide is based on Rails 3.1. Some of the code shown here will not +WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not work in earlier versions of Rails. WARNING: The Edge version of this guide is currently being re-worked. Please excuse us while we re-arrange the place. @@ -27,8 +27,8 @@ prerequisites installed: TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails 3.0. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02 though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults -on Rails 3.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 for smooth -sailing. +on Rails 3.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 or +1.9.3 for smooth sailing. * The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system ** If you want to learn more about RubyGems, please read the "RubyGems User Guide":http://docs.rubygems.org/read/book/1 @@ -45,10 +45,6 @@ internet for learning Ruby, including: h3. What is Rails? -TIP: This section goes into the background and philosophy of the Rails framework -in detail. You can safely skip this section and come back to it at a later time. -Section 3 starts you on the path to creating your first Rails application. - Rails is a web application development framework written in the Ruby language. It is designed to make programming web applications easier by making assumptions about what every developer needs to get started. It allows you to write less @@ -73,7 +69,8 @@ h3. Creating a New Rails Project The best way to use this guide is to follow each step as it happens, no code or step needed to make this example application has been left out, so you can -literally follow along step by step. You can get the complete code "here":https://github.com/lifo/docrails/tree/master/guides/code/getting_started. +literally follow along step by step. You can get the complete code +"here":https://github.com/lifo/docrails/tree/master/guides/code/getting_started. By following along with this guide, you'll create a Rails project called <tt>blog</tt>, a (very) simple weblog. Before you can start building the application, you need to @@ -97,7 +94,7 @@ To verify that you have everything installed correctly, you should be able to ru $ rails --version </shell> -If it says something like "Rails 3.2.2" you are ready to continue. +If it says something like "Rails 3.2.3" you are ready to continue. h4. Creating the Blog Application @@ -111,7 +108,7 @@ $ rails new blog This will create a Rails application called Blog in a directory called blog. -TIP: You can see all of the switches that the Rails application builder accepts by running <tt>rails new -h</tt>. +TIP: You can see all of the command line options that the Rails application builder accepts by running <tt>rails new -h</tt>. After you create the blog application, switch to its folder to continue work directly in that application: @@ -140,7 +137,7 @@ The +rails new blog+ command we ran above created a folder in your working direc h3. Hello, Rails! -One of the traditional places to start with a new language is by getting some text up on screen quickly. To do this, you need to get your Rails application server running. +To begin with, let's get some text up on screen quickly. To do this, you need to get your Rails application server running. h4. Starting up the Web Server @@ -152,7 +149,7 @@ $ rails server TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the absence of a runtime will give you an +execjs+ error. Usually Mac OS X and Windows come with a JavaScript runtime installed. Rails adds the +therubyracer+ gem to Gemfile in a commented line for new apps and you can uncomment if you need it. +therubyrhino+ is the recommended runtime for JRuby users and is added by default to Gemfile in apps generated under JRuby. You can investigate about all the supported runtimes at "ExecJS":https://github.com/sstephenson/execjs#readme. -This will fire up an instance of a webserver built into Ruby called WEBrick by default. To see your application in action, open a browser window and navigate to "http://localhost:3000":http://localhost:3000. You should see the Rails default information page: +This will fire up WEBrick, a webserver built into Ruby by default. To see your application in action, open a browser window and navigate to "http://localhost:3000":http://localhost:3000. You should see the Rails default information page: !images/rails_welcome.png(Welcome Aboard screenshot)! @@ -164,7 +161,7 @@ h4. Say "Hello", Rails To get Rails saying "Hello", you need to create at minimum a _controller_ and a _view_. -A controller's purpose is to receive specific requests for the application. What controller receives what request is determined by the _routing_. There is very often more than one route to each controller, and different routes can be served by different _actions_. Each action's purpose is to collect information to provide it to a view. +A controller's purpose is to receive specific requests for the application. _Routing_ decides which controller receives which requests. Often, there is more than one route to each controller, and different routes can be served by different _actions_. Each action's purpose is to collect information to provide it to a view. A view's purpose is to display this information in a human readable format. An important distinction to make is that it is the _controller_, not the view, where information is collected. The view should just display that information. By default, view templates are written in a language called ERB (Embedded Ruby) which is converted by the request cycle in Rails before being sent to the user. @@ -225,7 +222,6 @@ h4. Laying down the ground work The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at +/posts/new+. If you attempt to navigate to that now -- by visiting "http://localhost:3000/posts/new":http://localhost:3000/posts/new -- Rails will give you a routing error: - !images/getting_started/routing_error_no_route_matches.png(A routing error, no route matches /posts/new)! This is because there is nowhere inside the routes for the application -- defined inside +config/routes.rb+ -- that defines this route. By default, Rails has no routes configured at all, and so you must define your routes as you need them. @@ -240,7 +236,7 @@ This route is a super-simple route: it defines a new route that only responds to With the route defined, requests can now be made to +/posts/new+ in the application. Navigate to "http://localhost:3000/posts/new":http://localhost:3000/posts/new and you'll see another routing error: -!images/getting_started/routing_error_no_controller.png(Another routing error, uninitialized constant PostsController) +!images/getting_started/routing_error_no_controller.png(Another routing error, uninitialized constant PostsController)! This error is happening because this route need a controller to be defined. The route is attempting to find that controller so it can serve the request, but with the controller undefined, it just can't do that. The solution to this particular problem is simple: you need to create a controller called +PostsController+. You can do this by running this command: @@ -259,7 +255,7 @@ A controller is simply a class that is defined to inherit from +ApplicationContr If you refresh "http://localhost:3000/posts/new":http://localhost:3000/posts/new now, you'll get a new error: -!images/getting_started/unknown_action_new_for_posts.png(Unknown action new for PostsController!) +!images/getting_started/unknown_action_new_for_posts.png(Unknown action new for PostsController!)! This error indicates that Rails cannot find the +new+ action inside the +PostsController+ that you just generated. This is because when controllers are generated in Rails they are empty by default, unless you tell it you wanted actions during the generation process. @@ -272,7 +268,7 @@ end With the +new+ method defined in +PostsController+, if you refresh "http://localhost:3000/posts/new":http://localhost:3000/posts/new you'll see another error: -!images/getting_started/template_is_missing_posts_new.png(Template is missing for posts/new) +!images/getting_started/template_is_missing_posts_new.png(Template is missing for posts/new)! You're getting this error now because Rails expects plain actions like this one to have views associated with them to display their information. With no view available, Rails errors out. @@ -302,7 +298,9 @@ When you refresh "http://localhost:3000/posts/new":http://localhost:3000/posts/n h4. The first form -To create a form within this template, you will use a _form builder_. The primary form builder for Rails is provided by a helper method called +form_for+. To use this method, write this code into +app/views/posts/new.html.erb+: +To create a form within this template, you will use a <em>form +builder</em>. The primary form builder for Rails is provided by a helper +method called +form_for+. To use this method, add this code into +app/views/posts/new.html.erb+: <erb> <%= form_for :post do |f| %> @@ -346,7 +344,7 @@ By using the +post+ method rather than the +get+ method, Rails will define a rou With the form and the route for it defined now, you will be able to fill in the form and then click the submit button to begin the process of creating a new post, so go ahead and do that. When you submit the form, you should see a familiar error: -!images/getting_started/unknown_action_create_for_posts(Unknown action create for PostsController)! +!images/getting_started/unknown_action_create_for_posts.png(Unknown action create for PostsController)! You will now need to create the +create+ action within the +PostsController+ for this to work. @@ -385,25 +383,44 @@ If you re-submit the form one more time you'll now no longer get the missing tem This action is now displaying the parameters for the post that are coming in from the form. However, this isn't really all that helpful. Yes, you can see the parameters but nothing in particular is being done with them. +h4. Creating the Post model + +Rails uses models to manage database objects, so if you want to save +data to the database you'll have to create a model. In our blog +application you want to save posts, so you'll create a +Post+ model. + +You can create a model with the following command: + +<shell> +$ rails generate model Post title:string text:text +</shell> + +With that command we told Rails that we want a +Post+ model, which in +turn should have a title attribute of type string, and a text attribute +of type text. Rails in turn responded by creating a bunch of files. For +now, we're only interested in +app/models/post.rb+ and ++db/migrate/20120419084633_create_posts.rb+. The latter is responsible +for creating the database structure, which is what we'll look at next. + h4. Running a Migration -One of the products of the +rails generate scaffold+ command is a _database -migration_. Migrations are Ruby classes that are designed to make it simple to +As we've just seen, +rails generate model+ created a _database +migration_ file inside the +db/migrate+ directory. +Migrations are Ruby classes that are designed to make it simple to create and modify database tables. Rails uses rake commands to run migrations, and it's possible to undo a migration after it's been applied to your database. Migration filenames include a timestamp to ensure that they're processed in the order that they were created. -If you look in the +db/migrate/20100207214725_create_posts.rb+ file (remember, +If you look in the +db/migrate/20120419084633_create_posts.rb+ file (remember, yours will have a slightly different name), here's what you'll find: <ruby> class CreatePosts < ActiveRecord::Migration def change create_table :posts do |t| - t.string :name t.string :title - t.text :content + t.text :text t.timestamps end @@ -415,7 +432,7 @@ The above migration creates a method named +change+ which will be called when yo run this migration. The action defined in this method is also reversible, which means Rails knows how to reverse the change made by this migration, in case you want to reverse it later. When you run this migration it will create a -+posts+ table with two string columns and a text column. It also creates two ++posts+ table with one string column and a text column. It also creates two timestamp fields to allow Rails to track post creation and update times. More information about Rails migrations can be found in the "Rails Database Migrations":migrations.html guide. @@ -442,41 +459,191 @@ command will apply to the database defined in the +development+ section of your environment, for instance in production, you must explicitly pass it when invoking the command: <tt>rake db:migrate RAILS_ENV=production</tt>. -h4. Adding a Link +h4. Saving data in the controller + +Back in +posts_controller+, we need to change the +create+ action +to use the new +Post+ model to save data in the database. Open that file +and change the +create+ action to look like the following: + +<ruby> +def create + @post = Post.new(params[:post]) + + @post.save + redirect_to :action => :show, :id => @post.id +end +</ruby> + +Here's what's going on: every Rails model can be initialized with its +respective attributes, which are automatically mapped to its +database columns. In the first line we do just that (remember that ++params[:post]+ contains the attributes we're interested in). Then, ++@post.save+ is responsible for saving the model in the database. +Finally, on the last line we redirect the user to the +show+ action, +wich we have not defined yet. + +TIP: As we'll see later, +@post.save+ returns a boolean indicating +wherever the model was saved or not, and you can (and usually do) take +different actions depending on the result of calling +@post.save+. + +h4. Showing posts + +Before trying to create a new post, let's add the +show+ action, which +will be responsible for showing our posts. Open +config/routes.rb+ +and add the following route: + +<ruby> +get "posts/:id" => "posts#show" +</ruby> + +The special syntax +:id+ tells rails that this route expects an +:id+ +parameter, which in our case will be the id of the post. Note that this +time we had to specify the actual mapping, +posts#show+ because +otherwise Rails would not know which action to render. + +As we did before, we need to add the +show+ action in the ++posts_controller+ and its respective view. + +<ruby> +def show + @post = Post.find(params[:id]) +end +</ruby> + +A couple of things to note. We use +Post.find+ to find the post we're +interested in. We also use an instance variable (prefixed by +@+) to +hold a reference to the post object. We do this because Rails will pass all instance +variables to the view. + +Now, create a new file +app/view/posts/show.html.erb+ with the following +content: + +<erb> +<p> + <strong>Title:</strong> + <%= @post.title %> +</p> + +<p> + <strong>Text:</strong> + <%= @post.text %> +</p> +</erb> + +Finally, if you now go to +"http://localhost:3000/posts/new":http://localhost:3000/posts/new you'll +be able to create a post. Try it! -To hook the posts up to the home page you've already created, you can add a link -to the home page. Open +app/views/welcome/index.html.erb+ and modify it as follows: +!images/getting_started/show_action_for_posts.png(Show action for posts)! + +h4. Listing all posts + +We still need a way to list all our posts, so let's do that. As usual, +we'll need a route, a controller action, and a view: + +<ruby> +# Add to config/routes.rb +get "posts" => "posts#index" + +# Add to app/controllers/posts_controller.rb +def index + @posts = Post.all +end +</ruby> + ++app/view/posts/index.html.erb+: + +<erb> +<h1>Listing posts</h1> + +<table> + <tr> + <th>Title</th> + <th>Text</th> + </tr> + +<% @posts.each do |post| %> + <tr> + <td><%= post.title %></td> + <td><%= post.text %></td> + </tr> +<% end %> +</table> +</erb> + +h4. Adding links + +You can now create, show, and list posts. Now let's add some links to +navigate through pages. + +Open +app/views/welcome/index.html.erb+ and modify it as follows: <ruby> <h1>Hello, Rails!</h1> -<%= link_to "My Blog", posts_path %> +<%= link_to "My Blog", :controller => "posts" %> </ruby> The +link_to+ method is one of Rails' built-in view helpers. It creates a hyperlink based on text to display and where to go - in this case, to the path for posts. -h4. Working with Posts in the Browser +Let's add links to the other views as well. + +<erb> +# app/views/posts/index.html.erb + +<h1>Listing posts</h1> + +<%= link_to 'New post', :action => :new %> + +<table> + <tr> + <th>Title</th> + <th>Text</th> + <th></th> + </tr> + +<% @posts.each do |post| %> + <tr> + <td><%= post.title %></td> + <td><%= post.text %></td> + <td><%= link_to 'Show', :action => :show, :id => post.id %></td> + </tr> +<% end %> +</table> + +# app/views/posts/new.html.erb + +<%= form_for :post do |f| %> + ... +<% end %> + +<%= link_to 'Back', :action => :index %> + +# app/views/posts/show.html.erb + +<p> + <strong>Title:</strong> + <%= @post.title %> +</p> -Now you're ready to start working with posts. To do that, navigate to -"http://localhost:3000":http://localhost:3000/ and then click the "My Blog" -link: +<p> + <strong>Text:</strong> + <%= @post.text %> +</p> -!images/posts_index.png(Posts Index screenshot)! +<%= link_to 'Back', :action => :index %> +</erb> -This is the result of Rails rendering the +index+ view of your posts. There -aren't currently any posts in the database, but if you click the +New Post+ link -you can create one. After that, you'll find that you can edit posts, look at -their details, or destroy them. All of the logic and HTML to handle this was -built by the single +rails generate scaffold+ command. +TIP: If you want to link to an action in the same controller, you don't +need to specify the +:controller+ option, as Rails will use the current +controller by default. TIP: In development mode (which is what you're working in by default), Rails reloads your application with every browser request, so there's no need to stop -and restart the web server. - -Congratulations, you're riding the rails! Now it's time to see how it all works. +and restart the web server when a change is made. -h4. The Model +h4. Adding Some Validation The model file, +app/models/post.rb+ is about as simple as it can get: @@ -491,24 +658,442 @@ your Rails models for free, including basic database CRUD (Create, Read, Update, Destroy) operations, data validation, as well as sophisticated search support and the ability to relate multiple models to one another. -h4. Adding Some Validation - Rails includes methods to help you validate the data that you send to models. Open the +app/models/post.rb+ file and edit it: <ruby> class Post < ActiveRecord::Base - validates :name, :presence => true validates :title, :presence => true, :length => { :minimum => 5 } end </ruby> -These changes will ensure that all posts have a name and a title, and that the -title is at least five characters long. Rails can validate a variety of -conditions in a model, including the presence or uniqueness of columns, their +These changes will ensure that all posts have a title that is at least five characters long. +Rails can validate a variety of conditions in a model, including the presence or uniqueness of columns, their format, and the existence of associated objects. Validations are covered in detail -in "Active Record Validations and Callbacks":active_record_validations_callbacks.html#validations-overview +in "Active Record Validations and +Callbacks":active_record_validations_callbacks.html#validations-overview + +If you open +posts_controller+ again, you'll notice that we don't check +the result of calling +@post.save+. We need to change its behavior to +show the form back to the user if any error occur: + +<ruby> +def new + @post = Post.new +end + +def create + @post = Post.new(params[:post]) + + if @post.save + redirect_to :action => :show, :id => @post.id + else + render 'new' + end +end +</ruby> + +Notice that I've also added +@post = Post.new+ to the +new+ action. I'll +explain why I did that in the next section, for now add that to your +controller as well. + +Also notice that we use +render+ instead of +redirect_to+ when +save+ +returns false. We can use +render+ so that the +@post+ object is passed +back to the view. + +If you reload +"http://localhost:3000/posts/new":http://localhost:3000/posts/new and +try to save a post without a title, Rails will send you back to the +form, but that's not very useful. You need to tell the user that +something went wrong. To do that, you'll modify ++app/views/posts/index.html.erb+ to check for error messages: + +<erb> +<%= form_for :post, :url => { :action => :create } do |f| %> + <% if @post.errors.any? %> + <div id="errorExplanation"> + <h2><%= pluralize(@post.errors.count, "error") %> prohibited + this post from being saved:</h2> + <ul> + <% @post.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> + </div> + <% end %> + <p> + <%= f.label :title %><br> + <%= f.text_field :title %> + </p> + + <p> + <%= f.label :text %><br> + <%= f.text_area :text %> + </p> + + <p> + <%= f.submit %> + </p> +<% end %> + +<%= link_to 'Back', :action => :index %> +</erb> + +A few things are going on. We check if there are any errors with ++@post.errors.any?+, and in that case we show a list of all +errors with +@post.errors.full_messages+. + ++pluralize+ is a rails helper +that takes a number and a string as its arguments. If the number is +greater than one, the string will be automatically pluralized. + +The reason why we added +@post = Post.new+ in +posts_controller+ is that +otherwise +@post+ would be +nil+ in our view, and calling ++@post.errors.any?+ would throw an error. + +TIP: Rails automatically wraps fields that contain an error with a div +with class +field_with_errors+. You can define a css rule to make them +standout. + +Now you'll get a nice error message when saving a post without title: + +!images/getting_started/form_with_errors.png(Form With Errors)! + +h4. Updating Posts + +We've covered the "CR" part of CRUD. Now let's focus on the "U" part, +updating posts. + +The first step we'll take is adding a +edit+ action to ++posts_controller+. + +Start by adding a route to +config/routes.rb+: + +<ruby> +get "posts/:id/edit" => "posts#edit" +</ruby> + +And then add the controller action: + +<ruby> +def edit + @post = Post.find(params[:id]) +end +</ruby> + +The view will contain a form similar to the one we used when creating +new posts. Create a file called +app/views/posts/edit.html.erb+ and make +it look as follows: + +<erb> +<h1>Editing post</h1> + +<%= form_for :post, :url => { :action => :update, :id => @post.id }, +:method => :put do |f| %> + <% if @post.errors.any? %> + <div id="errorExplanation"> + <h2><%= pluralize(@post.errors.count, "error") %> prohibited + this post from being saved:</h2> + <ul> + <% @post.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> + </div> + <% end %> + <p> + <%= f.label :title %><br> + <%= f.text_field :title %> + </p> + + <p> + <%= f.label :text %><br> + <%= f.text_area :text %> + </p> + + <p> + <%= f.submit %> + </p> +<% end %> + +<%= link_to 'Back', :action => :index %> +</erb> + +This time we point the form to the +update+ action (not defined yet). +The +:method => :put+ option tells Rails that we want this form to be +submitted via +put+, which is the http method you're expected to use to +*update* resources according to the REST protocol. + +TIP: By default forms built with the +form_for_ helper are sent via +POST+. + +Moving on, we need to add the +update+ action. The file ++config/routes.rb+ will need just one more line: + +<ruby> +put "posts/:id" => "posts#update" +</ruby> + +And the +update+ action in +posts_controller+ itself should not look too complicated by now: + +<ruby> +def update + @post = Post.find(params[:id]) + + if @post.update_attributes(params[:post]) + redirect_to :action => :show, :id => @post.id + else + render 'edit' + end +end +</ruby> + +The new method +update_attributes+ is used when you want to update a record +that already exists, and it accepts an hash containing the attributes +that you want to update. As before, if there was an error updating the +post we want to show the form back to the user. + +TIP: you don't need to pass all attributes to +update_attributes+. For +example, if you'd call +@post.update_attributes(:title => 'A new title')+ +Rails would only update the +title+ attribute, leaving all other +attributes untouched. + +Finally, we want to show a link to the +edit+ action in the +index+ and ++show+ views: + +<erb> +# app/view/posts/index.html.erb + +<table> + <tr> + <th>Title</th> + <th>Text</th> + <th></th> + <th></th> + </tr> + +<% @posts.each do |post| %> + <tr> + <td><%= post.title %></td> + <td><%= post.text %></td> + <td><%= link_to 'Show', :action => :show, :id => post.id %></td> + <td><%= link_to 'Edit', :action => :edit, :id => post.id %></td> + </tr> +<% end %> +</table> + +# app/view/posts/show.html.erb + +... + +<%= link_to 'Back', :action => :index %> +| <%= link_to 'Edit', :action => :edit, :id => @post.id %> +</erb> + +And here's how our app looks so far: + +!images/getting_started/index_action_with_edit_link.png(Index action +with edit link)! + +h4. Using partials to clean up duplication in views + ++partials+ are what Rails uses to remove duplication in views. Here's a +simple example: + +<erb> +# app/views/user/show.html.erb + +<h1><%= @user.name %></h1> + +<%= render 'user_details' %> + +# app/views/user/_user_details.html.erb + +<%= @user.location %> + +<%= @user.about_me %> +</erb> + +The +show+ view will automatically include the content of the ++_user_details+ view. Note that partials are prefixed by an underscore, +as to not be confused with regular views. However, you don't include the +underscore when including them with the +helper+ method. + +TIP: You can read more about partials in the "Layouts and Rendering in +Rails":layouts_and_rendering.html guide. + +Our +edit+ action looks very similar to the +new+ action, in fact they +both share the same code for displaying the form. Lets clean them up by +using a +_form+ partial. + +Create a new file +app/views/posts/_form.html.erb+ with the following +content: + +<erb> +<%= form_for @post do |f| %> + <% if @post.errors.any? %> + <div id="errorExplanation"> + <h2><%= pluralize(@post.errors.count, "error") %> prohibited + this post from being saved:</h2> + <ul> + <% @post.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> + </div> + <% end %> + <p> + <%= f.label :title %><br> + <%= f.text_field :title %> + </p> + + <p> + <%= f.label :text %><br> + <%= f.text_area :text %> + </p> + + <p> + <%= f.submit %> + </p> +<% end %> +</erb> + +Everything except for the +form_for+ declaration remained the same. I'll +explain later how +form_for+ can figure out the right +action+ and ++method+ attributes when building the form, for now let's update the ++new+ and +edit+ views: + +<erb> +# app/views/posts/new.html.erb + +<h1>New post</h1> + +<%= render 'form' %> + +<%= link_to 'Back', :action => :index %> + + +# app/views/posts/edit.html.erb + +<h1>Edit post</h1> + +<%= render 'form' %> + +<%= link_to 'Back', :action => :index %> +</erb> + +Point your browser to +"http://localhost:3000/posts/new":http://localhost:3000/posts/new and +try creating a new post. Everything still works. Now try editing the +post and you'll receive the following error: + +!images/getting_started/undefined_method_post_path.png(Undefined method +post_path)! + +To understand this error, you need to understand how +form_for+ works. +When you pass an object to +form_for+ and you don't specify a +:url+ +option, Rails will try to guess the +action+ and +method+ options by +checking if the passed object is a new record or not. Rails follows the +REST convention, so to create a new +Post+ object it will look for a +route named +posts_path+, and to update a +Post+ object it will look for +a route named +post_path+ and pass the current object. Similarly, rails +knows that it should create new objects via POST and update them via +PUT. + +If you run +rake routes+ from the console you'll see that we already +have a +posts_path+ route, which was created automatically by Rails. +However, we don't have a +post_path+ yet, which is the reason why we +received an error before. + +<shell> +# rake routes + + posts GET /posts(.:format) posts#index + posts_new GET /posts/new(.:format) posts#new +posts_create POST /posts/create(.:format) posts#create + GET /posts/:id(.:format) posts#show + GET /posts/:id/edit(.:format) posts#edit + PUT /posts/:id(.:format) posts#update + root / welcome#index +</shell> + +To fix this, open +config/routes.rb+ and modify the +get "posts/:id"+ +line like this: + +<ruby> +get "posts/:id" => "posts#show", :as => :post +</ruby> + +Now you'll be able to update posts again. + +h4. Deleting Posts + +We're now ready to cover the "D" part of CRUD, deleting posts from the +database. Following the REST convention, we're going to add a route for +deleting posts: + +<ruby> +# config/routes.rb + +delete "posts/:id" => "posts#destroy" +</ruby> + +We use the +delete+ method for destroying resources, which is mapped to +the +destroy+ action, which is provided below: + +<ruby> +# app/controllers/posts_controller.rb + +def destroy + @post = Post.find(params[:id]) + @post.destroy + + redirect_to :action => :index +end +</ruby> + +You can call +destroy+ on Active Record objects when you want to delete +them from the dabase. Note that we don't need to add a view for this +action since we're redirecting to the +index+ action. + +Finally, add a 'destroy' link to your +index+ action to wrap everything +together. + +<erb> +<table> + <tr> + <th>Title</th> + <th>Text</th> + <th></th> + <th></th> + <th></th> + </tr> + +<% @posts.each do |post| %> + <tr> + <td><%= post.title %></td> + <td><%= post.text %></td> + <td><%= link_to 'Show', :action => :show, :id => post.id %></td> + <td><%= link_to 'Edit', :action => :edit, :id => post.id %></td> + <td><%= link_to 'Destroy', { :action => :destroy, :id => post.id }, :method => :delete, :confirm => 'Are you sure?' %></td> + </tr> +<% end %> +</table> +</erb> + +Here we're using +link_to+ in a different way. We wrap the ++:action+ and +:id+ attributes in a hash so that we can pass other +arguments to +link_to+. The +:method+ and +:confirm+ +options are used as html5 attributes so that when the click is linked, +Rails will first show a confirm dialog to the user, and then submit the +link with method +delete+. This is done via javascript automatically. + +!images/getting_started/confirm_dialog.png(Confirm Dialog)! + +Congratulations, you can now create, show, list, update and destroy +posts. In the next section will see how Rails can aid us when creating +REST applications, and how we can refactor our Blog app to take +advantage of it. h4. Using the Console diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index 35b6fc7014..0a8daf7ae5 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -14,6 +14,8 @@ <link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" /> <link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" /> + +<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" /> </head> <body class="guide"> <% if @edge %> diff --git a/guides/source/layouts_and_rendering.textile b/guides/source/layouts_and_rendering.textile index c2bba56581..f69afaa281 100644 --- a/guides/source/layouts_and_rendering.textile +++ b/guides/source/layouts_and_rendering.textile @@ -1193,6 +1193,16 @@ h5. Spacer Templates Rails will render the +_product_ruler+ partial (with no data passed in to it) between each pair of +_product+ partials. +h5. Partial Layouts + +When rendering collections it is also possible to use the +:layout+ option: + +<erb> +<%= render :partial => "product", :collection => @products, :layout => "special_layout" %> +</erb> + +The layout will be rendered together with the partial for each item in the collection. The current object and object_counter variables will be available in the layout as well, the same way they do within the partial. + h4. Using Nested Layouts You may find that your application requires a layout that differs slightly from your regular application layout to support one particular controller. Rather than repeating the main layout and editing it, you can accomplish this by using nested layouts (sometimes called sub-templates). Here's an example: diff --git a/guides/source/migrations.textile b/guides/source/migrations.textile index f663496854..f855072fd8 100644 --- a/guides/source/migrations.textile +++ b/guides/source/migrations.textile @@ -82,6 +82,8 @@ it to default to +false+ for new users, but existing users are considered to have already opted in, so we use the User model to set the flag to +true+ for existing users. +h4. Using the change method + Rails 3.1 makes migrations smarter by providing a new <tt>change</tt> method. This method is preferred for writing constructive migrations (adding columns or tables). The migration knows how to migrate your database and reverse it when @@ -475,7 +477,16 @@ end </ruby> will add an +attachment_id+ column and a string +attachment_type+ column with -a default value of 'Photo'. +a default value of 'Photo'. +references+ also allows you to define an +index directly, instead of using +add_index+ after the +create_table+ call: + +<ruby> +create_table :products do |t| + t.references :category, :index => true +end +</ruby> + +will create an index identical to calling `add_index :products, :category_id`. NOTE: The +references+ helper does not actually create foreign key constraints for you. You will need to use +execute+ or a plugin that adds "foreign key @@ -770,7 +781,7 @@ Both migrations work for Alice. Bob comes back from vacation and: -# Updates the source - which contains both migrations and the latests version of +# Updates the source - which contains both migrations and the latest version of the Product model. # Runs outstanding migrations with +rake db:migrate+, which includes the one that updates the +Product+ model. diff --git a/guides/source/plugins.textile b/guides/source/plugins.textile index 97b4eca779..95e38db483 100644 --- a/guides/source/plugins.textile +++ b/guides/source/plugins.textile @@ -25,16 +25,14 @@ endprologue. h3. Setup -Before you continue, take a moment to decide if your new plugin will be potentially shared across different Rails applications. +_"vendored plugins"_ were available in previous versions of Rails, but they are deprecated in +Rails 3.2, and will not be available in the future. -* If your plugin is specific to your application, your new plugin will be a _vendored plugin_. -* If you think your plugin may be used across applications, build it as a _gemified plugin_. +Currently, Rails plugins are built as gems, _gemified plugins_. They can be shared accross +different rails applications using RubyGems and Bundler if desired. h4. Generate a gemified plugin. -Writing your Rails plugin as a gem, rather than as a vendored plugin, - lets you share your plugin across different rails applications using - RubyGems and Bundler. Rails 3.1 ships with a +rails plugin new+ command which creates a skeleton for developing any kind of Rails extension with the ability diff --git a/guides/source/routing.textile b/guides/source/routing.textile index 75f4e82918..836e0cdd70 100644 --- a/guides/source/routing.textile +++ b/guides/source/routing.textile @@ -25,7 +25,7 @@ GET /patients/17 it asks the router to match it to a controller action. If the first matching route is <ruby> -match "/patients/:id" => "patients#show" +get "/patients/:id" => "patients#show" </ruby> the request is dispatched to the +patients+ controller's +show+ action with <tt>{ :id => "17" }</tt> in +params+. @@ -121,7 +121,7 @@ h4. Singular Resources Sometimes, you have a resource that clients always look up without referencing an ID. For example, you would like +/profile+ to always show the profile of the currently logged in user. In this case, you can use a singular resource to map +/profile+ (rather than +/profile/:id+) to the +show+ action. <ruby> -match "profile" => "users#show" +get "profile" => "users#show" </ruby> This resourceful route @@ -374,7 +374,7 @@ h4. Bound Parameters When you set up a regular route, you supply a series of symbols that Rails maps to parts of an incoming HTTP request. Two of these symbols are special: +:controller+ maps to the name of a controller in your application, and +:action+ maps to the name of an action within that controller. For example, consider one of the default Rails routes: <ruby> -match ':controller(/:action(/:id))' +get ':controller(/:action(/:id))' </ruby> If an incoming request of +/photos/show/1+ is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the +show+ action of the +PhotosController+, and to make the final parameter +"1"+ available as +params[:id]+. This route will also route the incoming request of +/photos+ to +PhotosController#index+, since +:action+ and +:id+ are optional parameters, denoted by parentheses. @@ -384,7 +384,7 @@ h4. Dynamic Segments You can set up as many dynamic segments within a regular route as you like. Anything other than +:controller+ or +:action+ will be available to the action as part of +params+. If you set up this route: <ruby> -match ':controller/:action/:id/:user_id' +get ':controller/:action/:id/:user_id' </ruby> An incoming path of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+. @@ -392,7 +392,7 @@ An incoming path of +/photos/show/1/2+ will be dispatched to the +show+ action o NOTE: You can't use +:namespace+ or +:module+ with a +:controller+ path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g: <ruby> -match ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/ +get ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/ </ruby> TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment add a constraint which overrides this - for example +:id+ => /[^\/]+/ allows anything except a slash. @@ -402,7 +402,7 @@ h4. Static Segments You can specify static segments when creating a route: <ruby> -match ':controller/:action/:id/with_user/:user_id' +get ':controller/:action/:id/with_user/:user_id' </ruby> This route would respond to paths such as +/photos/show/1/with_user/2+. In this case, +params+ would be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>. @@ -412,7 +412,7 @@ h4. The Query String The +params+ will also include any parameters from the query string. For example, with this route: <ruby> -match ':controller/:action/:id' +get ':controller/:action/:id' </ruby> An incoming path of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>. @@ -422,7 +422,7 @@ h4. Defining Defaults You do not need to explicitly use the +:controller+ and +:action+ symbols within a route. You can supply them as defaults: <ruby> -match 'photos/:id' => 'photos#show' +get 'photos/:id' => 'photos#show' </ruby> With this route, Rails will match an incoming path of +/photos/12+ to the +show+ action of +PhotosController+. @@ -430,7 +430,7 @@ With this route, Rails will match an incoming path of +/photos/12+ to the +show+ You can also define other defaults in a route by supplying a hash for the +:defaults+ option. This even applies to parameters that you do not specify as dynamic segments. For example: <ruby> -match 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' } +get 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' } </ruby> Rails would match +photos/12+ to the +show+ action of +PhotosController+, and set +params[:format]+ to +"jpg"+. @@ -440,49 +440,45 @@ h4. Naming Routes You can specify a name for any route using the +:as+ option. <ruby> -match 'exit' => 'sessions#destroy', :as => :logout +get 'exit' => 'sessions#destroy', :as => :logout </ruby> This will create +logout_path+ and +logout_url+ as named helpers in your application. Calling +logout_path+ will return +/exit+ h4. HTTP Verb Constraints -You can use the +:via+ option to constrain the request to one or more HTTP methods: +In general, you should use the +get+, +post+, +put+ and +delete+ methods to constrain a route to a particular verb. You can use the +match+ method with the +:via+ option to match multiple verbs at once: <ruby> -match 'photos/show' => 'photos#show', :via => :get +match 'photos' => 'photos#show', :via => [:get, :post] </ruby> -There is a shorthand version of this as well: +You can match all verbs to a particular route using +:via => :all+: <ruby> -get 'photos/show' +match 'photos' => 'photos#show', :via => :all </ruby> -You can also permit more than one verb to a single route: - -<ruby> -match 'photos/show' => 'photos#show', :via => [:get, :post] -</ruby> +You should avoid routing all verbs to an action unless you have a good reason to, as routing both +GET+ requests and +POST+ requests to a single action has security implications. h4. Segment Constraints You can use the +:constraints+ option to enforce a format for a dynamic segment: <ruby> -match 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ } +get 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ } </ruby> This route would match paths such as +/photos/A12345+. You can more succinctly express the same route this way: <ruby> -match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ +get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ </ruby> +:constraints+ takes regular expressions with the restriction that regexp anchors can't be used. For example, the following route will not work: <ruby> -match '/:id' => 'posts#show', :constraints => {:id => /^\d/} +get '/:id' => 'posts#show', :constraints => {:id => /^\d/} </ruby> However, note that you don't need to use anchors because all routes are anchored at the start. @@ -490,8 +486,8 @@ However, note that you don't need to use anchors because all routes are anchored For example, the following routes would allow for +posts+ with +to_param+ values like +1-hello-world+ that always begin with a number and +users+ with +to_param+ values like +david+ that never begin with a number to share the root namespace: <ruby> -match '/:id' => 'posts#show', :constraints => { :id => /\d.+/ } -match '/:username' => 'users#show' +get '/:id' => 'posts#show', :constraints => { :id => /\d.+/ } +get '/:username' => 'users#show' </ruby> h4. Request-Based Constraints @@ -501,7 +497,7 @@ You can also constrain a route based on any method on the <a href="action_contro You specify a request-based constraint the same way that you specify a segment constraint: <ruby> -match "photos", :constraints => {:subdomain => "admin"} +get "photos", :constraints => {:subdomain => "admin"} </ruby> You can also specify constraints in a block form: @@ -530,7 +526,7 @@ class BlacklistConstraint end TwitterClone::Application.routes.draw do - match "*path" => "blacklist#index", + get "*path" => "blacklist#index", :constraints => BlacklistConstraint.new end </ruby> @@ -539,7 +535,7 @@ You can also specify constraints as a lambda: <ruby> TwitterClone::Application.routes.draw do - match "*path" => "blacklist#index", + get "*path" => "blacklist#index", :constraints => lambda { |request| Blacklist.retrieve_ips.include?(request.remote_ip) } end </ruby> @@ -551,7 +547,7 @@ h4. Route Globbing Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example <ruby> -match 'photos/*other' => 'photos#unknown' +get 'photos/*other' => 'photos#unknown' </ruby> This route would match +photos/12+ or +/photos/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+. @@ -559,7 +555,7 @@ This route would match +photos/12+ or +/photos/long/path/to/12+, setting +params Wildcard segments can occur anywhere in a route. For example, <ruby> -match 'books/*section/:title' => 'books#show' +get 'books/*section/:title' => 'books#show' </ruby> would match +books/some/section/last-words-a-memoir+ with +params[:section]+ equals +"some/section"+, and +params[:title]+ equals +"last-words-a-memoir"+. @@ -567,7 +563,7 @@ would match +books/some/section/last-words-a-memoir+ with +params[:section]+ equ Technically a route can have even more than one wildcard segment. The matcher assigns segments to parameters in an intuitive way. For example, <ruby> -match '*a/foo/*b' => 'test#index' +get '*a/foo/*b' => 'test#index' </ruby> would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +params[:b]+ equals +"bar/baz"+. @@ -575,19 +571,19 @@ would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +par NOTE: Starting from Rails 3.1, wildcard routes will always match the optional format segment by default. For example if you have this route: <ruby> -match '*pages' => 'pages#show' +get '*pages' => 'pages#show' </ruby> NOTE: By requesting +"/foo/bar.json"+, your +params[:pages]+ will be equals to +"foo/bar"+ with the request format of JSON. If you want the old 3.0.x behavior back, you could supply +:format => false+ like this: <ruby> -match '*pages' => 'pages#show', :format => false +get '*pages' => 'pages#show', :format => false </ruby> NOTE: If you want to make the format segment mandatory, so it cannot be omitted, you can supply +:format => true+ like this: <ruby> -match '*pages' => 'pages#show', :format => true +get '*pages' => 'pages#show', :format => true </ruby> h4. Redirection @@ -595,20 +591,20 @@ h4. Redirection You can redirect any path to another path using the +redirect+ helper in your router: <ruby> -match "/stories" => redirect("/posts") +get "/stories" => redirect("/posts") </ruby> You can also reuse dynamic segments from the match in the path to redirect to: <ruby> -match "/stories/:name" => redirect("/posts/%{name}") +get "/stories/:name" => redirect("/posts/%{name}") </ruby> You can also provide a block to redirect, which receives the params and (optionally) the request object: <ruby> -match "/stories/:name" => redirect {|params| "/posts/#{params[:name].pluralize}" } -match "/stories" => redirect {|p, req| "/posts/#{req.subdomain}" } +get "/stories/:name" => redirect {|params| "/posts/#{params[:name].pluralize}" } +get "/stories" => redirect {|p, req| "/posts/#{req.subdomain}" } </ruby> Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible. @@ -620,10 +616,10 @@ h4. Routing to Rack Applications Instead of a String, like +"posts#index"+, which corresponds to the +index+ action in the +PostsController+, you can specify any <a href="rails_on_rack.html">Rack application</a> as the endpoint for a matcher. <ruby> -match "/application.js" => Sprockets +match "/application.js" => Sprockets, :via => :all </ruby> -As long as +Sprockets+ responds to +call+ and returns a <tt>[status, headers, body]</tt>, the router won't know the difference between the Rack application and an action. +As long as +Sprockets+ responds to +call+ and returns a <tt>[status, headers, body]</tt>, the router won't know the difference between the Rack application and an action. This is an appropriate use of +:via => :all+, as you will want to allow your Rack application to handle all verbs as it considers appropriate. NOTE: For the curious, +"posts#index"+ actually expands out to +PostsController.action(:index)+, which returns a valid Rack application. @@ -638,6 +634,8 @@ root 'pages#main' # shortcut for the above You should put the +root+ route at the top of the file, because it is the most popular route and should be matched first. You also need to delete the +public/index.html+ file for the root route to take effect. +NOTE: The +root+ route only routes +GET+ requests to the action. + h3. Customizing Resourceful Routes While the default routes and helpers generated by +resources :posts+ will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers. @@ -831,6 +829,24 @@ end This will create routing helpers such as +magazine_periodical_ads_url+ and +edit_magazine_periodical_ad_path+. +h3. Breaking Up a Large Route File + +If you have a large route file that you would like to break up into multiple files, you can use the +#draw+ method in your router: + +<ruby> +draw :admin +</ruby> + +Then, create a file called +config/routes/admin.rb+. Name the file the same as the symbol passed to the +draw+ method). You can then use the normal routing DSL inside that file: + +<ruby> +# in config/routes/admin.rb + +namespace :admin do + resources :posts +end +</ruby> + h3. Inspecting and Testing Routes Rails offers facilities for inspecting and testing your routes. diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index 945063e55c..59c3c56e59 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -22,6 +22,7 @@ end module Rails autoload :Info, 'rails/info' autoload :InfoController, 'rails/info_controller' + autoload :Queueing, 'rails/queueing' class << self def application @@ -37,6 +38,25 @@ module Rails application.config end + # Rails.queue is the application's queue. You can push a job onto + # the queue by: + # + # Rails.queue.push job + # + # A job is an object that responds to +run+. Queue consumers will + # pop jobs off of the queue and invoke the queue's +run+ method. + # + # Note that depending on your queue implementation, jobs may not + # be executed in the same process as they were created in, and + # are never executed in the same thread as they were created in. + # + # If necessary, a queue implementation may need to serialize your + # job for distribution to another process. The documentation of + # your queue will specify the requirements for that serialization. + def queue + application.queue + end + def initialize! application.initialize! end diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index b063e7681d..c7b19c964a 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -69,6 +69,7 @@ module Rails attr_accessor :assets, :sandbox alias_method :sandbox?, :sandbox attr_reader :reloaders + attr_writer :queue delegate :default_url_options, :default_url_options=, :to => :routes @@ -199,6 +200,14 @@ module Rails @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) end + def queue #:nodoc: + @queue ||= build_queue + end + + def build_queue # :nodoc: + config.queue.new + end + def to_app self end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 37297f0496..25bb680f69 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -11,7 +11,7 @@ module Rails :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, :preload_frameworks, :railties_order, :relative_url_root, :secret_token, :serve_static_assets, :ssl_options, :static_cache_control, :session_options, - :time_zone, :reload_classes_only_on_change, :use_schema_cache_dump + :time_zone, :reload_classes_only_on_change, :use_schema_cache_dump, :queue attr_writer :log_level attr_reader :encoding @@ -43,6 +43,7 @@ module Rails @autoflush_log = true @log_formatter = ActiveSupport::Logger::SimpleFormatter.new @use_schema_cache_dump = true + @queue = Rails::Queueing::Queue @assets = ActiveSupport::OrderedOptions.new @assets.enabled = false diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 7da495211d..6a24e01f29 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -22,7 +22,7 @@ module Rails initializer :add_builtin_route do |app| if Rails.env.development? app.routes.append do - match '/rails/info/properties' => "rails/info#properties" + get '/rails/info/properties' => "rails/info#properties" end end end @@ -93,6 +93,13 @@ module Rails ActiveSupport::Dependencies.unhook! end end + + initializer :activate_queue_consumer do |app| + if config.queue == Rails::Queueing::Queue + consumer = Rails::Queueing::ThreadedConsumer.start(app.queue) + at_exit { consumer.shutdown } + end + end end end end diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb index 6f9a200aa9..19f616ec50 100644 --- a/railties/lib/rails/application/routes_reloader.rb +++ b/railties/lib/rails/application/routes_reloader.rb @@ -3,12 +3,13 @@ require "active_support/core_ext/module/delegation" module Rails class Application class RoutesReloader - attr_reader :route_sets, :paths + attr_reader :route_sets, :paths, :external_routes delegate :execute_if_updated, :execute, :updated?, :to => :updater def initialize - @paths = [] - @route_sets = [] + @paths = [] + @route_sets = [] + @external_routes = [] end def reload! @@ -23,7 +24,11 @@ module Rails def updater @updater ||= begin - updater = ActiveSupport::FileUpdateChecker.new(paths) { reload! } + dirs = @external_routes.each_with_object({}) do |dir, hash| + hash[dir.to_s] = %w(rb) + end + + updater = ActiveSupport::FileUpdateChecker.new(paths, dirs) { reload! } updater.execute updater end diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index d7c9e820dc..cd6a03fe51 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -27,7 +27,7 @@ module Rails opt.on("-e", "--environment=name", String, "Specifies the environment to run this console under (test/development/production).", "Default: development") { |v| options[:environment] = v.strip } - opt.on("--debugger", 'Enable ruby-debugging for the console.') { |v| options[:debugger] = v } + opt.on("--debugger", 'Enable the debugger.') { |v| options[:debugger] = v } opt.parse!(arguments) end @@ -73,10 +73,10 @@ module Rails def require_debugger begin - require 'ruby-debug' + require 'debugger' puts "=> Debugger enabled" rescue Exception - puts "You need to install ruby-debug19 to run the console in debugging mode. With gems, use 'gem install ruby-debug19'" + puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again." exit end end diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index e8cc5d9e3b..2802981e7a 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -9,7 +9,6 @@ if ARGV.first.nil? end ARGV.clone.options do |opts| - script_name = File.basename($0) opts.banner = "Usage: runner [options] ('Some.ruby(code)' or a filename)" opts.separator "" diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 721a47a974..4c4caad69f 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -17,7 +17,7 @@ module Rails opts.on("-c", "--config=file", String, "Use custom rackup configuration file") { |v| options[:config] = v } opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:daemonize] = true } - opts.on("-u", "--debugger", "Enable ruby-debugging for the server.") { options[:debugger] = true } + opts.on("-u", "--debugger", "Enable the debugger") { options[:debugger] = true } opts.on("-e", "--environment=name", String, "Specifies the environment to run this server under (test/development/production).", "Default: development") { |v| options[:environment] = v } diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index d8ca6cbd21..493eacdc5a 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -16,7 +16,7 @@ module Rails # # config.middleware.use Magical::Unicorns # - # This will put the +Magical::Unicorns+ middleware on the end of the stack. + # This will put the <tt>Magical::Unicorns</tt> middleware on the end of the stack. # You can use +insert_before+ if you wish to add a middleware before another: # # config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 131d6e5711..d4f632702c 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -148,7 +148,7 @@ module Rails # # # ENGINE/config/routes.rb # MyEngine::Engine.routes.draw do - # match "/" => "posts#index" + # get "/" => "posts#index" # end # # == Mount priority @@ -158,7 +158,7 @@ module Rails # # MyRailsApp::Application.routes.draw do # mount MyEngine::Engine => "/blog" - # match "/blog/omg" => "main#omg" + # get "/blog/omg" => "main#omg" # end # # +MyEngine+ is mounted at <tt>/blog</tt>, and <tt>/blog/omg</tt> points to application's @@ -167,7 +167,7 @@ module Rails # It's much better to swap that: # # MyRailsApp::Application.routes.draw do - # match "/blog/omg" => "main#omg" + # get "/blog/omg" => "main#omg" # mount MyEngine::Engine => "/blog" # end # @@ -256,7 +256,7 @@ module Rails # # config/routes.rb # MyApplication::Application.routes.draw do # mount MyEngine::Engine => "/my_engine", :as => "my_engine" - # match "/foo" => "foo#index" + # get "/foo" => "foo#index" # end # # Now, you can use the <tt>my_engine</tt> helper inside your application: @@ -486,7 +486,10 @@ module Rails end def routes - @routes ||= ActionDispatch::Routing::RouteSet.new + @routes ||= ActionDispatch::Routing::RouteSet.new.tap do |routes| + routes.draw_paths.concat paths["config/routes"].paths + end + @routes.append(&Proc.new) if block_given? @routes end @@ -516,7 +519,7 @@ module Rails # # Blog::Engine.load_seed def load_seed - seed_file = paths["db/seeds"].existent.first + seed_file = paths["db/seeds.rb"].existent.first load(seed_file) if seed_file end @@ -544,11 +547,13 @@ module Rails end initializer :add_routing_paths do |app| - paths = self.paths["config/routes"].existent + paths = self.paths["config/routes.rb"].existent + external_paths = self.paths["config/routes"].paths if routes? || paths.any? app.routes_reloader.paths.unshift(*paths) app.routes_reloader.route_sets << routes + app.routes_reloader.external_routes.unshift(*external_paths) end end @@ -616,7 +621,7 @@ module Rails end def routes? - defined?(@routes) + defined?(@routes) && @routes end def has_migrations? diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index d7405cb519..d3b42021fc 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -52,10 +52,11 @@ module Rails paths.add "config/environments", :glob => "#{Rails.env}.rb" paths.add "config/initializers", :glob => "**/*.rb" paths.add "config/locales", :glob => "*.{rb,yml}" - paths.add "config/routes", :with => "config/routes.rb" + paths.add "config/routes.rb" + paths.add "config/routes", :glob => "**/*.rb" paths.add "db" paths.add "db/migrate" - paths.add "db/seeds", :with => "db/seeds.rb" + paths.add "db/seeds.rb" paths.add "vendor", :load_path => true paths.add "vendor/assets", :glob => "*" paths diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index b9c1b01f54..55642f8140 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -52,6 +52,7 @@ module Rails :orm => false, :performance_tool => nil, :resource_controller => :controller, + :resource_route => true, :scaffold_controller => :scaffold_controller, :stylesheets => true, :stylesheet_engine => :css, @@ -172,6 +173,7 @@ module Rails [ "rails", + "resource_route", "#{orm}:migration", "#{orm}:model", "#{orm}:observer", diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index bb2a9fcf22..b677e974c8 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -49,6 +49,9 @@ module Rails class_option :skip_javascript, :type => :boolean, :aliases => "-J", :default => false, :desc => "Skip JavaScript files" + class_option :skip_index_html, :type => :boolean, :aliases => "-I", :default => false, + :desc => "Skip public/index.html and app/assets/images/rails.png files" + class_option :dev, :type => :boolean, :default => false, :desc => "Setup the #{name} with Gemfile pointing to your Rails checkout" @@ -120,7 +123,7 @@ module Rails end def database_gemfile_entry - options[:skip_active_record] ? "" : "gem '#{gem_for_database}'\n" + options[:skip_active_record] ? "" : "gem '#{gem_for_database}'" end def include_all_railties? @@ -137,12 +140,14 @@ module Rails gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}' gem 'journey', :git => 'https://github.com/rails/journey.git' gem 'arel', :git => 'https://github.com/rails/arel.git' + gem 'active_record_deprecated_finders', :git => 'git://github.com/rails/active_record_deprecated_finders.git' GEMFILE elsif options.edge? <<-GEMFILE.strip_heredoc gem 'rails', :git => 'https://github.com/rails/rails.git' gem 'journey', :git => 'https://github.com/rails/journey.git' gem 'arel', :git => 'https://github.com/rails/arel.git' + gem 'active_record_deprecated_finders', :git => 'git://github.com/rails/active_record_deprecated_finders.git' GEMFILE else <<-GEMFILE.strip_heredoc diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb index 303331a4f0..d78d97b2b4 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb @@ -1,23 +1,27 @@ <h1>Listing <%= plural_table_name %></h1> <table> - <tr> -<% attributes.each do |attribute| -%> - <th><%= attribute.human_name %></th> -<% end -%> - <th></th> - <th></th> - <th></th> - </tr> + <thead> + <tr> + <% attributes.each do |attribute| -%> + <th><%= attribute.human_name %></th> + <% end -%> + <th></th> + <th></th> + <th></th> + </tr> + </thead> - <%%= content_tag_for(:tr, @<%= plural_table_name %>) do |<%= singular_table_name %>| %> -<% attributes.each do |attribute| -%> - <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td> -<% end -%> - <td><%%= link_to 'Show', <%= singular_table_name %> %></td> - <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td> - <td><%%= link_to 'Destroy', <%= singular_table_name %>, confirm: 'Are you sure?', method: :delete %></td> - <%% end %> + <tbody> + <%%= content_tag_for(:tr, @<%= plural_table_name %>) do |<%= singular_table_name %>| %> + <% attributes.each do |attribute| -%> + <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td> + <% end -%> + <td><%%= link_to 'Show', <%= singular_table_name %> %></td> + <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td> + <td><%%= link_to 'Destroy', <%= singular_table_name %>, confirm: 'Are you sure?', method: :delete %></td> + <%% end %> + </tbody> </table> <br /> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb index 67f263efbb..e15c963971 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb @@ -2,7 +2,7 @@ <% attributes.each do |attribute| -%> <p> - <b><%= attribute.human_name %>:</b> + <strong><%= attribute.human_name %>:</strong> <%%= @<%= singular_table_name %>.<%= attribute.name %> %> </p> diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 7dfc1aa599..25d0161e4c 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -1,6 +1,4 @@ require 'active_support/time' -require 'active_support/core_ext/object/inclusion' -require 'active_support/core_ext/object/blank' module Rails module Generators @@ -21,9 +19,20 @@ module Rails has_index, type = type, nil if INDEX_OPTIONS.include?(type) type, attr_options = *parse_type_and_options(type) + type = type.to_sym if type + + if type && reference?(type) + references_index = UNIQ_INDEX_OPTIONS.include?(has_index) ? { :unique => true } : true + attr_options[:index] = references_index + end + new(name, type, has_index, attr_options) end + def reference?(type) + [:references, :belongs_to].include? type + end + private # parse possible attribute options like :limit for string/text/binary/integer or :precision/:scale for decimals @@ -42,7 +51,7 @@ module Rails def initialize(name, type=nil, index_type=false, attr_options={}) @name = name - @type = (type.presence || :string).to_sym + @type = type || :string @has_index = INDEX_OPTIONS.include?(index_type) @has_uniq_index = UNIQ_INDEX_OPTIONS.include?(index_type) @attr_options = attr_options @@ -87,7 +96,7 @@ module Rails end def reference? - self.type.in?(:references, :belongs_to) + self.class.reference?(type) end def has_index? diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index c457b5fbbc..e85d1b8fa2 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -40,7 +40,7 @@ module Rails def indent(content, multiplier = 2) spaces = " " * multiplier - content = content.each_line.map {|line| "#{spaces}#{line}" }.join + content = content.each_line.map {|line| line.blank? ? line : "#{spaces}#{line}" }.join end def wrap_with_namespace(content) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index f0745df667..c06b0f8994 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -97,6 +97,11 @@ module Rails def public_directory directory "public", "public", :recursive => false + if options[:skip_index_html] + remove_file "public/index.html" + remove_file 'app/assets/images/rails.png' + git_keep 'app/assets/images' + end end def script diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 676662b9f5..bf47e66cc4 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -22,4 +22,4 @@ source 'https://rubygems.org' # gem 'capistrano', :group => :development # To use debugger -# gem 'ruby-debug19', :require => 'ruby-debug' +# gem 'debugger' diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README index d2014bd35f..b5d7b6436b 100644 --- a/railties/lib/rails/generators/rails/app/templates/README +++ b/railties/lib/rails/generators/rails/app/templates/README @@ -86,8 +86,8 @@ 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! You need to install ruby-debug19 to run the server in debugging -mode. With gems, use <tt>sudo gem install ruby-debug19</tt>. Example: +resume execution! You need to install the 'debugger' gem to run the server in debugging +mode. Add gem 'debugger' to your Gemfile and run <tt>bundle</tt> to install it. Example: class WeblogController < ActionController::Base def index diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index eb4dfa7c89..24bcec854c 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -35,4 +35,7 @@ # Expands the lines which load the assets. config.assets.debug = true <%- end -%> + + # In development, use an in-memory queue for queueing + config.queue = Rails::Queueing::Queue end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 7b2c86db24..072aa8355d 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -32,8 +32,8 @@ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true - # See everything in the log (default is :info). - # config.log_level = :debug + # Set to :debug to see everything in the log. + config.log_level = :info # Prepend all log lines with the following tags. # config.log_tags = [ :subdomain, :uuid ] @@ -76,4 +76,8 @@ # Use default logging formatter so that PID and timestamp are not suppressed config.log_formatter = ::Logger::Formatter.new + + # Default the production mode queue to an in-memory queue. You will probably + # want to replace this with an out-of-process queueing solution + config.queue = Rails::Queueing::Queue end diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index b725dd19f6..b27b88a3c6 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -33,4 +33,7 @@ # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr + + # Use the testing queue + config.queue = Rails::Queueing::TestQueue end diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index ea81748464..286e93c3cf 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -3,11 +3,11 @@ # first created -> highest priority. # Sample of regular route: - # match 'products/:id' => 'catalog#view' + # get 'products/:id' => 'catalog#view' # Keep in mind you can assign values other than :controller and :action # Sample of named route: - # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase + # get 'products/:id/purchase' => 'catalog#purchase', :as => :purchase # This route can be invoked with purchase_url(:id => product.id) # Sample resource route (maps HTTP verbs to controller actions automatically): @@ -51,8 +51,4 @@ # root :to => 'welcome#index' # See how all your routes lay out with "rake routes" - - # This is a legacy wild controller route that's not recommended for RESTful applications. - # Note: This route will make all actions in every controller accessible via GET requests. - # match ':controller(/:action(/:id))(.:format)' -end +end
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile index d316b00c43..9399c9cb77 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile @@ -20,4 +20,4 @@ gem "jquery-rails" <% end -%> # To use debugger -# gem 'ruby-debug19', :require => 'ruby-debug' +# gem 'debugger' diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb index 7c7b289d19..3a0586ee43 100644 --- a/railties/lib/rails/generators/rails/resource/resource_generator.rb +++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb @@ -14,16 +14,7 @@ module Rails class_option :actions, :type => :array, :banner => "ACTION ACTION", :default => [], :desc => "Actions for the resource controller" - class_option :http, :type => :boolean, :default => false, - :desc => "Generate resource with HTTP actions only" - - def add_resource_route - return if options[:actions].present? - route_config = regular_class_path.collect{ |namespace| "namespace :#{namespace} do " }.join(" ") - route_config << "resources :#{file_name.pluralize}" - route_config << " end" * regular_class_path.size - route route_config - end + hook_for :resource_route, :required => true end end end diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb new file mode 100644 index 0000000000..6a5d62803c --- /dev/null +++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb @@ -0,0 +1,13 @@ +module Rails + module Generators + class ResourceRouteGenerator < NamedBase + def add_resource_route + return if options[:actions].present? + route_config = regular_class_path.collect{ |namespace| "namespace :#{namespace} do " }.join(" ") + route_config << "resources :#{file_name.pluralize}" + route_config << " end" * regular_class_path.size + route route_config + end + end + end +end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb index 083eb49d65..0618b16984 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb @@ -10,9 +10,6 @@ module Rails class_option :orm, :banner => "NAME", :type => :string, :required => true, :desc => "ORM to generate the controller for" - class_option :http, :type => :boolean, :default => false, - :desc => "Generate controller with HTTP actions only" - def create_controller_files template "controller.rb", File.join('app/controllers', class_path, "#{controller_file_name}_controller.rb") end diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb index 2cb34fab7a..508e221c60 100644 --- a/railties/lib/rails/generators/test_case.rb +++ b/railties/lib/rails/generators/test_case.rb @@ -79,8 +79,8 @@ module Rails # # Finally, when a block is given, it yields the file content: # - # assert_file "app/controller/products_controller.rb" do |controller| - # assert_instance_method :index, content do |index| + # assert_file "app/controllers/products_controller.rb" do |controller| + # assert_instance_method :index, controller do |index| # assert_match(/Product\.all/, index) # end # end @@ -159,8 +159,8 @@ module Rails # Asserts the given method exists in the given content. When a block is given, # it yields the content of the method. # - # assert_file "app/controller/products_controller.rb" do |controller| - # assert_instance_method :index, content do |index| + # assert_file "app/controllers/products_controller.rb" do |controller| + # assert_instance_method :index, controller do |index| # assert_match(/Product\.all/, index) # end # end diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index 9e76587a0d..ca7fee3b6e 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -10,9 +10,6 @@ module TestUnit argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" - class_option :http, :type => :boolean, :default => false, - :desc => "Generate functional test with HTTP actions only" - def create_test_files template "functional_test.rb", File.join("test/functional", controller_class_path, "#{controller_file_name}_controller_test.rb") diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index dd1790299d..b787d91821 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -1,3 +1,5 @@ +require "pathname" + module Rails module Paths # This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system. @@ -8,7 +10,7 @@ module Rails # root.add "app/controllers", :eager_load => true # # The command above creates a new root object and add "app/controllers" as a path. - # This means we can get a +Rails::Paths::Path+ object back like below: + # This means we can get a <tt>Rails::Paths::Path</tt> object back like below: # # path = root["app/controllers"] # path.eager_load? # => true @@ -114,7 +116,7 @@ module Rails class Path include Enumerable - attr_reader :path + attr_reader :path, :root attr_accessor :glob def initialize(root, current, paths, options = {}) @@ -180,6 +182,14 @@ module Rails @paths end + def paths + raise "You need to set a path root" unless @root.path + + map do |p| + Pathname.new(@root.path).join(p) + end + end + # Expands all paths against the root and return all unique values. def expanded raise "You need to set a path root" unless @root.path diff --git a/railties/lib/rails/queueing.rb b/railties/lib/rails/queueing.rb new file mode 100644 index 0000000000..2e187b8555 --- /dev/null +++ b/railties/lib/rails/queueing.rb @@ -0,0 +1,69 @@ +require "thread" + +module Rails + module Queueing + # A Queue that simply inherits from STDLIB's Queue. Everytime this + # queue is used, Rails automatically sets up a ThreadedConsumer + # to consume it. + class Queue < ::Queue + end + + # In test mode, the Rails queue is backed by an Array so that assertions + # can be made about its contents. The test queue provides a +jobs+ + # method to make assertions about the queue's contents and a +drain+ + # method to drain the queue and run the jobs. + # + # Jobs are run in a separate thread to catch mistakes where code + # assumes that the job is run in the same thread. + class TestQueue < ::Queue + # Get a list of the jobs off this queue. This method may not be + # available on production queues. + def jobs + @que.dup + end + + # Drain the queue, running all jobs in a different thread. This method + # may not be available on production queues. + def drain + # run the jobs in a separate thread so assumptions of synchronous + # jobs are caught in test mode. + Thread.new { pop.run until empty? }.join + end + end + + # The threaded consumer will run jobs in a background thread in + # development mode or in a VM where running jobs on a thread in + # production mode makes sense. + # + # When the process exits, the consumer pushes a nil onto the + # queue and joins the thread, which will ensure that all jobs + # are executed before the process finally dies. + class ThreadedConsumer + def self.start(queue) + new(queue).start + end + + def initialize(queue) + @queue = queue + end + + def start + @thread = Thread.new do + while job = @queue.pop + begin + job.run + rescue Exception => e + Rails.logger.error "Job Error: #{e.message}\n#{e.backtrace.join("\n")}" + end + end + end + self + end + + def shutdown + @queue.push nil + @thread.join + end + end + end +end diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb index 5a78da1731..902361ce77 100644 --- a/railties/lib/rails/rack/debugger.rb +++ b/railties/lib/rails/rack/debugger.rb @@ -6,13 +6,13 @@ module Rails ARGV.clear # clear ARGV so that rails server options aren't passed to IRB - require 'ruby-debug' + require 'debugger' ::Debugger.start ::Debugger.settings[:autoeval] = true if ::Debugger.respond_to?(:settings) puts "=> Debugger enabled" rescue LoadError - puts "You need to install ruby-debug19 to run the server in debugging mode. With gems, use 'gem install ruby-debug19'" + puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again." exit end diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index 4cd60fdc39..a443c73962 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -30,8 +30,9 @@ class SourceAnnotationExtractor # Prints all annotations with tag +tag+ under the root directories +app+, +config+, +lib+, # +script+, and +test+ (recursively). Only filenames with extension - # +.builder+, +.rb+, and +.erb+ are taken into account. The +options+ - # hash is passed to each annotation's +to_s+. + # +.builder+, +.rb+, +.erb+, +.haml+, +.slim+, +.css+, +.scss+, +.js+, and + # +.coffee+ are taken into account. The +options+ hash is passed to each + # annotation's +to_s+. # # This class method is the single entry point for the rake tasks. def self.enumerate(tag, options={}) @@ -53,7 +54,8 @@ class SourceAnnotationExtractor # Returns a hash that maps filenames under +dir+ (recursively) to arrays # with their annotations. Only files with annotations are included, and only - # those with extension +.builder+, +.rb+, +.erb+, +.haml+, +.slim+ and +.coffee+ + # those with extension +.builder+, +.rb+, +.erb+, +.haml+, +.slim+, +.css+, + # +.scss+, +.js+, and +.coffee+ # are taken into account. def find_in(dir) results = {} @@ -65,6 +67,8 @@ class SourceAnnotationExtractor results.update(find_in(item)) elsif item =~ /\.(builder|rb|coffee)$/ results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/)) + elsif item =~ /\.(css|scss|js)$/ + results.update(extract_annotations_from(item, /\/\/\s*(#{tag}):?\s*(.*)$/)) elsif item =~ /\.erb$/ results.update(extract_annotations_from(item, /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/)) elsif item =~ /\.haml$/ diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index 2851ca4189..4ec49eee76 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -2,7 +2,7 @@ require 'rdoc/task' # Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise class RDocTaskWithoutDescriptions < RDoc::Task - include ::Rake::DSL + include ::Rake::DSL if defined?(::Rake::DSL) def define task rdoc_task_name diff --git a/railties/railties.gemspec b/railties/railties.gemspec index e84b1d0644..7067253279 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -21,7 +21,11 @@ Gem::Specification.new do |s| s.rdoc_options << '--exclude' << '.' s.add_dependency('rake', '>= 0.8.7') - s.add_dependency('thor', '~> 0.14.6') + + # The current API of the Thor gem (0.14) will remain stable at least until Thor 2.0. Because + # Thor is so heavily used by other gems, we will accept Thor's semver guarantee to reduce + # the possibility of conflicts. + s.add_dependency('thor', '>= 0.14.6', '< 2.0') s.add_dependency('rdoc', '~> 3.4') s.add_dependency('activesupport', version) s.add_dependency('actionpack', version) diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb index 1ac349a560..ecacb34cb2 100644 --- a/railties/test/application/asset_debugging_test.rb +++ b/railties/test/application/asset_debugging_test.rb @@ -15,7 +15,7 @@ module ApplicationTests app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do - match '/posts', :to => "posts#index" + get '/posts', :to => "posts#index" end RUBY diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 37981917e3..e23a19d69c 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -28,7 +28,7 @@ module ApplicationTests app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match '*path', :to => lambda { |env| [200, { "Content-Type" => "text/html" }, "Not an asset"] } + get '*path', :to => lambda { |env| [200, { "Content-Type" => "text/html" }, "Not an asset"] } end RUBY @@ -204,7 +204,7 @@ module ApplicationTests app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do - match '/posts', :to => "posts#index" + get '/posts', :to => "posts#index" end RUBY @@ -230,7 +230,7 @@ module ApplicationTests app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do - match '/posts', :to => "posts#index" + get '/posts', :to => "posts#index" end RUBY @@ -334,7 +334,7 @@ module ApplicationTests app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do - match '/omg', :to => "omg#index" + get '/omg', :to => "omg#index" end RUBY @@ -513,7 +513,7 @@ module ApplicationTests app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do - match '/posts', :to => "posts#index" + get '/posts', :to => "posts#index" end RUBY end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index ac5ac2b93e..252dd0e31a 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -41,6 +41,14 @@ module ApplicationTests FileUtils.rm_rf(new_app) if File.directory?(new_app) end + test "multiple queue construction is possible" do + require 'rails' + require "#{app_path}/config/environment" + mail_queue = Rails.application.build_queue + image_processing_queue = Rails.application.build_queue + assert_not_equal mail_queue, image_processing_queue + end + test "Rails.groups returns available groups" do require "rails" diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index a08e5b2374..c2d4a0f2c8 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -116,7 +116,7 @@ module ApplicationTests app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do - match "/:controller(/:action)" + get "/:controller(/:action)" end RUBY diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb index abb277dc1d..02d20bc150 100644 --- a/railties/test/application/initializers/i18n_test.rb +++ b/railties/test/application/initializers/i18n_test.rb @@ -85,7 +85,7 @@ en: app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match '/i18n', :to => lambda { |env| [200, {}, [Foo.instance_variable_get('@foo')]] } + get '/i18n', :to => lambda { |env| [200, {}, [Foo.instance_variable_get('@foo')]] } end RUBY @@ -109,7 +109,7 @@ en: app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] } + get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] } end RUBY diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index 92951e1676..e0286502f3 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -77,8 +77,8 @@ class LoadingTest < ActiveSupport::TestCase app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match '/load', :to => lambda { |env| [200, {}, Post.all] } - match '/unload', :to => lambda { |env| [200, {}, []] } + get '/load', :to => lambda { |env| [200, {}, Post.all] } + get '/unload', :to => lambda { |env| [200, {}, []] } end RUBY @@ -107,7 +107,7 @@ class LoadingTest < ActiveSupport::TestCase app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match '/c', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] } + get '/c', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] } end RUBY @@ -146,7 +146,7 @@ class LoadingTest < ActiveSupport::TestCase app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match '/c', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] } + get '/c', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] } end RUBY @@ -182,7 +182,7 @@ class LoadingTest < ActiveSupport::TestCase app_file 'config/routes.rb', <<-RUBY $counter = 0 AppTemplate::Application.routes.draw do - match '/c', :to => lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } + get '/c', :to => lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } end RUBY @@ -213,8 +213,8 @@ class LoadingTest < ActiveSupport::TestCase app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match '/title', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.title]] } - match '/body', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.body]] } + get '/title', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.title]] } + get '/body', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.body]] } end RUBY @@ -272,7 +272,7 @@ class LoadingTest < ActiveSupport::TestCase app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do - match "/:controller(/:action)" + get "/:controller(/:action)" end RUBY diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb index 561b020707..54b18542c2 100644 --- a/railties/test/application/middleware/cache_test.rb +++ b/railties/test/application/middleware/cache_test.rb @@ -46,7 +46,7 @@ module ApplicationTests app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match ':controller(/:action)' + get ':controller(/:action)' end RUBY end diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb index a80898092d..d1a614e181 100644 --- a/railties/test/application/middleware/exceptions_test.rb +++ b/railties/test/application/middleware/exceptions_test.rb @@ -17,31 +17,32 @@ module ApplicationTests end test "show exceptions middleware filter backtrace before logging" do - my_middleware = Struct.new(:app) do - def call(env) - raise "Failure" + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + raise 'oops' + end end - end - - app.config.middleware.use my_middleware + RUBY - stringio = StringIO.new - Rails.logger = Logger.new(stringio) + get "/foo" + assert_equal 500, last_response.status - get "/" - assert_no_match(/action_dispatch/, stringio.string) + log = File.read(Rails.application.config.paths["log"].first) + assert_no_match(/action_dispatch/, log, log) + assert_match(/oops/, log, log) end test "renders active record exceptions as 404" do - my_middleware = Struct.new(:app) do - def call(env) - raise ActiveRecord::RecordNotFound + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + raise ActiveRecord::RecordNotFound + end end - end - - app.config.middleware.use my_middleware + RUBY - get "/" + get "/foo" assert_equal 404, last_response.status end @@ -104,7 +105,7 @@ module ApplicationTests app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match ':controller(/:action)' + post ':controller(/:action)' end RUBY diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb index f4e77ee244..07134cc935 100644 --- a/railties/test/application/middleware/session_test.rb +++ b/railties/test/application/middleware/session_test.rb @@ -26,5 +26,25 @@ module ApplicationTests require "#{app_path}/config/environment" assert app.config.session_options[:secure], "Expected session to be marked as secure" end + + test "session is not loaded if it's not used" do + make_basic_app + + class ::OmgController < ActionController::Base + def index + if params[:flash] + flash[:notice] = "notice" + end + + render :nothing => true + end + end + + get "/?flash=true" + get "/" + + assert last_request.env["HTTP_COOKIE"] + assert !last_response.headers["Set-Cookie"] + end end end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index fc5fb60174..ba747bc633 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -148,6 +148,13 @@ module ApplicationTests assert_equal "Rack::Config", middleware.first end + test "can't change middleware after it's built" do + boot! + assert_raise RuntimeError do + app.config.middleware.use Rack::Config + end + end + # ConditionalGet + Etag test "conditional get + etag middlewares handle http caching based on body" do make_basic_app diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index 4029984ce9..e0893f53be 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -50,6 +50,8 @@ module ApplicationTests assert_path @paths["config/locales"], "config/locales/en.yml" assert_path @paths["config/environment"], "config/environment.rb" assert_path @paths["config/environments"], "config/environments/development.rb" + assert_path @paths["config/routes.rb"], "config/routes.rb" + assert_path @paths["config/routes"], "config/routes" assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first end diff --git a/railties/test/application/queue_test.rb b/railties/test/application/queue_test.rb new file mode 100644 index 0000000000..ec809d7cc1 --- /dev/null +++ b/railties/test/application/queue_test.rb @@ -0,0 +1,114 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class GeneratorsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + def app_const + @app_const ||= Class.new(Rails::Application) + end + + test "the queue is a TestQueue in test mode" do + app("test") + assert_kind_of Rails::Queueing::TestQueue, Rails.application.queue + assert_kind_of Rails::Queueing::TestQueue, Rails.queue + end + + test "the queue is a Queue in development mode" do + app("development") + assert_kind_of Rails::Queueing::Queue, Rails.application.queue + assert_kind_of Rails::Queueing::Queue, Rails.queue + end + + test "in development mode, an enqueued job will be processed in a separate thread" do + app("development") + current = Thread.current + + job = Struct.new(:origin, :target).new(Thread.current) + def job.run + self.target = Thread.current + end + + Rails.queue.push job + sleep 0.1 + + assert job.target, "The job was run" + assert_not_equal job.origin, job.target + end + + test "in test mode, explicitly draining the queue will process it in a separate thread" do + app("test") + current = Thread.current + + job = Struct.new(:origin, :target).new(Thread.current) + def job.run + self.target = Thread.current + end + + Rails.queue.push job + Rails.queue.drain + + assert job.target, "The job was run" + assert_not_equal job.origin, job.target + end + + test "in test mode, the queue can be observed" do + app("test") + + job = Class.new(Struct.new(:id)) do + def run + end + end + + jobs = (1..10).map do |id| + job.new(id) + end + + jobs.each do |job| + Rails.queue.push job + end + + assert_equal jobs, Rails.queue.jobs + end + + test "a custom queue implementation can be provided" do + add_to_env_config "production", <<-RUBY + require "my_queue" + config.queue = MyQueue + RUBY + + app_file "lib/my_queue.rb", <<-RUBY + class MyQueue + def push(job) + job.run + end + end + RUBY + + app("production") + + assert_kind_of MyQueue, Rails.queue + + job = Class.new(Struct.new(:id, :ran)) do + def run + self.ran = true + end + end + + job1 = job.new(1) + Rails.queue.push job1 + + assert_equal true, job1.ran + end + end +end diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb index 04abf9e3a1..d069703d30 100644 --- a/railties/test/application/rake/notes_test.rb +++ b/railties/test/application/rake/notes_test.rb @@ -17,6 +17,9 @@ module ApplicationTests app_file "app/views/home/index.html.haml", "-# TODO: note in haml" app_file "app/views/home/index.html.slim", "/ TODO: note in slim" app_file "app/assets/javascripts/application.js.coffee", "# TODO: note in coffee" + app_file "app/assets/javascripts/application.js", "// TODO: note in js" + app_file "app/assets/stylesheets/application.css", "// TODO: note in css" + app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss" app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby" boot_rails @@ -35,8 +38,11 @@ module ApplicationTests assert_match /note in slim/, output assert_match /note in ruby/, output assert_match /note in coffee/, output + assert_match /note in js/, output + assert_match /note in css/, output + assert_match /note in scss/, output - assert_equal 5, lines.size + assert_equal 8, lines.size lines.each do |line| assert_equal 4, line[0].size diff --git a/railties/test/application/route_inspect_test.rb b/railties/test/application/route_inspect_test.rb index 6393cfff4b..453ba8196c 100644 --- a/railties/test/application/route_inspect_test.rb +++ b/railties/test/application/route_inspect_test.rb @@ -13,6 +13,7 @@ module ApplicationTests app.config.assets = ActiveSupport::OrderedOptions.new app.config.assets.prefix = '/sprockets' Rails.stubs(:application).returns(app) + Rails.stubs(:env).returns("development") end def test_displaying_routes_for_engines @@ -78,47 +79,47 @@ module ApplicationTests root :to => 'pages#main' end output = @inspector.format @set.routes - assert_equal ["root / pages#main"], output + assert_equal ["root GET / pages#main"], output end def test_inspect_routes_shows_dynamic_action_route @set.draw do - match 'api/:action' => 'api' + get 'api/:action' => 'api' end output = @inspector.format @set.routes - assert_equal [" /api/:action(.:format) api#:action"], output + assert_equal [" GET /api/:action(.:format) api#:action"], output end def test_inspect_routes_shows_controller_and_action_only_route @set.draw do - match ':controller/:action' + get ':controller/:action' end output = @inspector.format @set.routes - assert_equal [" /:controller/:action(.:format) :controller#:action"], output + assert_equal [" GET /:controller/:action(.:format) :controller#:action"], output end def test_inspect_routes_shows_controller_and_action_route_with_constraints @set.draw do - match ':controller(/:action(/:id))', :id => /\d+/ + get ':controller(/:action(/:id))', :id => /\d+/ end output = @inspector.format @set.routes - assert_equal [" /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}"], output + assert_equal [" GET /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}"], output end def test_rake_routes_shows_route_with_defaults @set.draw do - match 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'} + get 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'} end output = @inspector.format @set.routes - assert_equal [%Q[ /photos/:id(.:format) photos#show {:format=>"jpg"}]], output + assert_equal [%Q[ GET /photos/:id(.:format) photos#show {:format=>"jpg"}]], output end def test_rake_routes_shows_route_with_constraints @set.draw do - match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ + get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ end output = @inspector.format @set.routes - assert_equal [" /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"], output + assert_equal [" GET /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"], output end class RackApp @@ -128,10 +129,10 @@ module ApplicationTests def test_rake_routes_shows_route_with_rack_app @set.draw do - match 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/ + get 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/ end output = @inspector.format @set.routes - assert_equal [" /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"], output + assert_equal [" GET /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"], output end def test_rake_routes_shows_route_with_rack_app_nested_with_dynamic_constraints @@ -153,7 +154,7 @@ module ApplicationTests def test_rake_routes_dont_show_app_mounted_in_assets_prefix @set.draw do - match '/sprockets' => RackApp + get '/sprockets' => RackApp end output = @inspector.format @set.routes assert_no_match(/RackApp/, output.first) diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index 28ce3beea9..977a5fc7e8 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -53,7 +53,7 @@ module ApplicationTests app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match ':controller(/:action)' + get ':controller(/:action)' end RUBY @@ -94,7 +94,7 @@ module ApplicationTests app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match ':controller(/:action)' + get ':controller(/:action)' end RUBY @@ -126,8 +126,8 @@ module ApplicationTests app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match 'admin/foo', :to => 'admin/foo#index' - match 'foo', :to => 'foo#index' + get 'admin/foo', :to => 'admin/foo#index' + get 'foo', :to => 'foo#index' end RUBY @@ -141,13 +141,13 @@ module ApplicationTests test "routes appending blocks" do app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match ':controller/:action' + get ':controller/:action' end RUBY add_to_config <<-R routes.append do - match '/win' => lambda { |e| [200, {'Content-Type'=>'text/plain'}, ['WIN']] } + get '/win' => lambda { |e| [200, {'Content-Type'=>'text/plain'}, ['WIN']] } end R @@ -158,7 +158,7 @@ module ApplicationTests app_file 'config/routes.rb', <<-R AppTemplate::Application.routes.draw do - match 'lol' => 'hello#index' + get 'lol' => 'hello#index' end R @@ -166,7 +166,90 @@ module ApplicationTests assert_equal 'WIN', last_response.body end + test "routes drawing from config/routes" do + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + draw :external + end + RUBY + + app_file 'config/routes/external.rb', <<-RUBY + get ':controller/:action' + RUBY + + controller :success, <<-RUBY + class SuccessController < ActionController::Base + def index + render :text => "success!" + end + end + RUBY + + app 'development' + get '/success/index' + assert_equal 'success!', last_response.body + end + {"development" => "baz", "production" => "bar"}.each do |mode, expected| + test "reloads routes when external configuration is changed in #{mode}" do + controller :foo, <<-RUBY + class FooController < ApplicationController + def bar + render :text => "bar" + end + + def baz + render :text => "baz" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + draw :external + end + RUBY + + app_file 'config/routes/external.rb', <<-RUBY + get 'foo', :to => 'foo#bar' + RUBY + + app(mode) + + get '/foo' + assert_equal 'bar', last_response.body + + app_file 'config/routes/external.rb', <<-RUBY + get 'foo', :to => 'foo#baz' + RUBY + + sleep 0.1 + + get '/foo' + assert_equal expected, last_response.body + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + draw :external + draw :other_external + end + RUBY + + app_file 'config/routes/other_external.rb', <<-RUBY + get 'win', :to => 'foo#baz' + RUBY + + sleep 0.1 + + get '/win' + + if mode == "development" + assert_equal expected, last_response.body + else + assert_equal 404, last_response.status + end + end + test "reloads routes when configuration is changed in #{mode}" do controller :foo, <<-RUBY class FooController < ApplicationController @@ -182,7 +265,7 @@ module ApplicationTests app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match 'foo', :to => 'foo#bar' + get 'foo', :to => 'foo#bar' end RUBY @@ -193,7 +276,7 @@ module ApplicationTests app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match 'foo', :to => 'foo#baz' + get 'foo', :to => 'foo#baz' end RUBY @@ -214,7 +297,7 @@ module ApplicationTests app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match 'foo', :to => ::InitializeRackApp + get 'foo', :to => ::InitializeRackApp end RUBY diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb index 85a8a15fcc..f7e60749a7 100644 --- a/railties/test/application/url_generation_test.rb +++ b/railties/test/application/url_generation_test.rb @@ -31,7 +31,7 @@ module ApplicationTests end MyApp.routes.draw do - match "/" => "omg#index", :as => :omg + get "/" => "omg#index", :as => :omg end require 'rack/test' diff --git a/railties/test/engine_test.rb b/railties/test/engine_test.rb new file mode 100644 index 0000000000..68406dce4c --- /dev/null +++ b/railties/test/engine_test.rb @@ -0,0 +1,24 @@ +require 'abstract_unit' + +class EngineTest < ActiveSupport::TestCase + it "reports routes as available only if they're actually present" do + engine = Class.new(Rails::Engine) do + def initialize(*args) + @routes = nil + super + end + end + + assert !engine.routes? + end + + it "does not add more paths to routes on each call" do + engine = Class.new(Rails::Engine) + + engine.routes + length = engine.routes.draw_paths.length + + engine.routes + assert_equal length, engine.routes.draw_paths.length + end +end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 40cb876b34..94983c504c 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -250,6 +250,13 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_generator_if_skip_index_html_is_given + run_generator [destination_root, "--skip-index-html"] + assert_no_file "public/index.html" + assert_no_file "app/assets/images/rails.png" + assert_file "app/assets/images/.gitkeep" + end + def test_creation_of_a_test_directory run_generator assert_file 'test' @@ -294,10 +301,10 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_inclusion_of_ruby_debug19 + def test_inclusion_of_debugger run_generator assert_file "Gemfile" do |contents| - assert_match(/gem 'ruby-debug19', :require => 'ruby-debug'/, contents) + assert_match(/gem 'debugger'/, contents) end end diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index e8d933935d..fd3b8c8a17 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -283,7 +283,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_migration "db/migrate/create_accounts.rb" do |m| assert_method :change, m do |up| - assert_match(/add_index/, up) + assert_match(/index: true/, up) end end end @@ -293,7 +293,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_migration "db/migrate/create_accounts.rb" do |m| assert_method :change, m do |up| - assert_match(/add_index/, up) + assert_match(/index: true/, up) end end end @@ -303,7 +303,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_migration "db/migrate/create_accounts.rb" do |m| assert_method :change, m do |up| - assert_no_match(/add_index/, up) + assert_no_match(/index: true/, up) end end end @@ -313,7 +313,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_migration "db/migrate/create_accounts.rb" do |m| assert_method :change, m do |up| - assert_no_match(/add_index/, up) + assert_no_match(/index: true/, up) end end end diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 5c63b13dce..6f00fc5d26 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -56,11 +56,20 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase run_generator assert_file "config/routes.rb", /get "account\/foo"/, /get "account\/bar"/ end -# + def test_invokes_default_template_engine_even_with_no_action run_generator ["account"] assert_file "app/views/test_app/account" end + + def test_namespaced_controller_dont_indent_blank_lines + run_generator + assert_file "app/controllers/test_app/account_controller.rb" do |content| + content.split("\n").each do |line| + assert_no_match line, /^\s+$/, "Don't indent blank lines" + end + end + end end class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 7123950add..9b456c64ef 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -14,10 +14,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/ assert_file "test/unit/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/ assert_file "test/fixtures/product_lines.yml" - assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product/ - assert_migration "db/migrate/create_product_lines.rb", /add_index :product_lines, :product_id/ - assert_migration "db/migrate/create_product_lines.rb", /references :user/ - assert_migration "db/migrate/create_product_lines.rb", /add_index :product_lines, :user_id/ + assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product, index: true/ + assert_migration "db/migrate/create_product_lines.rb", /references :user, index: true/ # Route assert_file "config/routes.rb" do |route| diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index ac4c2abfc8..f2dc0c71b1 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -8,7 +8,7 @@ # Rails booted up. require 'fileutils' -require 'rubygems' +require 'bundler/setup' require 'minitest/autorun' require 'active_support/test_case' @@ -18,7 +18,6 @@ RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..") # These files do not require any others and are needed # to run the tests require "#{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/testing/isolation" -require "#{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/testing/declarative" require "#{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/core_ext/kernel/reporting" module TestHelpers @@ -112,7 +111,7 @@ module TestHelpers routes = File.read("#{app_path}/config/routes.rb") if routes =~ /(\n\s*end\s*)\Z/ File.open("#{app_path}/config/routes.rb", 'w') do |f| - f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)'\n" + $1 + f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)', :via => :all\n" + $1 end end @@ -143,7 +142,7 @@ module TestHelpers app.initialize! app.routes.draw do - match "/" => "omg#index" + get "/" => "omg#index" end require 'rack/test' @@ -161,7 +160,7 @@ module TestHelpers app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do - match ':controller(/:action)' + get ':controller(/:action)' end RUBY end @@ -268,7 +267,6 @@ class ActiveSupport::TestCase include TestHelpers::Paths include TestHelpers::Rack include TestHelpers::Generation - extend ActiveSupport::Testing::Declarative end # Create a scope and build a fixture rails app diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index d334034e7d..aa04cad033 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -29,6 +29,7 @@ class PathsTest < ActiveSupport::TestCase test "creating a root level path" do @root.add "app" assert_equal ["/foo/bar/app"], @root["app"].to_a + assert_equal [Pathname.new("/foo/bar/app")], @root["app"].paths end test "creating a root level path with options" do @@ -191,6 +192,7 @@ class PathsTest < ActiveSupport::TestCase @root["app"] = "/app" @root["app"].glob = "*.rb" assert_equal "*.rb", @root["app"].glob + assert_equal [Pathname.new("/app")], @root["app"].paths end test "it should be possible to override a path's default glob without assignment" do diff --git a/railties/test/queueing/test_queue_test.rb b/railties/test/queueing/test_queue_test.rb new file mode 100644 index 0000000000..78c6c617fe --- /dev/null +++ b/railties/test/queueing/test_queue_test.rb @@ -0,0 +1,67 @@ +require 'abstract_unit' +require 'rails/queueing' + +class TestQueueTest < ActiveSupport::TestCase + class Job + def initialize(&block) + @block = block + end + + def run + @block.call if @block + end + end + + def setup + @queue = Rails::Queueing::TestQueue.new + end + + def test_drain_raises + @queue.push Job.new { raise } + assert_raises(RuntimeError) { @queue.drain } + end + + def test_jobs + @queue.push 1 + @queue.push 2 + assert_equal [1,2], @queue.jobs + end + + def test_contents + assert @queue.empty? + job = Job.new + @queue.push job + refute @queue.empty? + assert_equal job, @queue.pop + end + + def test_order + processed = [] + + job1 = Job.new { processed << 1 } + job2 = Job.new { processed << 2 } + + @queue.push job1 + @queue.push job2 + @queue.drain + + assert_equal [1,2], processed + end + + def test_drain + t = nil + ran = false + + job = Job.new do + ran = true + t = Thread.current + end + + @queue.push job + @queue.drain + + assert @queue.empty? + assert ran, "The job runs synchronously when the queue is drained" + assert_not_equal t, Thread.current + end +end diff --git a/railties/test/queueing/threaded_consumer_test.rb b/railties/test/queueing/threaded_consumer_test.rb new file mode 100644 index 0000000000..559de2a82d --- /dev/null +++ b/railties/test/queueing/threaded_consumer_test.rb @@ -0,0 +1,81 @@ +require 'abstract_unit' +require 'rails/queueing' + +class TestThreadConsumer < ActiveSupport::TestCase + class Job + attr_reader :id + def initialize(id, &block) + @id = id + @block = block + end + + def run + @block.call if @block + end + end + + def setup + @queue = Rails::Queueing::Queue.new + @consumer = Rails::Queueing::ThreadedConsumer.start(@queue) + end + + def teardown + @queue.push nil + end + + test "the jobs are executed" do + ran = false + + job = Job.new(1) do + ran = true + end + + @queue.push job + sleep 0.1 + assert_equal true, ran + end + + test "the jobs are not executed synchronously" do + ran = false + + job = Job.new(1) do + sleep 0.1 + ran = true + end + + @queue.push job + assert_equal false, ran + end + + test "shutting down the queue synchronously drains the jobs" do + ran = false + + job = Job.new(1) do + sleep 0.1 + ran = true + end + + @queue.push job + assert_equal false, ran + + @consumer.shutdown + + assert_equal true, ran + end + + test "log job that raises an exception" do + require "active_support/log_subscriber/test_helper" + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + Rails.logger = logger + + job = Job.new(1) do + raise "RuntimeError: Error!" + end + + @queue.push job + sleep 0.1 + + assert_equal 1, logger.logged(:error).size + assert_match(/Job Error: RuntimeError: Error!/, logger.logged(:error).last) + end +end diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index 8a9363fb80..f7a30a16d2 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -11,7 +11,7 @@ class InfoControllerTest < ActionController::TestCase def setup Rails.application.routes.draw do - match '/rails/info/properties' => "rails/info#properties" + get '/rails/info/properties' => "rails/info#properties" end @request.stubs(:local? => true) @controller.stubs(:consider_all_requests_local? => false) diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 5e93a8e783..9d9565e5f3 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -86,7 +86,6 @@ module RailtiesTest add_to_config "ActiveRecord::Base.timestamped_migrations = false" boot_rails - railties = Rails.application.railties.all.map(&:railtie_name) Dir.chdir(app_path) do output = `bundle exec rake bukkits:install:migrations` @@ -218,7 +217,7 @@ module RailtiesTest end Rails.application.routes.draw do - match "/sprokkit", :to => Sprokkit + get "/sprokkit", :to => Sprokkit end RUBY @@ -241,7 +240,7 @@ module RailtiesTest app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do - match 'foo', :to => 'foo#index' + get 'foo', :to => 'foo#index' end RUBY @@ -255,8 +254,8 @@ module RailtiesTest @plugin.write "config/routes.rb", <<-RUBY Rails.application.routes.draw do - match 'foo', :to => 'bar#index' - match 'bar', :to => 'bar#index' + get 'foo', :to => 'bar#index' + get 'bar', :to => 'bar#index' end RUBY @@ -336,7 +335,7 @@ YAML Rails.application.routes.draw do namespace :admin do namespace :foo do - match "bar", :to => "bar#index" + get "bar", :to => "bar#index" end end end @@ -491,7 +490,7 @@ YAML @plugin.write "config/routes.rb", <<-RUBY Bukkits::Engine.routes.draw do - match "/foo" => lambda { |env| [200, {'Content-Type' => 'text/html'}, ['foo']] } + get "/foo" => lambda { |env| [200, {'Content-Type' => 'text/html'}, ['foo']] } end RUBY @@ -570,18 +569,18 @@ YAML app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do - match "/bar" => "bar#index", :as => "bar" + get "/bar" => "bar#index", :as => "bar" mount Bukkits::Engine => "/bukkits", :as => "bukkits" end RUBY @plugin.write "config/routes.rb", <<-RUBY Bukkits::Engine.routes.draw do - match "/foo" => "foo#index", :as => "foo" - match "/foo/show" => "foo#show" - match "/from_app" => "foo#from_app" - match "/routes_helpers_in_view" => "foo#routes_helpers_in_view" - match "/polymorphic_path_without_namespace" => "foo#polymorphic_path_without_namespace" + get "/foo" => "foo#index", :as => "foo" + get "/foo/show" => "foo#show" + get "/from_app" => "foo#from_app" + get "/routes_helpers_in_view" => "foo#routes_helpers_in_view" + get "/polymorphic_path_without_namespace" => "foo#polymorphic_path_without_namespace" resources :posts end RUBY @@ -738,7 +737,7 @@ YAML @plugin.write "config/routes.rb", <<-RUBY Bukkits::Awesome::Engine.routes.draw do - match "/foo" => "foo#index" + get "/foo" => "foo#index" end RUBY @@ -1008,8 +1007,8 @@ YAML app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do - match "/foo" => "main#foo" - match "/bar" => "main#bar" + get "/foo" => "main#foo" + get "/bar" => "main#bar" end RUBY @@ -1080,7 +1079,7 @@ YAML app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do - match "/foo" => "main#foo" + get "/foo" => "main#foo" end RUBY diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index 2bb9df6b64..4c0fdee556 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -18,13 +18,13 @@ module ApplicationTests AppTemplate::Application.routes.draw do mount Weblog::Engine, :at => '/', :as => 'weblog' resources :posts - match "/engine_route" => "application_generating#engine_route" - match "/engine_route_in_view" => "application_generating#engine_route_in_view" - match "/weblog_engine_route" => "application_generating#weblog_engine_route" - match "/weblog_engine_route_in_view" => "application_generating#weblog_engine_route_in_view" - match "/url_for_engine_route" => "application_generating#url_for_engine_route" - match "/polymorphic_route" => "application_generating#polymorphic_route" - match "/application_polymorphic_path" => "application_generating#application_polymorphic_path" + get "/engine_route" => "application_generating#engine_route" + get "/engine_route_in_view" => "application_generating#engine_route_in_view" + get "/weblog_engine_route" => "application_generating#weblog_engine_route" + get "/weblog_engine_route_in_view" => "application_generating#weblog_engine_route_in_view" + get "/url_for_engine_route" => "application_generating#url_for_engine_route" + get "/polymorphic_route" => "application_generating#polymorphic_route" + get "/application_polymorphic_path" => "application_generating#application_polymorphic_path" scope "/:user", :user => "anonymous" do mount Blog::Engine => "/blog" end @@ -42,7 +42,7 @@ module ApplicationTests @simple_plugin.write "config/routes.rb", <<-RUBY Weblog::Engine.routes.draw do - match '/weblog' => "weblogs#index", :as => 'weblogs' + get '/weblog' => "weblogs#index", :as => 'weblogs' end RUBY @@ -86,9 +86,9 @@ module ApplicationTests @plugin.write "config/routes.rb", <<-RUBY Blog::Engine.routes.draw do resources :posts - match '/generate_application_route', :to => 'posts#generate_application_route' - match '/application_route_in_view', :to => 'posts#application_route_in_view' - match '/engine_polymorphic_path', :to => 'posts#engine_polymorphic_path' + get '/generate_application_route', :to => 'posts#generate_application_route' + get '/application_route_in_view', :to => 'posts#application_route_in_view' + get '/engine_polymorphic_path', :to => 'posts#engine_polymorphic_path' end RUBY |