diff options
221 files changed, 2523 insertions, 1812 deletions
diff --git a/.gitignore b/.gitignore index 854fdbf450..a3a5304ecd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,7 @@ debug.log .Gemfile /.bundle -/.rbenv-version -/.rvmrc +/.ruby-version /Gemfile.lock /pkg /dist diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc index 9af79f73e2..b065be4922 100644 --- a/RELEASING_RAILS.rdoc +++ b/RELEASING_RAILS.rdoc @@ -42,6 +42,9 @@ addressed, and that can impact your release date. Ruby implementors have high stakes in making sure Rails works. Be kind and give them a heads up that Rails will be released soonish. +This only needs done for major and minor releases, bugfix releases aren't a +big enough deal, and are supposed to be backwards compatible. + Send an email just giving a heads up about the upcoming release to these lists: diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index c4d2950abd..ff74185e37 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -748,7 +748,7 @@ module ActionMailer templates_path = headers.delete(:template_path) || self.class.mailer_name templates_name = headers.delete(:template_name) || action_name - each_template(templates_path, templates_name) do |template| + each_template(Array(templates_path), templates_name) do |template| self.formats = template.formats responses << { @@ -762,9 +762,9 @@ module ActionMailer end def each_template(paths, name, &block) #:nodoc: - templates = lookup_context.find_all(name, Array(paths)) + templates = lookup_context.find_all(name, paths) if templates.empty? - raise ActionView::MissingTemplate.new([paths], name, [paths], false, 'mailer') + raise ActionView::MissingTemplate.new(paths, name, paths, false, 'mailer') else templates.uniq { |t| t.formats }.each(&block) end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index a35caf6a94..5be7f34331 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,36 +1,123 @@ ## Rails 4.0.0 (unreleased) ## -* Change asset_path to not include `SCRIPT_NAME` when it's used - from a mounted engine (fixes #8119). +* Fix `respond_to` not using formats that have no block if all is present. *Michael Grosser* - *Piotr Sarnacki* +* New applications use an encrypted session store by default. -* Add javascript based routing path matcher to `/rails/info/routes`. - Routes can now be filtered by whether or not they match a path. + *Santiago Pastorino* - *Richard Schneeman* +* Determine the controller#action from only the matched path when using the + shorthand syntax. Previously the complete path was used, which led + to problems with nesting (scopes and namespaces). + Fixes #7554. + + Example: + + # This will route to questions#new. + scope ':locale' do + get 'questions/new' + end + + *Yves Senn* + +* Remove support for parsing XML parameters from request. If you still want to parse XML + parameters, please install `actionpack-xml_parser' gem. + + *Prem Sichanugrist* + +* Remove support for parsing YAML parameters from request. + + *Aaron Patterson* + +* Add a message when you have no routes defined to both `rake routes` and + GET "/rails/info/routes" that lets you know you have none defined and links + to the Rails guide on the topic. + + *Steve Klabnik* + +* Change `image_alt` method to replace underscores/hyphens to spaces in filenames. + + Previously, underscored filenames became `alt="A_long_file_name_with_underscores"` + in HTML, which is poor for accessibility. For instance, Apple's VoiceOver Utility + pronounces each underscore. `A_long_file_name` thus would be read as `A underscore + long underscore file underscore name.` Now underscored or hyphenated filenames + (both of which are very popular naming conventions) read more naturally in + screen readers by converting both hyphens and underscores to spaces. + + Before: + + image_tag('underscored_file_name.png') + # => <img alt="Underscored_file_name" src="/assets/underscored_file_name.png" /> + + After: + + image_tag('underscored_file_name.png') + # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" /> + + *Nick Cox* + +* We don't support Ruby constant notation in the `:controller` option for route + definitions. So, this raises an `ArgumentError` now: + + resources :posts, controller: "Admin::Posts" # WRONG + + Use path notation instead: + + resources :posts, controller: "admin/posts" # RIGHT + + *Yves Senn* + +* `assert_template` can be used to verify the locals of partials, + which live inside a directory. + + # Prefixed partials inside directories worked and still work. + assert_template partial: 'directory/_partial', locals: {name: 'John'} + + # This did not work but does now. + assert_template partial: 'directory/partial', locals: {name: 'John'} -* Given + Fixes #8516. - params.permit(:name) + *Yves Senn* + +* Fix `content_tag_for` with array HTML option. + It would embed array as string instead of joining it like `content_tag` does: - `:name` passes if it is a key of `params` whose value is a permitted scalar. + content_tag(:td, class: ["foo", "bar"]){} + # => <td class="foo bar"></td> + + Before: - Similarly, given + content_tag_for(:td, item, class: ["foo", "bar"]) + # => <td class="item ["foo", "bar"]" id="item_1"></td> + + After: + + content_tag_for(:td, item, class: ["foo", "bar"]) + # => <td class="item foo bar" id="item_1"></td> + + *Semyon Perepelitsa* + +* Remove `BestStandardsSupport` middleware, !DOCTYPE html already triggers + standards mode per http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx + and ChromeFrame header has been moved to `config.action_dispatch.default_headers` + + *Guillermo Iguaran* - params.permit(tags: []) +* Fix CSRF protection and `current_url?` helper to work with HEAD requests + now that `ActionDispatch::Head` has been removed in favor of `Rack::Head`. - `:tags` passes if it is a key of `params` whose value is an array of - permitted scalars. + *Michiel Sikkes* - Permitted scalars filtering happens at any level of nesting. +* Change `asset_path` to not include `SCRIPT_NAME` when it's used + from a mounted engine. Fixes #8119. - *Xavier Noria* + *Piotr Sarnacki* -* `BestStandardsSupport` no longer duplicates `X-UA-Compatible` values on - each request to prevent header size from blowing up. +* Add JavaScript based routing path matcher to `/rails/info/routes`. + Routes can now be filtered by whether or not they match a path. - *Edward Anderson* + *Richard Schneeman* * Change the behavior of route defaults so that explicit defaults are no longer required where the key is not part of the path. For example: @@ -38,7 +125,7 @@ resources :posts, bucket_type: 'posts' will be required whenever constructing the url from a hash such as a functional - test or using url_for directly. However using the explicit form alters the + test or using `url_for` directly. However using the explicit form alters the behavior so it's not required: resources :projects, defaults: { bucket_type: 'projects' } @@ -72,7 +159,7 @@ *Colin Burn-Murdoch* -* Fixed json params parsing regression for non-object JSON content. +* Fixed JSON params parsing regression for non-object JSON content. *Dylan Smith* @@ -110,12 +197,13 @@ * Do not append second slash to `root_url` when using `trailing_slash: true` Fix #8700 - Example: - # before - root_url # => http://test.host// + Before: + + root_url(trailing_slash: true) # => http://test.host// + + After: - # after - root_url # => http://test.host/ + root_url(trailing_slash: true) # => http://test.host/ *Yves Senn* @@ -139,8 +227,8 @@ *Yves Senn* -* Added `Mime::NullType` class. This allows to use html?, xml?, json?..etc when - the `format` of `request` is unknown, without raise an exception. +* Added `Mime::NullType` class. This allows to use `html?`, `xml?`, `json?`, etc. + when the format of the request is unknown, without raising an exception. *Angelo Capilleri* @@ -165,7 +253,7 @@ *Matt Venables* -* Prevent raising EOFError on multipart GET request (IE issue). *Adam Stankiewicz* +* Prevent raising `EOFError` on multipart GET request (IE issue). *Adam Stankiewicz* * Rename all action callbacks from *_filter to *_action to avoid the misconception that these callbacks are only suited for transforming or halting the response. With the new style, @@ -211,7 +299,7 @@ *Stephen Ausman + Fabrizio Regini + Angelo Capilleri* -* Add filter capability to ActionController logs for redirect locations: +* Add logging filter capability for redirect URLs: config.filter_redirect << 'http://please.hide.it/' @@ -224,7 +312,8 @@ * More descriptive error messages when calling `render :partial` with an invalid `:layout` argument. - #8376 + + Fixes #8376. render partial: 'partial', layout: true @@ -309,23 +398,17 @@ Before: check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1) - #=> <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" /> + # => <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" /> After: check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1) - #=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" /> + # => <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" /> Fix #8108. *Daniel Fox, Grant Hutchins & Trace Wax* -* `BestStandardsSupport` middleware now appends it's `X-UA-Compatible` value to app's - returned value if any. - Fix #8086. - - *Nikita Afanasenko* - * `date_select` helper accepts `with_css_classes: true` to add css classes similar with type of generated select tags. @@ -378,10 +461,6 @@ * Failsafe exception returns `text/plain`. *Steve Klabnik* -* Remove `rack-cache` dependency from Action Pack and declare it on Gemfile - - *Guillermo Iguaran* - * Rename internal variables on `ActionController::TemplateAssertions` to prevent naming collisions. `@partials`, `@templates` and `@layouts` are now prefixed with an underscore. Fix #7459. @@ -467,9 +546,7 @@ *Guillermo Iguaran* * `ActionDispatch::Session::MemCacheStore` now uses `dalli` instead of the deprecated - `memcache-client` gem. As side effect the autoloading of unloaded classes objects - saved as values in session isn't supported anymore when mem_cache session store is - used, this can have an impact in apps only when config.cache_classes is false. + `memcache-client` gem. *Arun Agrawal + Guillermo Iguaran* @@ -743,9 +820,9 @@ * `assert_generates`, `assert_recognizes`, and `assert_routing` all raise `Assertion` instead of `RoutingError` *David Chelimsky* -* URL path parameters with invalid encoding now raise ActionController::BadRequest. *Andrew White* +* URL path parameters with invalid encoding now raise `ActionController::BadRequest`. *Andrew White* -* Malformed query and request parameter hashes now raise ActionController::BadRequest. *Andrew White* +* Malformed query and request parameter hashes now raise `ActionController::BadRequest`. *Andrew White* * Add `divider` option to `grouped_options_for_select` to generate a separator `optgroup` automatically, and deprecate `prompt` as third argument, in favor @@ -756,7 +833,7 @@ * Removed old text helper apis from `highlight`, `excerpt` and `word_wrap`. *Jeremy Walker* * Templates without a handler extension now raises a deprecation warning but still - defaults to ERb. In future releases, it will simply return the template contents. *Steve Klabnik* + defaults to ERB. In future releases, it will simply return the template contents. *Steve Klabnik* * Deprecate `:disable_with` in favor of `data: { disable_with: "Text" }` option from `submit_tag`, `button_tag` and `button_to` helpers. @@ -772,7 +849,7 @@ *Andrew White* -* `respond_to` and `respond_with` now raise ActionController::UnknownFormat instead +* `respond_to` and `respond_with` now raise `ActionController::UnknownFormat` instead of directly returning head 406. The exception is rescued and converted to 406 in the exception handling middleware. *Steven Soroka* @@ -802,7 +879,7 @@ * Remove the leading \n added by textarea on `assert_select`. *Santiago Pastorino* * Changed default value for `config.action_view.embed_authenticity_token_in_remote_forms` - to `false`. This change breaks remote forms that need to work also without javascript, + to `false`. This change breaks remote forms that need to work also without JavaScript, so if you need such behavior, you can either set it to `true` or explicitly pass `authenticity_token: true` in form options. @@ -897,9 +974,6 @@ * `check_box` with `:form` html5 attribute will now replicate the `:form` attribute to the hidden field as well. *Carlos Antonio da Silva* -* Turn off verbose mode of rack-cache, we still have X-Rack-Cache to - check that info. Closes #5245. *Santiago Pastorino* - * `label` form helper accepts `for: nil` to not generate the attribute. *Carlos Antonio da Silva* * Add `:format` option to `number_to_percentage`. *Rodrigo Flores* diff --git a/actionpack/RUNNING_UNIT_TESTS b/actionpack/RUNNING_UNIT_TESTS.rdoc index 1b29abd2d1..1b29abd2d1 100644 --- a/actionpack/RUNNING_UNIT_TESTS +++ b/actionpack/RUNNING_UNIT_TESTS.rdoc diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 70a1975ee9..03eeb841ee 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,8 +21,8 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'builder', '~> 3.1.0' - s.add_dependency 'rack', '~> 1.5.0' - s.add_dependency 'rack-test', '~> 0.6.1' + s.add_dependency 'rack', '~> 1.5.2' + s.add_dependency 'rack-test', '~> 0.6.2' s.add_dependency 'erubis', '~> 2.7.0' s.add_development_dependency 'activemodel', version diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index cf2cda039d..ea33d975ef 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -82,10 +82,6 @@ module ActionController end end - def caching_allowed? - request.get? && response.status == 200 - end - def view_cache_dependencies self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 832dec7b2a..143b3e0cbd 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/array/extract_options' require 'action_dispatch/middleware/stack' module ActionController diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index eddee08545..6e0cd51d8b 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/keys' + module ActionController module ConditionalGet extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index d04fbae150..834d44f045 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/array/extract_options' require 'abstract_controller/collector' module ActionController #:nodoc: @@ -419,7 +420,7 @@ module ActionController #:nodoc: end def response - @responses[format] || @responses[Mime::ALL] + @responses.fetch(format, @responses[Mime::ALL]) end def negotiate_format(request) diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 59b91a240e..e9031f3fac 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -88,7 +88,7 @@ module ActionController # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") # characters; and is terminated by a colon (":"). # The protocol relative scheme starts with a double slash "//" - when %r{^(\w[\w+.-]*:|//).*} + when %r{\A(\w[\w+.-]*:|//).*} options when String request.protocol + request.host_with_port + options diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 2d5ba0024e..d275a854fd 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -50,6 +50,10 @@ module ActionController #:nodoc: config_accessor :request_forgery_protection_token self.request_forgery_protection_token ||= :authenticity_token + # Holds the class which implements the request forgery protection. + config_accessor :forgery_protection_strategy + self.forgery_protection_strategy = nil + # Controls whether request forgery protection is turned on or not. Turned off by default only in test mode. config_accessor :allow_forgery_protection self.allow_forgery_protection = true if allow_forgery_protection.nil? @@ -82,14 +86,14 @@ module ActionController #:nodoc: # * <tt>:reset_session</tt> - Resets the session. # * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified. def protect_from_forgery(options = {}) - include protection_method_module(options[:with] || :null_session) + self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session) self.request_forgery_protection_token ||= :authenticity_token prepend_before_action :verify_authenticity_token, options end private - def protection_method_module(name) + def protection_method_class(name) ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify) rescue NameError raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session' @@ -97,17 +101,22 @@ module ActionController #:nodoc: end module ProtectionMethods - module NullSession - protected + class NullSession + def initialize(controller) + @controller = controller + end # This is the method that defines the application behavior when a request is found to be unverified. def handle_unverified_request + request = @controller.request request.session = NullSessionHash.new(request.env) request.env['action_dispatch.request.flash_hash'] = nil request.env['rack.session.options'] = { skip: true } request.env['action_dispatch.cookies'] = NullCookieJar.build(request) end + protected + class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: def initialize(env) super(nil, env) @@ -126,7 +135,7 @@ module ActionController #:nodoc: host = request.host secure = request.ssl? - new(key_generator, host, secure) + new(key_generator, host, secure, options_for_env({})) end def write(*) @@ -135,16 +144,20 @@ module ActionController #:nodoc: end end - module ResetSession - protected + class ResetSession + def initialize(controller) + @controller = controller + end def handle_unverified_request - reset_session + @controller.reset_session end end - module Exception - protected + class Exception + def initialize(controller) + @controller = controller + end def handle_unverified_request raise ActionController::InvalidAuthenticityToken @@ -153,6 +166,10 @@ module ActionController #:nodoc: end protected + def handle_unverified_request + forgery_protection_strategy.new(self).handle_unverified_request + end + # The actual before_action that is used. Modify this to change how you handle unverified requests. def verify_authenticity_token unless verified_request? @@ -163,11 +180,11 @@ module ActionController #:nodoc: # Returns true or false if a request is verified. Checks: # - # * is it a GET request? Gets should be safe and idempotent + # * is it a GET or HEAD request? Gets should be safe and idempotent # * Does the form_authenticity_token match the given token value from the params? # * Does the X-CSRF-Token header match the form_authenticity_token def verified_request? - !protect_against_forgery? || request.get? || + !protect_against_forgery? || request.get? || request.head? || form_authenticity_token == params[request_forgery_protection_token] || form_authenticity_token == request.headers['X-CSRF-Token'] end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 7e720ca6f5..e4dcd3213f 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -191,9 +191,9 @@ module ActionController # # +:name+ passes it is a key of +params+ whose associated value is of type # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+, - # +Date+, +Time+, +DateTime+, +StringIO+, +IO+, or - # +ActionDispatch::Http::UploadedFile+. Otherwise, the key +:name+ is - # filtered out. + # +Date+, +Time+, +DateTime+, +StringIO+, +IO+, + # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+. + # Otherwise, the key +:name+ is filtered out. # # You may declare that the parameter should be an array of permitted scalars # by mapping it to an empty array: @@ -374,6 +374,7 @@ module ActionController StringIO, IO, ActionDispatch::Http::UploadedFile, + Rack::Test::UploadedFile, ] def permitted_scalar?(value) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index e9cf4372e4..bba1f1e201 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,6 +1,7 @@ require 'rack/session/abstract/id' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/module/anonymous' +require 'active_support/core_ext/hash/keys' module ActionController module TemplateAssertions @@ -125,7 +126,11 @@ module ActionController if expected_partial = options[:partial] if expected_locals = options[:locals] if defined?(@_rendered_views) - view = expected_partial.to_s.sub(/^_/,'') + view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/') + + partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view + assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg + msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial, expected_locals, @_rendered_views.locals_for(view)] diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 9ab048b756..618e2f3033 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -47,7 +47,6 @@ module ActionDispatch autoload_under 'middleware' do autoload :RequestId - autoload :BestStandardsSupport autoload :Callbacks autoload :Cookies autoload :DebugExceptions diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index e7e8905d7e..06e936cdb0 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,4 +1,3 @@ -require 'digest/md5' require 'active_support/core_ext/class/attribute_accessors' require 'monitor' @@ -170,7 +169,7 @@ module ActionDispatch # :nodoc: alias_method :status_message, :message def respond_to?(method) - if method.to_sym == :to_path + if method.to_s == 'to_path' stream.respond_to?(:to_path) else super diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 43f26d696d..97ac462411 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -1,3 +1,6 @@ +require 'active_support/core_ext/module/attribute_accessors' +require 'active_support/core_ext/hash/slice' + module ActionDispatch module Http module URL @@ -32,8 +35,12 @@ module ActionDispatch params.reject! { |_,v| v.to_param.nil? } result = build_host_url(options) - if options[:trailing_slash] && !path.ends_with?('/') - result << path.sub(/(\?|\z)/) { "/" + $& } + if options[:trailing_slash] + if path.include?('?') + result << path.sub(/\?/, '/\&') + else + result << path.sub(/[^\/]\z|\A\z/, '\&/') + end else result << path end diff --git a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb deleted file mode 100644 index 94efeb79fa..0000000000 --- a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb +++ /dev/null @@ -1,30 +0,0 @@ -module ActionDispatch - class BestStandardsSupport - def initialize(app, type = true) - @app = app - - @header = case type - when true - "IE=Edge,chrome=1" - when :builtin - "IE=Edge" - when false - nil - end - end - - def call(env) - status, headers, body = @app.call(env) - - if headers["X-UA-Compatible"] && @header - unless headers["X-UA-Compatible"][@header] - headers["X-UA-Compatible"] << "," << @header.to_s - end - else - headers["X-UA-Compatible"] = @header - end - - [status, headers, body] - end - end -end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 0f02d230d4..ee29e5c59c 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/module/attribute_accessors' +require 'active_support/key_generator' require 'active_support/message_verifier' module ActionDispatch @@ -110,13 +111,17 @@ module ActionDispatch # $& => example.local DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/ + def self.options_for_env(env) #:nodoc: + { signed_cookie_salt: env[SIGNED_COOKIE_SALT] || '', + encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT] || '', + encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '', + token_key: env[TOKEN_KEY] } + end + def self.build(request) env = request.env key_generator = env[GENERATOR_KEY] - options = { signed_cookie_salt: env[SIGNED_COOKIE_SALT], - encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT], - encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT], - token_key: env[TOKEN_KEY] } + options = options_for_env env host = request.host secure = request.ssl? @@ -405,7 +410,7 @@ module ActionDispatch @encryptor.decrypt_and_verify(encrypted_message) end rescue ActiveSupport::MessageVerifier::InvalidSignature, - ActiveSupport::MessageVerifier::InvalidMessage + ActiveSupport::MessageEncryptor::InvalidMessage nil end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 0898ad82dd..0fa1e9b859 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -13,10 +13,7 @@ module ActionDispatch end end - DEFAULT_PARSERS = { - Mime::XML => :xml_simple, - Mime::JSON => :json - } + DEFAULT_PARSERS = { Mime::JSON => :json } def initialize(app, parsers = {}) @app, @parsers = app, DEFAULT_PARSERS.merge(parsers) @@ -36,19 +33,13 @@ module ActionDispatch return false if request.content_length.zero? - mime_type = content_type_from_legacy_post_data_format_header(env) || - request.content_mime_type - - strategy = @parsers[mime_type] + strategy = @parsers[request.content_mime_type] return false unless strategy case strategy when Proc strategy.call(request.raw_post) - when :xml_simple, :xml_node - data = request.deep_munge(Hash.from_xml(request.body.read) || {}) - data.with_indifferent_access when :json data = ActiveSupport::JSON.decode(request.body) data = {:_json => data} unless data.is_a?(Hash) @@ -56,23 +47,12 @@ module ActionDispatch else false end - rescue Exception => e # YAML, XML or Ruby code block errors + rescue Exception => e # JSON or Ruby code block errors logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}" raise ParseError.new(e.message, e) end - def content_type_from_legacy_post_data_format_header(env) - if x_post_format = env['HTTP_X_POST_DATA_FORMAT'] - case x_post_format.to_s.downcase - when 'yaml' then return Mime::YAML - when 'xml' then return Mime::XML - end - end - - nil - end - def logger(env) env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr) end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 4e36c9bb49..6de9be63c5 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -2,7 +2,7 @@ module ActionDispatch # This middleware calculates the IP address of the remote client that is # making the request. It does this by checking various headers that could # contain the address, and then picking the last-set address that is not - # on the list of trusted IPs. This follows the precendent set by e.g. + # on the list of trusted IPs. This follows the precedent set by e.g. # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453], # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection] # by @gingerlime. A more detailed explanation of the algorithm is given @@ -83,7 +83,7 @@ module ActionDispatch # This constant contains a regular expression that validates every known # form of IP v4 and v6 address, with or without abbreviations, adapted - # from {this gist}[https://gist.github.com/1289635]. + # from {this gist}[https://gist.github.com/gazay/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 (^( diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb index ab24118f3e..550f4dbd0d 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb @@ -1,6 +1,6 @@ <% unless @exception.blamed_files.blank? %> <% if (hide = @exception.blamed_files.length > 8) %> - <a href="#" onclick="toggleTrace()">Toggle blamed files</a> + <a href="#" onclick="return toggleTrace()">Toggle blamed files</a> <% end %> <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre> <% end %> @@ -21,12 +21,12 @@ <p><b>Parameters</b>:</p> <pre><%= request_dump %></pre> <div class="details"> - <div class="summary"><a href="#" onclick="toggleSessionDump()">Toggle session dump</a></div> + <div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div> <div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div> </div> <div class="details"> - <div class="summary"><a href="#" onclick="toggleEnvDump()">Toggle env dump</a></div> + <div class="summary"><a href="#" onclick="return toggleEnvDump()">Toggle env dump</a></div> <div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div> </div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb index 9878c2747e..891c87ac27 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -121,6 +121,7 @@ var toggle = function(id) { var s = document.getElementById(id).style; s.display = s.display == 'none' ? 'block' : 'none'; + return false; } var show = function(id) { document.getElementById(id).style.display = 'block'; @@ -129,13 +130,13 @@ document.getElementById(id).style.display = 'none'; } var toggleTrace = function() { - toggle('blame_trace'); + return toggle('blame_trace'); } var toggleSessionDump = function() { - toggle('session_dump'); + return toggle('session_dump'); } var toggleEnvDump = function() { - toggle('env_dump'); + return toggle('env_dump'); } </script> </head> diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 5a835ae439..edf37bb9a5 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -6,7 +6,6 @@ module ActionDispatch config.action_dispatch.x_sendfile_header = nil config.action_dispatch.ip_spoofing_check = true config.action_dispatch.show_exceptions = true - config.action_dispatch.best_standards_support = true config.action_dispatch.tld_length = 1 config.action_dispatch.ignore_accept_header = false config.action_dispatch.rescue_templates = { } @@ -21,7 +20,8 @@ module ActionDispatch config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block', - 'X-Content-Type-Options' => 'nosniff' + 'X-Content-Type-Options' => 'nosniff', + 'X-UA-Compatible' => 'chrome=1' } config.eager_load_namespaces << ActionDispatch diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index bc6dd7145c..d251de33df 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -1,4 +1,5 @@ require 'delegate' +require 'active_support/core_ext/string/strip' module ActionDispatch module Routing @@ -90,6 +91,13 @@ module ActionDispatch routes_to_display = filter_routes(filter) routes = collect_routes(routes_to_display) + + if routes.none? + formatter.no_routes + return formatter.result + end + + formatter.header routes formatter.section routes @engines.each do |name, engine_routes| @@ -155,16 +163,40 @@ module ActionDispatch @buffer << draw_section(routes) end + def header(routes) + @buffer << draw_header(routes) + end + + def no_routes + @buffer << <<-MESSAGE.strip_heredoc + You don't have any routes defined! + + Please add some routes in config/routes.rb. + + For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html. + MESSAGE + end + private def draw_section(routes) - name_width = routes.map { |r| r[:name].length }.max - verb_width = routes.map { |r| r[:verb].length }.max - path_width = routes.map { |r| r[:path].length }.max + name_width, verb_width, path_width = widths(routes) routes.map do |r| "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" end end + + def draw_header(routes) + name_width, verb_width, path_width = widths(routes) + + "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action" + end + + def widths(routes) + [routes.map { |r| r[:name].length }.max, + routes.map { |r| r[:verb].length }.max, + routes.map { |r| r[:path].length }.max] + end end class HtmlTableFormatter @@ -181,6 +213,23 @@ module ActionDispatch @buffer << @view.render(partial: "routes/route", collection: routes) end + # the header is part of the HTML page, so we don't construct it here. + def header(routes) + end + + def no_routes + @buffer << <<-MESSAGE.strip_heredoc + <p>You don't have any routes defined!</p> + <ul> + <li>Please add some routes in <tt>config/routes.rb</tt>.</li> + <li> + For more information about routes, please see the Rails guide + <a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>. + </li> + </ul> + MESSAGE + end + def result @view.raw @view.render(layout: "routes/table") { @view.raw @buffer.join("\n") diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 3a86432622..a8e225d61c 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/enumerable' +require 'active_support/core_ext/array/extract_options' require 'active_support/inflector' require 'action_dispatch/routing/redirection' @@ -49,7 +50,6 @@ module ActionDispatch class Mapping #:nodoc: IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format] ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} - SHORTHAND_REGEX = %r{/[\w/]+$} WILDCARD_PATH = %r{\*([^/\)]+)\)?$} attr_reader :scope, :path, :options, :requirements, :conditions, :defaults @@ -110,17 +110,7 @@ module ActionDispatch @options[:controller] ||= /.+?/ end - if using_match_shorthand?(path_without_format, @options) - to_shorthand = @options[:to].blank? - @options[:to] ||= path_without_format.gsub(/\(.*\)/, "")[1..-1].sub(%r{/([^/]*)$}, '#\1') - end - - @options.merge!(default_controller_and_action(to_shorthand)) - end - - # match "account/overview" - def using_match_shorthand?(path, options) - path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX + @options.merge!(default_controller_and_action) end def normalize_format! @@ -213,7 +203,7 @@ module ActionDispatch Constraints.new(endpoint, blocks, @set.request_class) end - def default_controller_and_action(to_shorthand=nil) + def default_controller_and_action if to.respond_to?(:call) { } else @@ -226,7 +216,7 @@ module ActionDispatch controller ||= default_controller action ||= default_action - unless controller.is_a?(Regexp) || to_shorthand + unless controller.is_a?(Regexp) controller = [@scope[:module], controller].compact.join("/").presence end @@ -245,6 +235,12 @@ module ActionDispatch raise ArgumentError, "missing :action" end + if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/ + message = "'#{controller}' is not a supported controller name. This can lead to potential routing problems." + message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use" + raise ArgumentError, message + end + hash = {} hash[:controller] = controller unless controller.blank? hash[:action] = action unless action.blank? @@ -1376,6 +1372,11 @@ module ActionDispatch paths = [path] + rest end + path_without_format = path.to_s.sub(/\(\.:format\)$/, '') + if using_match_shorthand?(path_without_format, options) + options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1') + end + options[:anchor] = true unless options.key?(:anchor) if options[:on] && !VALID_ON_OPTIONS.include?(options[:on]) @@ -1386,6 +1387,10 @@ module ActionDispatch self end + def using_match_shorthand?(path, options) + path && (options[:to] || options[:action]).nil? && path =~ %r{/[\w/]+$} + end + def decomposed_match(path, options) # :nodoc: if on = options.delete(:on) send(on) { decomposed_match(path, options) } diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 705314f8ab..ff86f87d49 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -4,6 +4,7 @@ require 'thread_safe' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/array/extract_options' require 'action_controller/metal/exceptions' module ActionDispatch @@ -100,25 +101,7 @@ module ActionDispatch def initialize @routes = {} @helpers = [] - @module = Module.new do - protected - - def handle_positional_args(args, options, segment_keys) - inner_options = args.extract_options! - result = options.dup - - if args.size > 0 - keys = 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 + @module = Module.new end def helper_names @@ -160,68 +143,126 @@ module ActionDispatch routes.length end - private + class UrlHelper # :nodoc: + def self.create(route, options) + if optimize_helper?(route) + OptimizedUrlHelper.new(route, options) + else + new route, options + end + end - def define_named_route_methods(name, route) - define_url_helper route, :"#{name}_path", - route.defaults.merge(:use_route => name, :only_path => true) - define_url_helper route, :"#{name}_url", - route.defaults.merge(:use_route => name, :only_path => false) + def self.optimize_helper?(route) + route.requirements.except(:controller, :action).empty? end - # Create a url helper allowing ordered parameters to be associated - # with corresponding dynamic segments, so you can do: - # - # foo_url(bar, baz, bang) - # - # Instead of: - # - # foo_url(bar: bar, baz: baz, bang: bang) - # - # Also allow options hash, so you can do: - # - # foo_url(bar, baz, bang, sort_by: 'baz') - # - def define_url_helper(route, name, options) - @module.remove_possible_method name - @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - def #{name}(*args) - if #{optimize_helper?(route)} && args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation? - options = #{options.inspect} - options.merge!(url_options) if respond_to?(:url_options) - options[:path] = "#{optimized_helper(route)}" - ActionDispatch::Http::URL.url_for(options) - else - url_for(handle_positional_args(args, #{options.inspect}, #{route.segment_keys.inspect})) - end + class OptimizedUrlHelper < UrlHelper # :nodoc: + attr_reader :arg_size + + def initialize(route, options) + super + @path_parts = @route.required_parts + @arg_size = @path_parts.size + @string_route = string_route(route) + end + + def call(t, args) + if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t) + @options.merge!(t.url_options) if t.respond_to?(:url_options) + @options[:path] = optimized_helper(args) + ActionDispatch::Http::URL.url_for(@options) + else + super + end + end + + private + + def string_route(route) + string_route = route.ast.to_s.dup + while string_route.gsub!(/\([^\)]*\)/, "") + true + end + string_route + end + + def optimized_helper(args) + path = @string_route.dup + klass = Journey::Router::Utils + + @path_parts.zip(args) do |part, arg| + # Replace each route parameter + # e.g. :id for regular parameter or *path for globbing + # with ruby string interpolation code + path.gsub!(/(\*|:)#{part}/, klass.escape_fragment(arg.to_param)) end - END_EVAL + path + end - helpers << name + def optimize_routes_generation?(t) + t.send(:optimize_routes_generation?) + end end - # Clause check about when we need to generate an optimized helper. - def optimize_helper?(route) #:nodoc: - route.requirements.except(:controller, :action).empty? + def initialize(route, options) + @options = options + @segment_keys = route.segment_keys + @route = route + end + + def call(t, args) + t.url_for(handle_positional_args(t, args, @options, @segment_keys)) end - # Generates the interpolation to be used in the optimized helper. - def optimized_helper(route) - string_route = route.ast.to_s + def handle_positional_args(t, args, options, keys) + inner_options = args.extract_options! + result = options.dup - while string_route.gsub!(/\([^\)]*\)/, "") - true + if args.size > 0 + if args.size < keys.size - 1 # take format into account + keys -= t.url_options.keys if t.respond_to?(:url_options) + keys -= options.keys + end + result.merge!(Hash[keys.zip(args)]) end - route.required_parts.each_with_index do |part, i| - # Replace each route parameter - # e.g. :id for regular parameter or *path for globbing - # with ruby string interpolation code - string_route.gsub!(/(\*|:)#{part}/, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}") - end + result.merge!(inner_options) + end + end + + private + # Create a url helper allowing ordered parameters to be associated + # with corresponding dynamic segments, so you can do: + # + # foo_url(bar, baz, bang) + # + # Instead of: + # + # foo_url(bar: bar, baz: baz, bang: bang) + # + # Also allow options hash, so you can do: + # + # foo_url(bar, baz, bang, sort_by: 'baz') + # + def define_url_helper(route, name, options) + helper = UrlHelper.create(route, options.dup) - string_route + @module.remove_possible_method name + @module.module_eval do + define_method(name) do |*args| + helper.call self, args + end end + + helpers << name + end + + def define_named_route_methods(name, route) + define_url_helper route, :"#{name}_path", + route.defaults.merge(:use_route => name, :only_path => true) + define_url_helper route, :"#{name}_url", + route.defaults.merge(:use_route => name, :only_path => false) + end end attr_accessor :formatter, :set, :named_routes, :default_scope, :router diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb index 73af5920ed..e2393d3799 100644 --- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb +++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/array/extract_options' + module ActionDispatch module Routing class RoutesProxy #:nodoc: diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 5b3a2cae7c..31e37893c6 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -214,10 +214,24 @@ module ActionView end # Returns a string suitable for an html image tag alt attribute. - # +src+ is meant to be an image file path. - # It removes the basename of the file path and the digest, if any. + # The +src+ argument is meant to be an image file path. + # The method removes the basename of the file path and the digest, + # if any. It also removes hyphens and underscores from file names and + # replaces them with spaces, returning a space-separated, titleized + # string. + # + # ==== Examples + # + # image_tag('rails.png') + # # => <img alt="Rails" src="/assets/rails.png" /> + # + # image_tag('hyphenated-file-name.png') + # # => <img alt="Hyphenated file name" src="/assets/hyphenated-file-name.png" /> + # + # image_tag('underscored_file_name.png') + # # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" /> def image_alt(src) - File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').capitalize + File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize end # Returns an html video tag for the +sources+. If +sources+ is a string, diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index da8a9dafe4..d3953c26b7 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -1,5 +1,6 @@ require 'date' require 'action_view/helpers/tag_helper' +require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/date/conversions' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/object/with_options' @@ -12,7 +13,7 @@ module ActionView # elements. All of the select-type methods share a number of common options that are as follows: # # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" - # would give \birthday[month] instead of \date[month] if passed to the <tt>select_month</tt> method. + # would give \birthday[month] instead of \date[month] if passed to the <tt>select_month</tt> method. # * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date. # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, # the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead @@ -641,6 +642,8 @@ module ActionView # <time datetime="2010-11-03">Yesterday</time> # time_tag Date.today, pubdate: true # => # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time> + # time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # => + # <time datetime="2010-W44">November 04, 2010</time> # # <%= time_tag Time.now do %> # <span>Right now</span> @@ -650,7 +653,7 @@ module ActionView options = args.extract_options! format = options.delete(:format) || :long content = args.first || I18n.l(date_or_time, :format => format) - datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339 + datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601 content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block) end diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index 34fc23ac1a..c29c1b1eea 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -32,7 +32,7 @@ module ActionView content_tag(:pre, object, :class => "debug_dump") rescue Exception # errors from Marshal or YAML # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback - content_tag(:code, object.to_yaml, :class => "debug_dump") + content_tag(:code, object.inspect, :class => "debug_dump") end end end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 4a31de715d..3dae1fc87a 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -8,7 +8,6 @@ require 'action_view/model_naming' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/output_safety' -require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/string/inflections' module ActionView @@ -771,8 +770,8 @@ module ActionView # text_field(:post, :title, class: "create_input") # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" /> # - # text_field(:session, :user, onchange: "if $('#session_user').value == 'admin' { alert('Your login can not be admin!'); }") - # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('#session_user').value == 'admin' { alert('Your login can not be admin!'); }"/> + # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }") + # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }"/> # # text_field(:snippet, :code, size: 20, class: 'code_input') # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" /> @@ -792,8 +791,8 @@ module ActionView # password_field(:account, :secret, class: "form_input", value: @account.secret) # # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" /> # - # password_field(:user, :password, onchange: "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }") - # # => <input type="password" id="user_password" name="user[password]" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/> + # password_field(:user, :password, onchange: "if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }") + # # => <input type="password" id="user_password" name="user[password]" onchange="if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }"/> # # password_field(:account, :pin, size: 20, class: 'form_input') # # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" /> diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 89c24956a3..377819a80c 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -2,6 +2,7 @@ require 'cgi' require 'erb' require 'action_view/helpers/form_helper' require 'active_support/core_ext/string/output_safety' +require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/array/wrap' module ActionView @@ -564,14 +565,14 @@ module ActionView if priority_zones if priority_zones.is_a?(Regexp) - priority_zones = zones.select { |z| z =~ priority_zones } + priority_zones = zones.grep(priority_zones) end zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected) zone_options.safe_concat content_tag(:option, '-------------', :value => '', :disabled => 'disabled') zone_options.safe_concat "\n" - zones.reject! { |z| priority_zones.include?(z) } + zones = zones - priority_zones end zone_options.safe_concat options_for_select(convert_zones[zones], selected) diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 86d9f94067..1adc8225f1 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -33,7 +33,7 @@ module ActionView # (by passing <tt>false</tt>). Remote forms may omit the embedded authenticity token # by setting <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>. # This is helpful when you're fragment-caching the form. Remote forms get the - # authenticity from the <tt>meta</tt> tag, so embedding is unnecessary unless you + # authenticity token from the <tt>meta</tt> tag, so embedding is unnecessary unless you # support browsers without JavaScript. # * A list of parameters to feed to the URL the form will be posted to. # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 271a194913..f767957fa9 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -92,7 +92,7 @@ module ActionView # for each record. def content_tag_for_single_record(tag_name, record, prefix, options, &block) options = options ? options.dup : {} - options[:class] = "#{dom_class(record, prefix)} #{options[:class]}".rstrip + options[:class] = [ dom_class(record, prefix), options[:class] ].compact options[:id] = dom_id(record, prefix) if block_given? diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index bade121d44..775d93ed39 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -241,13 +241,13 @@ module ActionView # # </div> # # </form>" # - # <%= button_to "New", action: "new", form_class: "new-thing" %> + # <%= button_to "New", { action: "new" }, form_class: "new-thing" %> # # => "<form method="post" action="/controller/new" class="new-thing"> # # <div><input value="New" type="submit" /></div> # # </form>" # # - # <%= button_to "Create", action: "create", remote: true, form: { "data-type" => "json" } %> + # <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %> # # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json"> # # <div> # # <input value="Create" type="submit" /> @@ -514,7 +514,7 @@ module ActionView "in a #request method" end - return false unless request.get? + return false unless request.get? || request.head? url_string = url_for(options) diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 4479da5bc4..463f192d0c 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -122,7 +122,7 @@ module ActionView class RenderedViewsCollection def initialize - @rendered_views ||= {} + @rendered_views ||= Hash.new { |hash, key| hash[key] = [] } end def add(view, locals) @@ -134,6 +134,10 @@ module ActionView @rendered_views[view] end + def rendered_views + @rendered_views.keys + end + def view_rendered?(view, expected_locals) locals_for(view).any? do |actual_locals| expected_locals.all? {|key, value| value == actual_locals[key] } diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 365fa04570..71bcfd664e 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'rbconfig' +require 'active_support/core_ext/array/extract_options' # The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited # method has access to the view_paths array when looking for a layout to automatically assign. diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index ed013e2185..a9c62899b5 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -80,6 +80,13 @@ class RespondToController < ActionController::Base respond_to(:html, :xml) end + def using_defaults_with_all + respond_to do |type| + type.html + type.all{ render text: "ALL" } + end + end + def made_for_content_type respond_to do |type| type.rss { render :text => "RSS" } @@ -301,6 +308,20 @@ class RespondToControllerTest < ActionController::TestCase assert_equal "<p>Hello world!</p>\n", @response.body end + def test_using_defaults_with_all + @request.accept = "*/*" + get :using_defaults_with_all + assert_equal "HTML!", @response.body.strip + + @request.accept = "text/html" + get :using_defaults_with_all + assert_equal "HTML!", @response.body.strip + + @request.accept = "application/json" + get :using_defaults_with_all + assert_equal "ALL", @response.body + end + def test_using_defaults_with_type_list @request.accept = "*/*" get :using_defaults_with_type_list diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index aadb142660..437da43d9b 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -32,7 +32,8 @@ class ParametersPermitTest < ActiveSupport::TestCase values += [0, 1.0, 2**128, BigDecimal.new(1)] values += [true, false] values += [Date.today, Time.now, DateTime.now] - values += [STDOUT, StringIO.new, ActionDispatch::Http::UploadedFile.new(tempfile: __FILE__)] + values += [STDOUT, StringIO.new, ActionDispatch::Http::UploadedFile.new(tempfile: __FILE__), + Rack::Test::UploadedFile.new(__FILE__)] values.each do |value| params = ActionController::Parameters.new(id: value) diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 523a8d0572..c272e785c2 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -66,6 +66,19 @@ class RequestForgeryProtectionControllerUsingException < ActionController::Base protect_from_forgery :only => %w(index meta), :with => :exception end +class RequestForgeryProtectionControllerUsingNullSession < ActionController::Base + protect_from_forgery :with => :null_session + + def signed + cookies.signed[:foo] = 'bar' + render :nothing => true + end + + def encrypted + cookies.encrypted[:foo] = 'bar' + render :nothing => true + end +end class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession self.allow_forgery_protection = false @@ -170,6 +183,10 @@ module RequestForgeryProtectionTests assert_not_blocked { get :index } end + def test_should_allow_head + assert_not_blocked { head :index } + end + def test_should_allow_post_without_token_on_unsafe_action assert_not_blocked { post :unsafe } end @@ -283,6 +300,28 @@ class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController end end +class NullSessionDummyKeyGenerator + def generate_key(secret) + '03312270731a2ed0d11ed091c2338a06' + end +end + +class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase + def setup + @request.env[ActionDispatch::Cookies::GENERATOR_KEY] = NullSessionDummyKeyGenerator.new + end + + test 'should allow to set signed cookies' do + post :signed + assert_response :ok + end + + test 'should allow to set encrypted cookies' do + post :encrypted + assert_response :ok + end +end + class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase include RequestForgeryProtectionTests def assert_blocked diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 305659b219..9aea7e860a 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -1,6 +1,7 @@ require 'abstract_unit' require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/with_options' +require 'active_support/core_ext/array/extract_options' class ResourcesTest < ActionController::TestCase def test_default_restful_routes diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 0480295056..b2dfd96606 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -32,182 +32,53 @@ class WebServiceTest < ActionDispatch::IntegrationTest end end - def test_post_xml + def test_post_json with_test_route_set do - post "/", '<entry attributed="true"><summary>content...</summary></entry>', - {'CONTENT_TYPE' => 'application/xml'} + post "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json' assert_equal 'entry', @controller.response.body assert @controller.params.has_key?(:entry) assert_equal 'content...', @controller.params["entry"]['summary'] - assert_equal 'true', @controller.params["entry"]['attributed'] end end - def test_put_xml + def test_put_json with_test_route_set do - put "/", '<entry attributed="true"><summary>content...</summary></entry>', - {'CONTENT_TYPE' => 'application/xml'} + put "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json' assert_equal 'entry', @controller.response.body assert @controller.params.has_key?(:entry) assert_equal 'content...', @controller.params["entry"]['summary'] - assert_equal 'true', @controller.params["entry"]['attributed'] end end - def test_put_xml_using_a_type_node + def test_register_and_use_json_simple with_test_route_set do - put "/", '<type attributed="true"><summary>content...</summary></type>', - {'CONTENT_TYPE' => 'application/xml'} - - assert_equal 'type', @controller.response.body - assert @controller.params.has_key?(:type) - assert_equal 'content...', @controller.params["type"]['summary'] - assert_equal 'true', @controller.params["type"]['attributed'] - end - end - - def test_put_xml_using_a_type_node_and_attribute - with_test_route_set do - put "/", '<type attributed="true"><summary type="boolean">false</summary></type>', - {'CONTENT_TYPE' => 'application/xml'} - - assert_equal 'type', @controller.response.body - assert @controller.params.has_key?(:type) - assert_equal false, @controller.params["type"]['summary'] - assert_equal 'true', @controller.params["type"]['attributed'] - end - end - - def test_post_xml_using_a_type_node - with_test_route_set do - post "/", '<font attributed="true"><type>arial</type></font>', - {'CONTENT_TYPE' => 'application/xml'} - - assert_equal 'font', @controller.response.body - assert @controller.params.has_key?(:font) - assert_equal 'arial', @controller.params['font']['type'] - assert_equal 'true', @controller.params["font"]['attributed'] - end - end - - def test_post_xml_using_a_root_node_named_type - with_test_route_set do - post "/", '<type type="integer">33</type>', - {'CONTENT_TYPE' => 'application/xml'} - - assert @controller.params.has_key?(:type) - assert_equal 33, @controller.params['type'] - end - end - - def test_post_xml_using_an_attributted_node_named_type - with_test_route_set do - with_params_parsers Mime::XML => Proc.new { |data| Hash.from_xml(data)['request'].with_indifferent_access } do - post "/", '<request><type type="string">Arial,12</type><z>3</z></request>', - {'CONTENT_TYPE' => 'application/xml'} - - assert_equal 'type, z', @controller.response.body - assert @controller.params.has_key?(:type) - assert_equal 'Arial,12', @controller.params['type'], @controller.params.inspect - assert_equal '3', @controller.params['z'], @controller.params.inspect - end - end - end - - def test_post_xml_using_a_disallowed_type_attribute - $stderr = StringIO.new - with_test_route_set do - post '/', '<foo type="symbol">value</foo>', 'CONTENT_TYPE' => 'application/xml' - assert_response 500 - - post '/', '<foo type="yaml">value</foo>', 'CONTENT_TYPE' => 'application/xml' - assert_response 500 - end - ensure - $stderr = STDERR - end - - def test_register_and_use_yaml - with_test_route_set do - with_params_parsers Mime::YAML => Proc.new { |d| YAML.load(d) } do - post "/", {"entry" => "loaded from yaml"}.to_yaml, - {'CONTENT_TYPE' => 'application/x-yaml'} - - assert_equal 'entry', @controller.response.body - assert @controller.params.has_key?(:entry) - assert_equal 'loaded from yaml', @controller.params["entry"] - end - end - end - - def test_register_and_use_xml_simple - with_test_route_set do - with_params_parsers Mime::XML => Proc.new { |data| Hash.from_xml(data)['request'].with_indifferent_access } do - post "/", '<request><summary>content...</summary><title>SimpleXml</title></request>', - {'CONTENT_TYPE' => 'application/xml'} + with_params_parsers Mime::JSON => Proc.new { |data| JSON.parse(data)['request'].with_indifferent_access } do + post "/", '{"request":{"summary":"content...","title":"JSON"}}', + 'CONTENT_TYPE' => 'application/json' assert_equal 'summary, title', @controller.response.body assert @controller.params.has_key?(:summary) assert @controller.params.has_key?(:title) assert_equal 'content...', @controller.params["summary"] - assert_equal 'SimpleXml', @controller.params["title"] + assert_equal 'JSON', @controller.params["title"] end end end - def test_use_xml_ximple_with_empty_request + def test_use_json_with_empty_request with_test_route_set do - assert_nothing_raised { post "/", "", {'CONTENT_TYPE' => 'application/xml'} } + assert_nothing_raised { post "/", "", 'CONTENT_TYPE' => 'application/json' } assert_equal '', @controller.response.body end end - def test_dasherized_keys_as_xml - with_test_route_set do - post "/?full=1", "<first-key>\n<sub-key>...</sub-key>\n</first-key>", - {'CONTENT_TYPE' => 'application/xml'} - assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body - assert_equal "...", @controller.params[:first_key][:sub_key] - end - end - - def test_typecast_as_xml - with_test_route_set do - xml = <<-XML - <data> - <a type="integer">15</a> - <b type="boolean">false</b> - <c type="boolean">true</c> - <d type="date">2005-03-17</d> - <e type="datetime">2005-03-17T21:41:07Z</e> - <f>unparsed</f> - <g type="integer">1</g> - <g>hello</g> - <g type="date">1974-07-25</g> - </data> - XML - post "/", xml, {'CONTENT_TYPE' => 'application/xml'} - - params = @controller.params - assert_equal 15, params[:data][:a] - assert_equal false, params[:data][:b] - assert_equal true, params[:data][:c] - assert_equal Date.new(2005,3,17), params[:data][:d] - assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e] - assert_equal "unparsed", params[:data][:f] - assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g] - end - end - - def test_entities_unescaped_as_xml_simple + def test_dasherized_keys_as_json with_test_route_set do - xml = <<-XML - <data><foo "bar's" & friends></data> - XML - post "/", xml, {'CONTENT_TYPE' => 'application/xml'} - assert_equal %(<foo "bar's" & friends>), @controller.params[:data] + post "/?full=1", '{"first-key":{"sub-key":"..."}}', 'CONTENT_TYPE' => 'application/json' + assert_equal 'action, controller, first-key(sub-key), full', @controller.response.body + assert_equal "...", @controller.params['first-key']['sub-key'] end end diff --git a/actionpack/test/dispatch/best_standards_support_test.rb b/actionpack/test/dispatch/best_standards_support_test.rb deleted file mode 100644 index 551bb9621a..0000000000 --- a/actionpack/test/dispatch/best_standards_support_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'abstract_unit' - -class BestStandardsSupportTest < ActiveSupport::TestCase - def test_with_best_standards_support - _, headers, _ = app(true, {}).call({}) - assert_equal "IE=Edge,chrome=1", headers["X-UA-Compatible"] - end - - def test_with_builtin_best_standards_support - _, headers, _ = app(:builtin, {}).call({}) - assert_equal "IE=Edge", headers["X-UA-Compatible"] - end - - def test_without_best_standards_support - _, headers, _ = app(false, {}).call({}) - assert_equal nil, headers["X-UA-Compatible"] - end - - def test_appends_to_app_headers_without_duplication_after_multiple_requests - app_headers = { "X-UA-Compatible" => "requiresActiveX=true" } - _, headers, _ = app(true, app_headers).call({}) - _, headers, _ = app(true, app_headers).call({}) - - expects = "requiresActiveX=true,IE=Edge,chrome=1" - assert_equal expects, headers["X-UA-Compatible"] - end - - private - - def app(type, headers) - app = proc { [200, headers, "response"] } - ActionDispatch::BestStandardsSupport.new(app, type) - end - -end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 66d2cade22..5ada5a7603 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -72,6 +72,11 @@ class CookiesTest < ActionController::TestCase head :ok end + def set_invalid_encrypted_cookie + cookies[:invalid_cookie] = 'invalid--9170e00a57cfc27083363b5c75b835e477bd90cf' + head :ok + end + def raise_data_overflow cookies.signed[:foo] = 'bye!' * 1024 head :ok @@ -336,6 +341,11 @@ class CookiesTest < ActionController::TestCase assert_equal 45, @controller.send(:cookies).signed[:user_id] end + def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature + get :set_signed_cookie + assert_nil @controller.send(:cookies).signed[:non_existant_attribute] + end + def test_encrypted_cookie get :set_encrypted_cookie cookies = @controller.send :cookies @@ -346,9 +356,14 @@ class CookiesTest < ActionController::TestCase assert_equal 'bar', cookies.encrypted[:foo] end - def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature - get :set_signed_cookie - assert_nil @controller.send(:cookies).signed[:non_existant_attribute] + def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message + get :set_encrypted_cookie + assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute] + end + + def test_setting_invalid_encrypted_cookie_should_return_nil_when_accessing_it + get :set_invalid_encrypted_cookie + assert_nil @controller.send(:cookies).encrypted[:invalid_cookie] end def test_permanent_signed_cookie diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb deleted file mode 100644 index f13b64a3c7..0000000000 --- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb +++ /dev/null @@ -1,182 +0,0 @@ -require 'abstract_unit' - -class XmlParamsParsingTest < ActionDispatch::IntegrationTest - class TestController < ActionController::Base - class << self - attr_accessor :last_request_parameters - end - - def parse - self.class.last_request_parameters = request.request_parameters - head :ok - end - end - - def teardown - TestController.last_request_parameters = nil - end - - test "parses a strict rack.input" do - class Linted - undef call if method_defined?(:call) - def call(env) - bar = env['action_dispatch.request.request_parameters']['foo'] - result = "<ok>#{bar}</ok>" - [200, {"Content-Type" => "application/xml", "Content-Length" => result.length.to_s}, [result]] - end - end - req = Rack::MockRequest.new(ActionDispatch::ParamsParser.new(Linted.new)) - resp = req.post('/', "CONTENT_TYPE" => "application/xml", :input => "<foo>bar</foo>", :lint => true) - assert_equal "<ok>bar</ok>", resp.body - end - - def assert_parses(expected, xml) - with_test_routing do - post "/parse", xml, default_headers - assert_response :ok - assert_equal(expected, TestController.last_request_parameters) - end - end - - test "nils are stripped from collections" do - assert_parses( - {"hash" => { "person" => nil} }, - "<hash><person type=\"array\"><person nil=\"true\"/></person></hash>") - assert_parses( - {"hash" => { "person" => ['foo']} }, - "<hash><person type=\"array\"><person>foo</person><person nil=\"true\"/></person>\n</hash>") - end - - test "parses hash params" do - with_test_routing do - xml = "<person><name>David</name></person>" - post "/parse", xml, default_headers - assert_response :ok - assert_equal({"person" => {"name" => "David"}}, TestController.last_request_parameters) - end - end - - test "parses single file" do - with_test_routing do - xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{::Base64.encode64('ABC')}</avatar></person>" - post "/parse", xml, default_headers - assert_response :ok - - person = TestController.last_request_parameters - assert_equal "image/jpg", person['person']['avatar'].content_type - assert_equal "me.jpg", person['person']['avatar'].original_filename - assert_equal "ABC", person['person']['avatar'].read - end - end - - test "logs error if parsing unsuccessful" do - with_test_routing do - output = StringIO.new - xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{::Base64.encode64('ABC')}</avatar></pineapple>" - post "/parse", xml, default_headers.merge('action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output)) - assert_response :error - output.rewind && err = output.read - assert err =~ /Error occurred while parsing request parameters/ - end - end - - test "occurring a parse error if parsing unsuccessful" do - with_test_routing do - begin - $stderr = StringIO.new # suppress the log - xml = "<person><name>David</name></pineapple>" - exception = assert_raise(ActionDispatch::ParamsParser::ParseError) { post "/parse", xml, default_headers.merge('action_dispatch.show_exceptions' => false) } - assert_equal REXML::ParseException, exception.original_exception.class - assert_equal exception.original_exception.message, exception.message - ensure - $stderr = STDERR - end - end - end - - test "parses multiple files" do - xml = <<-end_body - <person> - <name>David</name> - <avatars> - <avatar type='file' name='me.jpg' content_type='image/jpg'>#{::Base64.encode64('ABC')}</avatar> - <avatar type='file' name='you.gif' content_type='image/gif'>#{::Base64.encode64('DEF')}</avatar> - </avatars> - </person> - end_body - - with_test_routing do - post "/parse", xml, default_headers - assert_response :ok - end - - person = TestController.last_request_parameters - - assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type - assert_equal "me.jpg", person['person']['avatars']['avatar'].first.original_filename - assert_equal "ABC", person['person']['avatars']['avatar'].first.read - - assert_equal "image/gif", person['person']['avatars']['avatar'].last.content_type - assert_equal "you.gif", person['person']['avatars']['avatar'].last.original_filename - assert_equal "DEF", person['person']['avatars']['avatar'].last.read - end - - private - def with_test_routing - with_routing do |set| - set.draw do - post ':action', :to => ::XmlParamsParsingTest::TestController - end - yield - end - end - - def default_headers - {'CONTENT_TYPE' => 'application/xml'} - end -end - -class LegacyXmlParamsParsingTest < XmlParamsParsingTest - private - def default_headers - {'HTTP_X_POST_DATA_FORMAT' => 'xml'} - end -end - -class RootLessXmlParamsParsingTest < ActionDispatch::IntegrationTest - class TestController < ActionController::Base - wrap_parameters :person, :format => :xml - - class << self - attr_accessor :last_request_parameters - end - - def parse - self.class.last_request_parameters = request.request_parameters - head :ok - end - end - - def teardown - TestController.last_request_parameters = nil - end - - test "parses hash params" do - with_test_routing do - xml = "<name>David</name>" - post "/parse", xml, {'CONTENT_TYPE' => 'application/xml'} - assert_response :ok - assert_equal({"name" => "David", "person" => {"name" => "David"}}, TestController.last_request_parameters) - end - end - - private - def with_test_routing - with_routing do |set| - set.draw do - post ':action', :to => ::RootLessXmlParamsParsingTest::TestController - end - yield - end - end -end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 8a01b29340..91810864d5 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -13,6 +13,9 @@ class RequestTest < ActiveSupport::TestCase assert_equal '/books', url_for(:only_path => true, :path => '/books') + assert_equal 'http://www.example.com/books/?q=code', url_for(trailing_slash: true, path: '/books?q=code') + assert_equal 'http://www.example.com/books/?spareslashes=////', url_for(trailing_slash: true, path: '/books?spareslashes=////') + assert_equal 'http://www.example.com', url_for assert_equal 'http://api.example.com', url_for(:subdomain => 'api') assert_equal 'http://example.com', url_for(:subdomain => false) diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 2fbe7358f9..74f5253c11 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -182,7 +182,8 @@ class ResponseTest < ActiveSupport::TestCase ActionDispatch::Response.default_headers = { 'X-Frame-Options' => 'DENY', 'X-Content-Type-Options' => 'nosniff', - 'X-XSS-Protection' => '1;' + 'X-XSS-Protection' => '1;', + 'X-UA-Compatible' => 'chrome=1' } resp = ActionDispatch::Response.new.tap { |response| response.body = 'Hello' @@ -192,6 +193,7 @@ class ResponseTest < ActiveSupport::TestCase assert_equal('DENY', resp.headers['X-Frame-Options']) assert_equal('nosniff', resp.headers['X-Content-Type-Options']) assert_equal('1;', resp.headers['X-XSS-Protection']) + assert_equal('chrome=1', resp.headers['X-UA-Compatible']) ensure ActionDispatch::Response.default_headers = nil end diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 55221f87c4..234ae5764f 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -44,35 +44,45 @@ module ActionDispatch mount engine => "/blog", :as => "blog" end - expected = [ + assert_equal [ + " Prefix Verb URI Pattern Controller#Action", "custom_assets GET /custom/assets(.:format) custom_assets#show", " blog /blog Blog::Engine", "", "Routes for Blog::Engine:", "cart GET /cart(.:format) cart#show" - ] - assert_equal expected, output + ], output end def test_cart_inspect output = draw do get '/cart', :to => 'cart#show' end - assert_equal ["cart GET /cart(.:format) cart#show"], output + + assert_equal [ + "Prefix Verb URI Pattern Controller#Action", + "cart GET /cart(.:format) cart#show" + ], output end def test_inspect_shows_custom_assets output = draw do get '/custom/assets', :to => 'custom_assets#show' end - assert_equal ["custom_assets GET /custom/assets(.:format) custom_assets#show"], output + + assert_equal [ + " Prefix Verb URI Pattern Controller#Action", + "custom_assets GET /custom/assets(.:format) custom_assets#show" + ], output end def test_inspect_routes_shows_resources_route output = draw do resources :articles end - expected = [ + + assert_equal [ + " Prefix Verb URI Pattern Controller#Action", " articles GET /articles(.:format) articles#index", " POST /articles(.:format) articles#create", " new_article GET /articles/new(.:format) articles#new", @@ -80,50 +90,74 @@ module ActionDispatch " article GET /articles/:id(.:format) articles#show", " PATCH /articles/:id(.:format) articles#update", " PUT /articles/:id(.:format) articles#update", - " DELETE /articles/:id(.:format) articles#destroy" ] - assert_equal expected, output + " DELETE /articles/:id(.:format) articles#destroy" + ], output end def test_inspect_routes_shows_root_route output = draw do root :to => 'pages#main' end - assert_equal ["root GET / pages#main"], output + + assert_equal [ + "Prefix Verb URI Pattern Controller#Action", + "root GET / pages#main" + ], output end def test_inspect_routes_shows_dynamic_action_route output = draw do get 'api/:action' => 'api' end - assert_equal [" GET /api/:action(.:format) api#:action"], output + + assert_equal [ + "Prefix Verb URI Pattern Controller#Action", + " GET /api/:action(.:format) api#:action" + ], output end def test_inspect_routes_shows_controller_and_action_only_route output = draw do get ':controller/:action' end - assert_equal [" GET /:controller/:action(.:format) :controller#:action"], output + + assert_equal [ + "Prefix Verb URI Pattern Controller#Action", + " GET /:controller/:action(.:format) :controller#:action" + ], output end def test_inspect_routes_shows_controller_and_action_route_with_constraints output = draw do get ':controller(/:action(/:id))', :id => /\d+/ end - assert_equal [" GET /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}"], output + + assert_equal [ + "Prefix Verb URI Pattern Controller#Action", + " GET /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}" + ], output end def test_rake_routes_shows_route_with_defaults output = draw do get 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'} end - assert_equal [%Q[ GET /photos/:id(.:format) photos#show {:format=>"jpg"}]], output + + assert_equal [ + "Prefix Verb URI Pattern Controller#Action", + %Q[ GET /photos/:id(.:format) photos#show {:format=>"jpg"}] + ], output end def test_rake_routes_shows_route_with_constraints output = draw do get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ end - assert_equal [" GET /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"], output + + assert_equal [ + "Prefix Verb URI Pattern Controller#Action", + " GET /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}" + ], output end class RackApp @@ -135,7 +169,11 @@ module ActionDispatch output = draw do get 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/ end - assert_equal [" GET /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"], output + + assert_equal [ + "Prefix Verb URI Pattern Controller#Action", + " 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 @@ -151,7 +189,10 @@ module ActionDispatch end end - assert_equal [" /foo #{RackApp.name} {:constraint=>( my custom constraint )}"], output + assert_equal [ + "Prefix Verb URI Pattern Controller#Action", + " /foo #{RackApp.name} {:constraint=>( my custom constraint )}" + ], output end def test_rake_routes_dont_show_app_mounted_in_assets_prefix @@ -169,9 +210,12 @@ module ActionDispatch get "/foobar" => redirect{ "/foo/bar" } end - assert_equal " foo GET /foo(.:format) redirect(301, /foo/bar) {:subdomain=>\"admin\"}", output[0] - assert_equal " bar GET /bar(.:format) redirect(307, path: /foo/bar)", output[1] - assert_equal "foobar GET /foobar(.:format) redirect(301)", output[2] + assert_equal [ + "Prefix Verb URI Pattern Controller#Action", + " foo GET /foo(.:format) redirect(301, /foo/bar) {:subdomain=>\"admin\"}", + " bar GET /bar(.:format) redirect(307, path: /foo/bar)", + "foobar GET /foobar(.:format) redirect(301)" + ], output end def test_routes_can_be_filtered @@ -180,7 +224,8 @@ module ActionDispatch resources :posts end - assert_equal [" posts GET /posts(.:format) posts#index", + assert_equal [" Prefix Verb URI Pattern Controller#Action", + " posts GET /posts(.:format) posts#index", " POST /posts(.:format) posts#create", " new_post GET /posts/new(.:format) posts#new", "edit_post GET /posts/:id/edit(.:format) posts#edit", diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 9f31ce8127..37ad9ddb6b 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1031,6 +1031,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'users/home#index', @response.body end + def test_namespace_containing_numbers + draw do + namespace :v2 do + resources :subscriptions + end + end + + get '/v2/subscriptions' + assert_equal 'v2/subscriptions#index', @response.body + assert_equal '/v2/subscriptions', v2_subscriptions_path + end + def test_articles_with_id draw do controller :articles do @@ -1134,6 +1146,33 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'api/products#list', @response.body end + def test_match_shorthand_inside_scope_with_variables_with_controller + draw do + scope ':locale' do + match 'questions/new', via: [:get] + end + end + + get '/de/questions/new' + assert_equal 'questions#new', @response.body + assert_equal 'de', @request.params[:locale] + end + + def test_match_shorthand_inside_nested_namespaces_and_scopes_with_controller + draw do + namespace :api do + namespace :v3 do + scope ':locale' do + get "products/list" + end + end + end + end + + get '/api/v3/en/products/list' + assert_equal 'api/v3/products#list', @response.body + end + def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper draw do resources :replies do @@ -2833,21 +2872,52 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest end end - DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new - DefaultScopeRoutes.draw do - namespace :admin do - resources :storage_files, :controller => "StorageFiles" - end + def draw(&block) + @app = ActionDispatch::Routing::RouteSet.new + @app.draw(&block) end - def app - DefaultScopeRoutes - end + def test_valid_controller_options_inside_namespace + draw do + namespace :admin do + resources :storage_files, :controller => "storage_files" + end + end - def test_controller_options get '/admin/storage_files' assert_equal "admin/storage_files#index", @response.body end + + def test_resources_with_valid_namespaced_controller_option + draw do + resources :storage_files, :controller => 'admin/storage_files' + end + + get 'storage_files' + assert_equal "admin/storage_files#index", @response.body + end + + def test_warn_with_ruby_constant_syntax_controller_option + e = assert_raise(ArgumentError) do + draw do + namespace :admin do + resources :storage_files, :controller => "StorageFiles" + end + end + end + + assert_match "'admin/StorageFiles' is not a supported controller name", e.message + end + + def test_warn_with_ruby_constant_syntax_namespaced_controller_option + e = assert_raise(ArgumentError) do + draw do + resources :storage_files, :controller => 'Admin::StorageFiles' + end + end + + assert_match "'Admin::StorageFiles' is not a supported controller name", e.message + end end class TestDefaultScope < ActionDispatch::IntegrationTest diff --git a/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb b/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb new file mode 100644 index 0000000000..9f1f855269 --- /dev/null +++ b/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb @@ -0,0 +1 @@ +HTML! diff --git a/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb b/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb new file mode 100644 index 0000000000..1cc8d41475 --- /dev/null +++ b/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb @@ -0,0 +1 @@ +Hello <%= name %> diff --git a/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb b/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb new file mode 100644 index 0000000000..1461b95186 --- /dev/null +++ b/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb @@ -0,0 +1 @@ +<%= render partial: 'test/_directory/partial_with_locales', locals: {'name' => 'Jane'} %> diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 82c9d383ac..11614a45dc 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -443,7 +443,8 @@ class AssetTagHelperTest < ActionView::TestCase [nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix| assert_equal 'Rails', image_alt("#{prefix}rails.png") assert_equal 'Rails', image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png") - assert_equal 'Avatar-0000', image_alt("#{prefix}avatar-0000.png") + assert_equal 'Long file name with hyphens', image_alt("#{prefix}long-file-name-with-hyphens.png") + assert_equal 'Long file name with underscores', image_alt("#{prefix}long_file_name_with_underscores.png") end end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index f11adefad8..242b56a1fd 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1510,42 +1510,42 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, date_select("post", "written_on") end - + def test_date_select_with_selected @post = Post.new @post.written_on = Date.new(2004, 6, 15) - + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} expected << "</select>\n" - + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7" selected="selected">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} expected << "</select>\n" - + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10" selected="selected">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} - + expected << "</select>\n" - + assert_dom_equal expected, date_select("post", "written_on", :selected => Date.new(2004, 07, 10)) end def test_date_select_with_selected_nil @post = Post.new @post.written_on = Date.new(2004, 6, 15) - + expected = '<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="1"/>' + "\n" - + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} expected << %{<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} expected << "</select>\n" - + expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} expected << %{<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</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} - + expected << "</select>\n" - + assert_dom_equal expected, date_select("post", "written_on", include_blank: true, discard_year: true, selected: nil) end @@ -2296,7 +2296,7 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, datetime_select("post", "updated_at", discard_year: true, selected: nil) end - + def test_datetime_select_defaults_to_time_zone_now_when_config_time_zone_is_set # The love zone is UTC+0 mytz = Class.new(ActiveSupport::TimeZone) { @@ -3160,14 +3160,14 @@ class DateHelperTest < ActionView::TestCase end def test_time_tag_with_date - date = Date.today - expected = "<time datetime=\"#{date.rfc3339}\">#{I18n.l(date, :format => :long)}</time>" + date = Date.new(2013, 2, 20) + expected = '<time datetime="2013-02-20">February 20, 2013</time>' assert_equal expected, time_tag(date) end def test_time_tag_with_time - time = Time.now - expected = "<time datetime=\"#{time.xmlschema}\">#{I18n.l(time, :format => :long)}</time>" + time = Time.new(2013, 2, 20, 0, 0, 0, '+00:00') + expected = '<time datetime="2013-02-20T00:00:00+00:00">February 20, 2013 00:00</time>' assert_equal expected, time_tag(time) end @@ -3184,8 +3184,8 @@ class DateHelperTest < ActionView::TestCase end def test_time_tag_with_different_format - time = Time.now - expected = "<time datetime=\"#{time.xmlschema}\">#{I18n.l(time, :format => :short)}</time>" + time = Time.new(2013, 2, 20, 0, 0, 0, '+00:00') + expected = '<time datetime="2013-02-20T00:00:00+00:00">20 Feb 00:00</time>' assert_equal expected, time_tag(time, :format => :short) end diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index c5efed3195..04cdd068c8 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -21,10 +21,10 @@ class FormOptionsHelperTest < ActionView::TestCase end def setup - @fake_timezones = %w(A B C D E).inject([]) do |zones, id| + @fake_timezones = %w(A B C D E).map do |id| tz = TZInfo::Timezone.loaded_zones[id] = stub(:name => id, :to_s => id) ActiveSupport::TimeZone.stubs(:[]).with(id).returns(tz) - zones << tz + tz end ActiveSupport::TimeZone.stubs(:all).returns(@fake_timezones) end @@ -351,7 +351,7 @@ class FormOptionsHelperTest < ActionView::TestCase ) end - def test_time_zone_options_no_parms + def test_time_zone_options_no_params opts = time_zone_options_for_select assert_dom_equal "<option value=\"A\">A</option>\n" + "<option value=\"B\">B</option>\n" + @@ -417,6 +417,13 @@ class FormOptionsHelperTest < ActionView::TestCase opts end + def test_time_zone_options_with_priority_zones_does_not_mutate_time_zones + original_zones = ActiveSupport::TimeZone.all.dup + zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ] + time_zone_options_for_select(nil, zones) + assert_equal original_zones, ActiveSupport::TimeZone.all + end + def test_time_zone_options_returns_html_safe_string assert time_zone_options_for_select.html_safe? end @@ -1079,11 +1086,13 @@ class FormOptionsHelperTest < ActionView::TestCase def test_time_zone_select_with_priority_zones_as_regexp @firm = Firm.new("D") + + priority_zones = /A|D/ @fake_timezones.each_with_index do |tz, i| - tz.stubs(:=~).returns(i.zero? || i == 3) + priority_zones.stubs(:===).with(tz).returns(i.zero? || i == 3) end - html = time_zone_select("firm", "time_zone", /A|D/) + html = time_zone_select("firm", "time_zone", priority_zones) assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + "<option value=\"A\">A</option>\n" + "<option value=\"D\" selected=\"selected\">D</option>" + diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index 9c49438f6a..ab84bccb56 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -41,6 +41,12 @@ class RecordTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_content_tag_for_with_array_css_class + expected = %(<tr class="record_tag_post special odd" id="record_tag_post_45"></tr>) + actual = content_tag_for(:tr, @post, class: ["special", "odd"]) + assert_dom_equal expected, actual + end + def test_content_tag_for_with_prefix_and_extra_html_options expected = %(<tr class="archived_record_tag_post special" id="archived_record_tag_post_45" style='background-color: #f0f0f0'></tr>) actual = content_tag_for(:tr, @post, :archived, class: "special", style: "background-color: #f0f0f0") diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index c7231d9cd5..acd002ce73 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -329,6 +329,27 @@ module ActionView assert_template partial: '_partial', locals: { 'second' => '2' } end + test 'raises descriptive error message when template was not rendered' do + controller.controller_path = "test" + render(template: "test/hello_world_with_partial") + e = assert_raise ActiveSupport::TestCase::Assertion do + assert_template partial: 'i_was_never_rendered', locals: { 'did_not' => 'happen' } + end + assert_match "i_was_never_rendered to be rendered but it was not.", e.message + assert_match 'Expected ["/test/partial"] to include "i_was_never_rendered"', e.message + end + + test 'specifying locals works when the partial is inside a directory with underline prefix' do + controller.controller_path = "test" + render(template: 'test/render_partial_inside_directory') + assert_template partial: 'test/_directory/_partial_with_locales', locals: { 'name' => 'Jane' } + end + + test 'specifying locals works when the partial is inside a directory without underline prefix' do + controller.controller_path = "test" + render(template: 'test/render_partial_inside_directory') + assert_template partial: 'test/_directory/partial_with_locales', locals: { 'name' => 'Jane' } + end end module AHelperWithInitialize diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index ba65349b6a..5d87c96605 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -437,6 +437,12 @@ class UrlHelperTest < ActiveSupport::TestCase ActionDispatch::Request.new(env) end + def test_current_page_with_http_head_method + @request = request_for_url("/", :method => :head) + assert current_page?(url_hash) + assert current_page?("http://www.example.com/") + end + def test_current_page_with_simple_url @request = request_for_url("/") assert current_page?(url_hash) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 09e6ede064..416922cb89 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -19,7 +19,7 @@ *Roberto Vasquez Angel* -* `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!` +* `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!`. *Renato Mascarenhas* @@ -27,9 +27,11 @@ *Rafael Mendonça França* -* Specify type of singular association during serialization *Steve Klabnik* +* Specify type of singular association during serialization. -* Fixed length validator to correctly handle nil values. Fixes #7180. + *Steve Klabnik* + +* Fixed length validator to correctly handle `nil`. Fixes #7180. *Michal Zima* @@ -53,24 +55,27 @@ *Guillermo Iguaran* -* Due to a change in builder, nil values and empty strings now generates +* Due to a change in builder, `nil` and empty strings now generate closed tags, so instead of this: <pseudonyms nil=\"true\"></pseudonyms> - It generates this: + it generates this: <pseudonyms nil=\"true\"/> *Carlos Antonio da Silva* -* Changed inclusion and exclusion validators to accept a symbol for `:in` option. +* Inclusion/exclusion validators accept a method name passed as a symbol to the + `:in` option. - This allows to use dynamic inclusion/exclusion values using methods, besides the current lambda/proc support. + This allows to use dynamic inclusion/exclusion values using methods, besides + the current lambda/proc support. *Gabriel Sobrinho* -* `AM::Validation#validates` ability to pass custom exception to `:strict` option. +* `ActiveModel::Validation#validates` ability to pass custom exception to the + `:strict` option. *Bogdan Gusiev* @@ -81,7 +86,7 @@ *Anthony Alberto* -* Changed `AM::Serializers::JSON.include_root_in_json' default value to false. +* Changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to false. Now, AM Serializers and AR objects have the same default behaviour. Fixes #6578. class User < ActiveRecord::Base; end @@ -108,16 +113,31 @@ *Francesco Rodriguez* -* Passing false hash values to `validates` will no longer enable the corresponding validators *Steve Purcell* +* Passing false hash values to `validates` will no longer enable the corresponding validators. + + *Steve Purcell* + +* `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. -* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella* + *Guillermo Iguaran* + +* `AM::Errors#to_json`: support `:full_messages` parameter. + + *Bogdan Gusiev* -* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran* +* Trim down Active Model API by removing `valid?` and `errors.full_messages`. -* `AM::Errors#to_json`: support `:full_messages` parameter *Bogdan Gusiev* + *José Valim* -* Trim down Active Model API by removing `valid?` and `errors.full_messages` *José Valim* +* When `^` or `$` are used in the regular expression provided to `validates_format_of` + and the `:multiline` option is not set to true, an exception will be raised. This is + to prevent security vulnerabilities when using `validates_format_of`. The problem is + described in detail in the Rails security guide. -* When `^` or `$` are used in the regular expression provided to `validates_format_of` and the :multiline option is not set to true, an exception will be raised. This is to prevent security vulnerabilities when using `validates_format_of`. The problem is described in detail in the Rails security guide *Jan Berdajs + Egor Homakov* + *Jan Berdajs + Egor Homakov* Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index c52e4947ae..b5562dda2e 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/array/extract_options' + module ActiveModel # == Active \Model \Callbacks # diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index f989b21140..037650e5ac 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -60,6 +60,9 @@ module ActiveModel # end # end # + # Note that the validator is initialized only once for the whole application + # lifecycle, and not on each validation run. + # # The easiest way to add custom validators for validating individual attributes # is with the convenient <tt>ActiveModel::EachValidator</tt>. # diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index cc0c3f16d2..51dcfc37d8 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -274,6 +274,13 @@ class ErrorsTest < ActiveModel::TestCase person.errors.add_on_empty :name, :message => 'custom' end + test "add_on_empty generates message with empty string value" do + person = Person.new + person.name = '' + person.errors.expects(:generate_message).with(:name, :empty, {}) + person.errors.add_on_empty :name + end + test "add_on_blank generates message" do person = Person.new person.errors.expects(:generate_message).with(:name, :blank, {}) diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index 1a40ca8efc..8b2f886cc4 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -5,13 +5,12 @@ require 'models/topic' require 'models/person' class LengthValidationTest < ActiveModel::TestCase - def teardown Topic.reset_callbacks(:validate) end def test_validates_length_of_with_allow_nil - Topic.validates_length_of( :title, :is => 5, :allow_nil => true ) + Topic.validates_length_of( :title, is: 5, allow_nil: true ) assert Topic.new("title" => "ab").invalid? assert Topic.new("title" => "").invalid? @@ -20,7 +19,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_with_allow_blank - Topic.validates_length_of( :title, :is => 5, :allow_blank => true ) + Topic.validates_length_of( :title, is: 5, allow_blank: true ) assert Topic.new("title" => "ab").invalid? assert Topic.new("title" => "").valid? @@ -29,7 +28,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_minimum - Topic.validates_length_of :title, :minimum => 5 + Topic.validates_length_of :title, minimum: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -51,13 +50,13 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum_should_allow_nil - Topic.validates_length_of :title, :maximum => 10 + Topic.validates_length_of :title, maximum: 10 t = Topic.new assert t.valid? end def test_optionally_validates_length_of_using_minimum - Topic.validates_length_of :title, :minimum => 5, :allow_nil => true + Topic.validates_length_of :title, minimum: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -67,7 +66,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum - Topic.validates_length_of :title, :maximum => 5 + Topic.validates_length_of :title, maximum: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -82,7 +81,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_maximum - Topic.validates_length_of :title, :maximum => 5, :allow_nil => true + Topic.validates_length_of :title, maximum: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -92,7 +91,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_within - Topic.validates_length_of(:title, :content, :within => 3..5) + Topic.validates_length_of(:title, :content, within: 3..5) t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long") assert t.invalid? @@ -111,7 +110,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_within_with_exclusive_range - Topic.validates_length_of(:title, :within => 4...10) + Topic.validates_length_of(:title, within: 4...10) t = Topic.new("title" => "9 chars!!") assert t.valid? @@ -125,7 +124,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_within - Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true + Topic.validates_length_of :title, :content, within: 3..5, allow_nil: true t = Topic.new('title' => 'abc', 'content' => 'abcd') assert t.valid? @@ -135,7 +134,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_is - Topic.validates_length_of :title, :is => 5 + Topic.validates_length_of :title, is: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -153,7 +152,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_is - Topic.validates_length_of :title, :is => 5, :allow_nil => true + Topic.validates_length_of :title, is: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -167,25 +166,25 @@ class LengthValidationTest < ActiveModel::TestCase bigmax = 2 ** 32 bigrange = bigmin...bigmax assert_nothing_raised do - Topic.validates_length_of :title, :is => bigmin + 5 - Topic.validates_length_of :title, :within => bigrange - Topic.validates_length_of :title, :in => bigrange - Topic.validates_length_of :title, :minimum => bigmin - Topic.validates_length_of :title, :maximum => bigmax + Topic.validates_length_of :title, is: bigmin + 5 + Topic.validates_length_of :title, within: bigrange + Topic.validates_length_of :title, in: bigrange + Topic.validates_length_of :title, minimum: bigmin + Topic.validates_length_of :title, maximum: bigmax end end def test_validates_length_of_nasty_params - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => -6) } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => 6) } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum => "a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum => "a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => "a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, is: -6) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, within: 6) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, minimum: "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, maximum: "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, within: "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, is: "a") } end def test_validates_length_of_custom_errors_for_minimum_with_message - Topic.validates_length_of( :title, :minimum => 5, :message => "boo %{count}" ) + Topic.validates_length_of( :title, minimum: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -193,7 +192,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_minimum_with_too_short - Topic.validates_length_of( :title, :minimum => 5, :too_short => "hoo %{count}" ) + Topic.validates_length_of( :title, minimum: 5, too_short: "hoo %{count}" ) t = Topic.new("title" => "uhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -201,7 +200,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_maximum_with_message - Topic.validates_length_of( :title, :maximum => 5, :message => "boo %{count}" ) + Topic.validates_length_of( :title, maximum: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -209,7 +208,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_in - Topic.validates_length_of(:title, :in => 10..20, :message => "hoo %{count}") + Topic.validates_length_of(:title, in: 10..20, message: "hoo %{count}") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -222,7 +221,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_maximum_with_too_long - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}" ) + Topic.validates_length_of( :title, maximum: 5, too_long: "hoo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -230,21 +229,21 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_both_too_short_and_too_long - Topic.validates_length_of :title, :minimum => 3, :maximum => 5, :too_short => 'too short', :too_long => 'too long' + Topic.validates_length_of :title, minimum: 3, maximum: 5, too_short: 'too short', too_long: 'too long' - t = Topic.new(:title => 'a') + t = Topic.new(title: 'a') assert t.invalid? assert t.errors[:title].any? assert_equal ['too short'], t.errors['title'] - t = Topic.new(:title => 'aaaaaa') + t = Topic.new(title: 'aaaaaa') assert t.invalid? assert t.errors[:title].any? assert_equal ['too long'], t.errors['title'] end def test_validates_length_of_custom_errors_for_is_with_message - Topic.validates_length_of( :title, :is => 5, :message => "boo %{count}" ) + Topic.validates_length_of( :title, is: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -252,7 +251,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_is_with_wrong_length - Topic.validates_length_of( :title, :is => 5, :wrong_length => "hoo %{count}" ) + Topic.validates_length_of( :title, is: 5, wrong_length: "hoo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -260,7 +259,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_minimum_utf8 - Topic.validates_length_of :title, :minimum => 5 + Topic.validates_length_of :title, minimum: 5 t = Topic.new("title" => "一二三四五", "content" => "whatever") assert t.valid? @@ -272,7 +271,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum_utf8 - Topic.validates_length_of :title, :maximum => 5 + Topic.validates_length_of :title, maximum: 5 t = Topic.new("title" => "一二三四五", "content" => "whatever") assert t.valid? @@ -284,7 +283,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_within_utf8 - Topic.validates_length_of(:title, :content, :within => 3..5) + Topic.validates_length_of(:title, :content, within: 3..5) t = Topic.new("title" => "一二", "content" => "12三四五六七") assert t.invalid? @@ -296,12 +295,12 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_within_utf8 - Topic.validates_length_of :title, :within => 3..5, :allow_nil => true + Topic.validates_length_of :title, within: 3..5, allow_nil: true - t = Topic.new(:title => "一二三四五") + t = Topic.new(title: "一二三四五") assert t.valid?, t.errors.inspect - t = Topic.new(:title => "一二三") + t = Topic.new(title: "一二三") assert t.valid?, t.errors.inspect t.title = nil @@ -309,7 +308,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_is_utf8 - Topic.validates_length_of :title, :is => 5 + Topic.validates_length_of :title, is: 5 t = Topic.new("title" => "一二345", "content" => "whatever") assert t.valid? @@ -321,9 +320,9 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_with_block - Topic.validates_length_of :content, :minimum => 5, :too_short => "Your essay must be at least %{count} words.", - :tokenizer => lambda {|str| str.scan(/\w+/) } - t = Topic.new(:content => "this content should be long enough") + Topic.validates_length_of :content, minimum: 5, too_short: "Your essay must be at least %{count} words.", + tokenizer: lambda {|str| str.scan(/\w+/) } + t = Topic.new(content: "this content should be long enough") assert t.valid? t.content = "not long enough" @@ -333,18 +332,18 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_for_fixnum - Topic.validates_length_of(:approved, :is => 4) + Topic.validates_length_of(:approved, is: 4) - t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1) + t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1) assert t.invalid? assert t.errors[:approved].any? - t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1234) + t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1234) assert t.valid? end def test_validates_length_of_for_ruby_class - Person.validates_length_of :karma, :minimum => 5 + Person.validates_length_of :karma, minimum: 5 p = Person.new p.karma = "Pix" @@ -359,7 +358,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_for_infinite_maxima - Topic.validates_length_of(:title, :within => 5..Float::INFINITY) + Topic.validates_length_of(:title, within: 5..Float::INFINITY) t = Topic.new("title" => "1234") assert t.invalid? @@ -368,7 +367,7 @@ class LengthValidationTest < ActiveModel::TestCase t.title = "12345" assert t.valid? - Topic.validates_length_of(:author_name, :maximum => Float::INFINITY) + Topic.validates_length_of(:author_name, maximum: Float::INFINITY) assert t.valid? @@ -377,13 +376,13 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum_should_not_allow_nil_when_nil_not_allowed - Topic.validates_length_of :title, :maximum => 10, :allow_nil => false + Topic.validates_length_of :title, maximum: 10, allow_nil: false t = Topic.new assert t.invalid? end def test_validates_length_of_using_maximum_should_not_allow_nil_and_empty_string_when_blank_not_allowed - Topic.validates_length_of :title, :maximum => 10, :allow_blank => false + Topic.validates_length_of :title, maximum: 10, allow_blank: false t = Topic.new assert t.invalid? @@ -392,13 +391,13 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_both_minimum_and_maximum_should_not_allow_nil - Topic.validates_length_of :title, :minimum => 5, :maximum => 10 + Topic.validates_length_of :title, minimum: 5, maximum: 10 t = Topic.new assert t.invalid? end def test_validates_length_of_using_minimum_0_should_not_allow_nil - Topic.validates_length_of :title, :minimum => 0 + Topic.validates_length_of :title, minimum: 0 t = Topic.new assert t.invalid? @@ -407,11 +406,19 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_is_0_should_not_allow_nil - Topic.validates_length_of :title, :is => 0 + Topic.validates_length_of :title, is: 0 t = Topic.new assert t.invalid? t.title = "" assert t.valid? end + + def test_validates_with_diff_in_option + Topic.validates_length_of(:title, is: 5) + Topic.validates_length_of(:title, is: 5, if: Proc.new { false } ) + + assert Topic.new("title" => "david").valid? + assert Topic.new("title" => "david2").invalid? + end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index c6a6e44724..c7085a0eaa 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,6 +1,195 @@ ## Rails 4.0.0 (unreleased) ## -* Relation#merge now only overwrites where values on the LHS of the +* Fixing issue #8345. Now throwing an error when one attempts to touch a + new object that has not yet been persisted. For instance: + + ball = Ball.new + ball.touch :updated_at # => raises error + + It is not until the ball object has been persisted that it can be touched. + This follows the behavior of update_column. + + *John Wang* + +* Preloading ordered `has_many :through` associations no longer applies + invalid ordering to the `:through` association. + Fixes #8663. + + *Yves Senn* + +* The auto explain feature has been removed. This feature was + activated by configuring `config.active_record.auto_explain_threshold_in_seconds`. + The configuration option was deprecated and has no more effect. + + You can still use `ActiveRecord::Relation#explain` to see the EXPLAIN output for + any given relation. + + *Yves Senn* + +* The `:on` option for `after_commit` and `after_rollback` now + accepts an Array of actions. + Fixes #988. + + Example: + + after_commit :update_cache on: [:create, :update] + +* Rename related indexes on `rename_table` and `rename_column`. This + does not affect indexes with custom names. + + *Yves Senn* + +* Prevent the creation of indices with too long names, which cause + internal operations to fail (sqlite3 adapter only). The method + `allowed_index_name_length` defines the length limit enforced by + rails. It's value defaults to `index_name_length` but can vary per adapter. + Fixes #8264. + + *Yves Senn* + +* Fixing issue #776. + + Memory bloat in transactions is handled by having the transaction hold only + the AR objects which it absolutely needs to know about. These are the AR + objects with callbacks (they need to be updated as soon as something in the + transaction occurs). + + All other AR objects can be updated lazily by keeping a reference to a + TransactionState object. If an AR object gets inside a transaction, then + the transaction will add its TransactionState to the AR object. When the + user makes a call to some attribute on an AR object (which has no + callbacks) associated with a transaction, the AR object will call the + sync_with_transaction_state method and make sure it is up to date with the + transaction. After it has synced with the transaction state, the AR object + will return the attribute that was requested. + + Most of the logic in the changes are used to handle multiple transactions, + in which case the AR object has to recursively follow parent pointers of + TransactionState objects. + + *John Wang* + +* Descriptive error message when the necessary AR adapter gem was not found. + Fixes #7313. + + *Yves Senn* + +* Active Record now raises an error when blank arguments are passed to query + methods for which blank arguments do not make sense. + + Example: + + Post.includes() # => raises error + + *John Wang* + +* Simplified type casting code for timezone aware attributes to use the + `in_time_zone` method if it is available. This introduces a subtle change + of behavior when using `Date` instances as they are directly converted to + `ActiveSupport::TimeWithZone` instances without first being converted to + `Time` instances. For example: + + # Rails 3.2 behavior + >> Date.today.to_time.in_time_zone + => Wed, 13 Feb 2013 07:00:00 UTC +00:00 + + # Rails 4.0 behavior + >> Date.today.in_time_zone + => Wed, 13 Feb 2013 00:00:00 UTC +00:00 + + On the plus side it now behaves the same whether you pass a `String` date + or an actual `Date` instance. For example: + + # Rails 3.2 behavior + >> Date.civil(2013, 2, 13).to_time.in_time_zone + => Wed, 13 Feb 2013 07:00:00 UTC +00:00 + >> Time.zone.parse("2013-02-13") + => Wed, 13 Feb 2013 00:00:00 UTC +00:00 + + # Rails 4.0 behavior + >> Date.civil(2013, 2, 13).in_time_zone + => Wed, 13 Feb 2013 00:00:00 UTC +00:00 + >> "2013-02-13".in_time_zone + => Wed, 13 Feb 2013 00:00:00 UTC +00:00 + + If you need the old behavior you can convert the dates to times manually. + For example: + + >> Post.new(created_at: Date.today).created_at + => Wed, 13 Feb 2013 00:00:00 UTC +00:00 + + >> Post.new(created_at: Date.today.to_time).created_at + => Wed, 13 Feb 2013 07:00:00 UTC +00:00 + + *Andrew White* + +* Preloading `has_many :through` associations with conditions won't + cache the `:through` association. This will prevent invalid + subsets to be cached. + Fixes #8423. + + Example: + + class User + has_many :posts + has_many :recent_comments, -> { where('created_at > ?', 1.week.ago) }, :through => :posts + end + + a_user = User.includes(:recent_comments).first + + # This is preloaded. + a_user.recent_comments + + # This is not preloaded, fetched now. + a_user.posts + + *Yves Senn* + +* Don't run `after_commit` callbacks when creating through an association + if saving the record fails. + + *James Miller* + +* Allow store accessors to be overrided like other attribute methods, e.g.: + + class User < ActiveRecord::Base + store :settings, accessors: [ :color, :homepage ], coder: JSON + + def color + super || 'red' + end + end + + *Sergey Nartimov* + +* Quote numeric values being compared to non-numeric columns. Otherwise, + in some database, the string column values will be coerced to a numeric + allowing 0, 0.0 or false to match any string starting with a non-digit. + + Example: + + App.where(apikey: 0) # => SELECT * FROM users WHERE apikey = '0' + + *Dylan Smith* + +* Schema dumper supports dumping the enabled database extensions to `schema.rb` + (currently only supported by postgresql). + + *Justin George* + +* The database adpters now converts the options passed thought `DATABASE_URL` + environment variable to the proper Ruby types before using. For example, SQLite requires + that the timeout value is an integer, and PostgreSQL requires that the + prepared_statements option is a boolean. These now work as expected: + + Example: + + DATABASE_URL=sqlite3://localhost/test_db?timeout=500 + DATABASE_URL=postgresql://localhost/test_db?prepared_statements=false + + *Aaron Stone + Rafael Mendonça França* + +* `Relation#merge` now only overwrites where values on the LHS of the merge. Consider: left = Person.where(age: [13, 14, 15]) @@ -55,11 +244,11 @@ *Lilibeth De La Cruz* * When `#count` is used in conjunction with `#uniq` we perform `count(:distinct => true)`. - Fix #6865. + Fixes #6865. Example: - relation.uniq.count # => SELECT COUNT(DISTINCT *) + relation.uniq.count # => SELECT COUNT(DISTINCT *) *Yves Senn + Kaspar Schiess* @@ -88,7 +277,7 @@ *John Wang* * Collection associations `#empty?` always respects builded records. - Fix #8879. + Fixes #8879. Example: @@ -98,10 +287,6 @@ *Yves Senn* -* Remove support for parsing YAML parameters from request. - - *Aaron Patterson* - * Support for PostgreSQL's `ltree` data type. *Rob Worley* @@ -127,7 +312,7 @@ * Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary. * The methods `drop_table` and `remove_column` are now reversible, as long as the necessary information is given. - The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not revertible). + The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not reversible). The method `change_table` is also reversible, as long as its block doesn't call `remove`, `change` or `change_default` * New method `reversible` makes it possible to specify code to be run when migrating up or down. @@ -181,18 +366,13 @@ *Yves Senn* * Add `ActiveRecord::Base.cache_timestamp_format` class attribute to control - the format of the timestamp value in the cache key. - This allows users to improve the precision of the cache key. + the format of the timestamp value in the cache key. Defaults to `:nsec`. Fixes #8195. *Rafael Mendonça França* -* Add `:nsec` date format. This can be used to improve the precision of cache key. - - *Jamie Gaskins* - * Session variables can be set for the `mysql`, `mysql2`, and `postgresql` adapters - in the `variables: <hash>` parameter in `database.yml`. The key-value pairs of this + in the `variables: <hash>` parameter in `config/database.yml`. The key-value pairs of this hash will be sent in a `SET key = value` query on new database connections. See also: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html http://www.postgresql.org/docs/8.3/static/sql-set.html @@ -215,7 +395,7 @@ to the update query. class User < ActiveRecord::Base - default_scope where(active: true) + default_scope -> { where(active: true) } end user = User.first @@ -352,11 +532,6 @@ *kennyj* -* Added `#none!` method for mutating `ActiveRecord::Relation` objects to a NullRelation. - It acts like `#none` but modifies relation in place. - - *Juanjo Bazán* - * Fix bug where `update_columns` and `update_column` would not let you update the primary key column. *Henrik Nyh* @@ -579,7 +754,7 @@ After: - #=> SELECT * FROM users WHERE 1 = 2; + #=> SELECT * FROM users WHERE 1=0; *Damien Mathieu* @@ -601,7 +776,7 @@ *Matt Jones* -* Accept belongs_to (including polymorphic) association keys in queries. +* Accept `belongs_to` (including polymorphic) association keys in queries. The following queries are now equivalent: @@ -1134,13 +1309,6 @@ *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 - object - - *Jon Leighton* - * Added `#find_by` and `#find_by!` to mirror the functionality provided by dynamic finders in a way that allows dynamic input more easily: diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS.rdoc index bdd8834dcb..bdd8834dcb 100644 --- a/activerecord/RUNNING_UNIT_TESTS +++ b/activerecord/RUNNING_UNIT_TESTS.rdoc diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 06bdabfced..513d1012ba 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -965,8 +965,8 @@ module ActiveRecord # For +has_and_belongs_to_many+, <tt>delete</tt> and <tt>destroy</tt> are the same: they # cause the records in the join table to be removed. # - # For +has_many+, <tt>destroy</tt> will always call the <tt>destroy</tt> method of the - # record(s) being removed so that callbacks are run. However <tt>delete</tt> will either + # For +has_many+, <tt>destroy</tt> and <tt>destory_all</tt> will always call the <tt>destroy</tt> method of the + # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or # if no <tt>:dependent</tt> option is given, then it will follow the default strategy. # The default strategy is <tt>:nullify</tt> (set the foreign keys to <tt>nil</tt>), except for diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index cd366ac8b7..f40368cfeb 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -68,7 +68,7 @@ module ActiveRecord remove_duplicate_results!(base, records, association) end when Hash - associations.keys.each do |name| + associations.each_key do |name| reflection = base.reflections[name] remove_uniq_by_reflection(reflection, records) diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 1c1ba11c44..c4b50ab306 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -31,7 +31,8 @@ module ActiveRecord through_records = Array.wrap(owner.send(through_reflection.name)) # Dont cache the association - we would only be caching a subset - if reflection.options[:source_type] && through_reflection.collection? + if (through_scope != through_reflection.klass.unscoped) || + (reflection.options[:source_type] && through_reflection.collection?) owner.association(through_reflection.name).reset end @@ -46,12 +47,12 @@ module ActiveRecord through_scope.where! reflection.foreign_type => options[:source_type] else unless reflection_scope.where_values.empty? - through_scope.includes_values = reflection_scope.values[:includes] || options[:source] + through_scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) through_scope.where_values = reflection_scope.values[:where] end - through_scope.order! reflection_scope.values[:order] through_scope.references! reflection_scope.values[:references] + through_scope.order! reflection_scope.values[:order] if through_scope.eager_loading? end through_scope diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 310f1b6e75..3e454b713a 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -8,27 +8,32 @@ module ActiveRecord # Returns this record's primary key value wrapped in an Array if one is # available. def to_key + sync_with_transaction_state key = self.id [key] if key end # Returns the primary key value. def id + sync_with_transaction_state read_attribute(self.class.primary_key) end # Sets the primary key value. def id=(value) + sync_with_transaction_state write_attribute(self.class.primary_key, value) if self.class.primary_key end # Queries the primary key value. def id? + sync_with_transaction_state query_attribute(self.class.primary_key) end # Returns the primary key value before type cast. def id_before_type_cast + sync_with_transaction_state read_attribute_before_type_cast(self.class.primary_key) end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 3c03cce838..506f5d75f9 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -74,7 +74,7 @@ module ActiveRecord # to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) # If it's cached, just return it - # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/3552829. + # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829. name = attr_name.to_s @attributes_cache[name] || @attributes_cache.fetch(name) { column = @columns_hash.fetch(name) { 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 847d9da6e6..1754e424b8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -577,10 +577,10 @@ module ActiveRecord # When a connection is established or removed, we invalidate the cache. # # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil. - # However, benchmarking (https://gist.github.com/3552829) showed that #fetch is - # significantly slower than #[]. So in the nil case, no caching will take place, - # but that's ok since the nil case is not the common one that we wish to optimise - # for. + # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that + # #fetch is significantly slower than #[]. So in the nil case, no caching will + # take place, but that's ok since the nil case is not the common one that we wish + # to optimise for. def retrieve_connection_pool(klass) class_to_pool[klass.name] ||= begin until pool = pool_for(klass) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb index 30ccb8f0a4..2859fb31e8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -17,6 +17,15 @@ module ActiveRecord 64 end + # Returns the maximum allowed length for an index name. This + # limit is enforced by rails and Is less than or equal to + # <tt>index_name_length</tt>. The gap between + # <tt>index_name_length</tt> is to allow internal rails + # opreations to use prefixes in temporary opreations. + def allowed_index_name_length + index_name_length + end + # Returns the maximum length of an index name. def index_name_length 64 diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index be6fda95b4..41e07fbda9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -9,9 +9,9 @@ module ActiveRecord def dirties_query_cache(base, *method_names) method_names.each do |method_name| base.class_eval <<-end_code, __FILE__, __LINE__ + 1 - def #{method_name}(*) # def update_with_query_dirty(*args) + def #{method_name}(*) # def update_with_query_dirty(*) clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled - super # update_without_query_dirty(*args) + super # super end # end end_code end @@ -85,6 +85,8 @@ module ActiveRecord end end + # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such + # queries should not be cached. def locked?(arel) arel.respond_to?(:locked) && arel.locked end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index d18b9c991f..aec4654eee 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -25,13 +25,19 @@ module ActiveRecord when true, false if column && column.type == :integer value ? '1' : '0' + elsif column && [:text, :string, :binary].include?(column.type) + value ? "'1'" : "'0'" else value ? quoted_true : quoted_false end # BigDecimals need to be put in a non-normalized form and quoted. when nil then "NULL" - when BigDecimal then value.to_s('F') - when Numeric, ActiveSupport::Duration then value.to_s + when Numeric, ActiveSupport::Duration + value = BigDecimal === value ? value.to_s('F') : value.to_s + if column && ![:integer, :float, :decimal].include?(column.type) + value = "'#{value}'" + end + value when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value.to_s}'" 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 b1ec33d06c..f758e19a4f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -252,15 +252,12 @@ module ActiveRecord self end - %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| - class_eval <<-EOV, __FILE__, __LINE__ + 1 - def #{column_type}(*args) # def string(*args) - options = args.extract_options! # options = args.extract_options! - column_names = args # column_names = args - type = :'#{column_type}' # type = :string - column_names.each { |name| column(name, type, options) } # column_names.each { |name| column(name, type, options) } - end # end - EOV + [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| + define_method column_type do |*args| + options = args.extract_options! + column_names = args + column_names.each { |name| column(name, column_type, options) } + end end # Adds index options to the indexes hash, keyed by column name @@ -486,15 +483,13 @@ module ActiveRecord # # t.string(:goat) # t.string(:goat, :sheep) - %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| - class_eval <<-EOV, __FILE__, __LINE__ + 1 - def #{column_type}(*args) # def string(*args) - options = args.extract_options! # options = args.extract_options! - args.each do |name| # column_names.each do |name| - @base.add_column(@table_name, name, :#{column_type}, options) # @base.add_column(@table_name, name, :string, options) - end # end - end # end - EOV + [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| + define_method column_type do |*args| + options = args.extract_options! + args.each do |name| + @base.add_column(@table_name, name, column_type, options) + end + end end private 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 cdc8433185..9bae880024 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -192,6 +192,14 @@ module ActiveRecord # Set to true to drop the table before creating it. # Defaults to false. # + # Note that +create_join_table+ does not create any indices by default; you can use + # its block form to do so yourself: + # + # create_join_table :products, :categories do |t| + # t.index :products + # t.index :categories + # end + # # ====== Add a backend specific option to the generated SQL (MySQL) # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8') # generates: @@ -647,10 +655,11 @@ module ActiveRecord index_name = index_name(table_name, column: column_names) if Hash === options # legacy support, since this param was a string - options.assert_valid_keys(:unique, :order, :name, :where, :length) + options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal) index_type = options[:unique] ? "UNIQUE" : "" index_name = options[:name].to_s if options.key?(:name) + max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length if supports_partial_index? index_options = options[:where] ? " WHERE #{options[:where]}" : "" @@ -665,10 +674,11 @@ module ActiveRecord end index_type = options + max_index_length = allowed_index_name_length end - if index_name.length > index_name_length - raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters" + if index_name.length > max_index_length + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" end if index_name_exists?(table_name, index_name, false) raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" @@ -694,6 +704,28 @@ module ActiveRecord column_names.map {|column_name| quote_column_name(column_name) } end + def rename_table_indexes(table_name, new_name) + indexes(new_name).each do |index| + generated_index_name = index_name(table_name, column: index.columns) + if generated_index_name == index.name + rename_index new_name, generated_index_name, index_name(new_name, column: index.columns) + end + end + end + + def rename_column_indexes(table_name, column_name, new_column_name) + column_name, new_column_name = column_name.to_s, new_column_name.to_s + indexes(table_name).each do |index| + next unless index.columns.include?(new_column_name) + old_columns = index.columns.dup + old_columns[old_columns.index(new_column_name)] = column_name + generated_index_name = index_name(table_name, column: old_columns) + if generated_index_name == index.name + rename_index table_name, generated_index_name, index_name(table_name, column: index.columns) + end + end + end + private def table_definition TableDefinition.new(self) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 3ecef96b10..73c80a3220 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -5,7 +5,7 @@ module ActiveRecord def initialize(connection) @connection = connection - @state = TransactionState.new + @state = TransactionState.new end def state @@ -14,11 +14,13 @@ module ActiveRecord end class TransactionState + attr_accessor :parent VALID_STATES = Set.new([:committed, :rolledback, nil]) def initialize(state = nil) @state = state + @parent = nil end def committed? @@ -116,7 +118,11 @@ module ActiveRecord end def add_record(record) - records << record + if record.has_transactional_callbacks? + records << record + else + record.set_transaction_state(@state) + end end def rollback_records @@ -188,8 +194,9 @@ module ActiveRecord end def perform_commit + @state.set_state(:committed) + @state.parent = parent.state connection.release_savepoint - records.each { |r| parent.add_record(r) } end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index cbb6869e66..ff9de712bc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -61,12 +61,30 @@ module ActiveRecord include MonitorMixin include ColumnDumper + SIMPLE_INT = /\A\d+\z/ + define_callbacks :checkout, :checkin attr_accessor :visitor, :pool attr_reader :schema_cache, :last_use, :in_use, :logger alias :in_use? :in_use + def self.type_cast_config_to_integer(config) + if config =~ SIMPLE_INT + config.to_i + else + config + end + end + + def self.type_cast_config_to_boolean(config) + if config == "false" + false + else + config + end + end + def initialize(connection, logger = nil, pool = nil) #:nodoc: super() @@ -171,10 +189,22 @@ module ActiveRecord false end + # Does this adapter support database extensions? As of this writing only + # postgresql does. + def supports_extensions? + false + end + + # A list of extensions, to be filled in by adapters that support them. At + # the moment only postgresql does. + def extensions + [] + end + # QUOTING ================================================== # Returns a bind substitution value given a +column+ and list of current - # +binds+ + # +binds+. def substitute_at(column, index) Arel::Nodes::BindParam.new '?' 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 52b0b3fe79..5480204511 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -31,7 +31,7 @@ module ActiveRecord return false if blob_or_text_column? #mysql forbids defaults on blob and text columns super end - + def blob_or_text_column? sql_type =~ /blob/i || type == :text end @@ -140,7 +140,7 @@ module ActiveRecord @connection_options, @config = connection_options, config @quoted_column_names, @quoted_table_names = {}, {} - if config.fetch(:prepared_statements) { true } + if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @visitor = Arel::Visitors::MySQL.new self else @visitor = BindSubstitution.new self @@ -212,8 +212,6 @@ module ActiveRecord 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] "x'#{s}'" - elsif value.kind_of?(BigDecimal) - value.to_s("F") else super end @@ -470,6 +468,7 @@ module ActiveRecord # rename_table('octopuses', 'octopi') def rename_table(table_name, new_name) execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" + rename_table_indexes(table_name, new_name) end def add_column(table_name, column_name, type, options = {}) @@ -497,6 +496,7 @@ module ActiveRecord def rename_column(table_name, column_name, new_column_name) #:nodoc: execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}") + rename_column_indexes(table_name, column_name, new_column_name) end # Maps logical Rails types to MySQL-specific data types. @@ -581,7 +581,7 @@ module ActiveRecord end def strict_mode? - @config.fetch(:strict, true) + self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) end protected @@ -722,7 +722,7 @@ module ActiveRecord # Increase timeout so the server doesn't disconnect us. wait_timeout = @config[:wait_timeout] wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum) - variables[:wait_timeout] = wait_timeout + variables[:wait_timeout] = self.class.type_cast_config_to_integer(wait_timeout) # Make MySQL reject illegal values rather than truncating or blanking them, see # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables @@ -749,7 +749,6 @@ module ActiveRecord # ...and send them all in one query execute("SET #{encoding} #{variable_assignments}", :skip_logging) end - end end end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 747331f3a1..a4b3a0c584 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -74,12 +74,13 @@ module ActiveRecord def type_cast_for_write(value) return value unless number? - if value == false + case value + when FalseClass 0 - elsif value == true + when TrueClass 1 - elsif value.is_a?(String) && value.blank? - nil + when String + value.presence else value end diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 09250d3c01..2c683fc3ac 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -38,7 +38,7 @@ module ActiveRecord private def resolve_string_connection(spec) # :nodoc: hash = configurations.fetch(spec) do |k| - connection_url_to_hash(k) + self.class.connection_url_to_hash(k) end raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash @@ -51,10 +51,13 @@ module ActiveRecord raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) + path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter" begin - require "active_record/connection_adapters/#{spec[:adapter]}_adapter" + require path_to_adapter + rescue Gem::LoadError => e + raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile." rescue LoadError => e - raise LoadError, "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e.message})", e.backtrace + raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql', 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace end adapter_method = "#{spec[:adapter]}_connection" @@ -62,7 +65,7 @@ module ActiveRecord ConnectionSpecification.new(spec, adapter_method) end - def connection_url_to_hash(url) # :nodoc: + def self.connection_url_to_hash(url) # :nodoc: config = URI.parse url adapter = config.scheme adapter = "postgresql" if adapter == "postgres" @@ -72,13 +75,19 @@ module ActiveRecord :port => config.port, :database => config.path.sub(%r{^/},""), :host => config.host } + spec.reject!{ |_,value| value.blank? } + uri_parser = URI::Parser.new + spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) } + if config.query options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys + spec.merge!(options) end + spec end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 631f646f58..7544c2a783 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -126,7 +126,7 @@ module ActiveRecord def initialize(connection, logger, connection_options, config) super @statements = StatementPool.new(@connection, - config.fetch(:statement_limit) { 1000 }) + self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) @client_encoding = nil connect end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index d90b9283ef..e09319890a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -84,7 +84,7 @@ module ActiveRecord @subtype = subtype end - def exctract_bounds(value) + def extract_bounds(value) from, to = value[1..-2].split(',') { from: (value[1] == ',' || from == '-infinity') ? infinity(:negative => true) : from, @@ -110,7 +110,7 @@ module ActiveRecord return if value.nil? || value == 'empty' return value if value.is_a?(::Range) - extracted = exctract_bounds(value) + extracted = extract_bounds(value) case @subtype when :date @@ -239,6 +239,10 @@ module ActiveRecord @mapping[oid] end + def clear + @mapping.clear + end + def key?(oid) @mapping.key? oid end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 73ca2c8e61..3bc61c5e0c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -321,14 +321,16 @@ module ActiveRecord # # Example: # rename_table('octopuses', 'octopi') - def rename_table(name, new_name) + def rename_table(table_name, new_name) clear_cache! - execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" + execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}" pk, seq = pk_and_sequence_for(new_name) - if seq == "#{name}_#{pk}_seq" + if seq == "#{table_name}_#{pk}_seq" new_seq = "#{new_name}_#{pk}_seq" execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}" end + + rename_table_indexes(table_name, new_name) end # Adds a new column to the named table. @@ -370,6 +372,7 @@ module ActiveRecord def rename_column(table_name, column_name, new_column_name) clear_cache! execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}" + rename_column_indexes(table_name, column_name, new_column_name) end def remove_index!(table_name, index_name) #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 209553b26e..2bb2557efd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -478,7 +478,7 @@ module ActiveRecord def initialize(connection, logger, connection_parameters, config) super(connection, logger) - if config.fetch(:prepared_statements) { true } + if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @visitor = Arel::Visitors::PostgreSQL.new self else @visitor = BindSubstitution.new self @@ -492,7 +492,7 @@ module ActiveRecord connect @statements = StatementPool.new @connection, - config.fetch(:statement_limit) { 1000 } + self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }) if postgresql_version < 80200 raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" @@ -500,7 +500,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 + @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true end # Clears the prepared statements cache. @@ -575,11 +575,45 @@ module ActiveRecord true end + # Returns true if pg > 9.2 + def supports_extensions? + postgresql_version >= 90200 + end + # Range datatypes weren't introduced until PostgreSQL 9.2 def supports_ranges? postgresql_version >= 90200 end + def enable_extension(name) + exec_query("CREATE EXTENSION IF NOT EXISTS #{name}").tap { + reload_type_map + } + end + + def disable_extension(name) + exec_query("DROP EXTENSION IF EXISTS #{name} CASCADE").tap { + reload_type_map + } + end + + def extension_enabled?(name) + if supports_extensions? + res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL)", + 'SCHEMA' + res.column_types['exists'].type_cast res.rows.first.first + end + end + + def extensions + if supports_extensions? + res = exec_query "SELECT extname from pg_extension", "SCHEMA" + res.rows.map { |r| res.column_types['extname'].type_cast r.first } + else + super + end + end + # Returns the configured supported identifier length supported by PostgreSQL def table_alias_length @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i @@ -645,6 +679,11 @@ module ActiveRecord private + def reload_type_map + OID::TYPE_MAP.clear + initialize_type_map + end + def initialize_type_map result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA') leaves, nodes = result.partition { |row| row['typelem'] == '0' } diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 7bead4bde9..981c4c96a0 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -26,7 +26,7 @@ module ActiveRecord :results_as_hash => true ) - db.busy_timeout(config[:timeout]) if config[:timeout] + db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout] ConnectionAdapters::SQLite3Adapter.new(db, logger, config) end @@ -107,10 +107,10 @@ module ActiveRecord @active = nil @statements = StatementPool.new(@connection, - config.fetch(:statement_limit) { 1000 }) + self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) @config = config - if config.fetch(:prepared_statements) { true } + if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @visitor = Arel::Visitors::SQLite.new self else @visitor = BindSubstitution.new self @@ -187,6 +187,13 @@ module ActiveRecord true end + # Returns 62. SQLite supports index names up to 64 + # characters. The rest is used by rails internally to perform + # temporary rename operations + def allowed_index_name_length + index_name_length - 2 + end + def native_database_types #:nodoc: { :primary_key => default_primary_key_type, @@ -428,8 +435,9 @@ module ActiveRecord # # 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)}" + def rename_table(table_name, new_name) + exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}" + rename_table_indexes(table_name, new_name) end # See: http://www.sqlite.org/lang_altertable.html @@ -488,6 +496,7 @@ module ActiveRecord raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}" end alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s}) + rename_column_indexes(table_name, column_name, new_column_name) end protected @@ -502,7 +511,7 @@ module ActiveRecord end def alter_table(table_name, options = {}) #:nodoc: - altered_table_name = "altered_#{table_name}" + altered_table_name = "a#{table_name}" caller = lambda {|definition| yield definition if block_given?} transaction do @@ -546,10 +555,10 @@ module ActiveRecord 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] + if to == "a#{from}" + name = "t#{name}" + elsif from == "a#{to}" + name = name[1..-1] end to_column_names = columns(to).map { |c| c.name } @@ -559,7 +568,7 @@ module ActiveRecord unless columns.empty? # index name can't be the same - opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_") } + opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true } opts[:unique] = true if index.unique add_index(to, columns, opts) end @@ -602,7 +611,6 @@ module ActiveRecord super end end - end end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 63a1197a56..6510433056 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -347,8 +347,54 @@ module ActiveRecord Hash[methods.map { |method| [method, public_send(method)] }].with_indifferent_access end + def set_transaction_state(state) # :nodoc: + @transaction_state = state + end + + def has_transactional_callbacks? # :nodoc: + !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_create_callbacks.empty? + end + private + # Updates the attributes on this particular ActiveRecord object so that + # if it is associated with a transaction, then the state of the AR object + # will be updated to reflect the current state of the transaction + # + # The @transaction_state variable stores the states of the associated + # transaction. This relies on the fact that a transaction can only be in + # one rollback or commit (otherwise a list of states would be required) + # Each AR object inside of a transaction carries that transaction's + # TransactionState. + # + # This method checks to see if the ActiveRecord object's state reflects + # the TransactionState, and rolls back or commits the ActiveRecord object + # as appropriate. + # + # Since ActiveRecord objects can be inside multiple transactions, this + # method recursively goes through the parent of the TransactionState and + # checks if the ActiveRecord object reflects the state of the object. + def sync_with_transaction_state + update_attributes_from_transaction_state(@transaction_state, 0) + end + + def update_attributes_from_transaction_state(transaction_state, depth) + if transaction_state && !has_transactional_callbacks? + unless @reflects_state[depth] + if transaction_state.committed? + committed! + elsif transaction_state.rolledback? + rolledback! + end + @reflects_state[depth] = true + end + + if transaction_state.parent && !@reflects_state[depth+1] + update_attributes_from_transaction_state(transaction_state.parent, depth+1) + end + end + end + # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements # of the array, and then rescues from the possible NoMethodError. If those elements are # ActiveRecord::Base's, then this triggers the various method_missing's that we have, @@ -376,7 +422,8 @@ module ActiveRecord @new_record = true @txn = nil @_start_transaction_state = {} - @transaction = nil + @transaction_state = nil + @reflects_state = [false] end end end diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index 70683eb731..b2a9a54af1 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -2,43 +2,7 @@ require 'active_support/lazy_load_hooks' module ActiveRecord module Explain - def self.extended(base) - base.mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false - end - - # If the database adapter supports explain and auto explain is enabled, - # this method triggers EXPLAIN logging for the queries triggered by the - # block if it takes more than the threshold as a whole. That is, the - # threshold is not checked against each individual query, but against the - # duration of the entire block. This approach is convenient for relations. - - # - # The available_queries_for_explain thread variable collects the queries - # to be explained. If the value is nil, it means queries are not being - # currently collected. A false value indicates collecting is turned - # off. Otherwise it is an array of queries. - def logging_query_plan # :nodoc: - return yield unless logger - - threshold = auto_explain_threshold_in_seconds - current = Thread.current - if connection.supports_explain? && threshold && current[:available_queries_for_explain].nil? - begin - queries = current[:available_queries_for_explain] = [] - start = Time.now - result = yield - logger.warn(exec_explain(queries)) if Time.now - start > threshold - result - ensure - current[:available_queries_for_explain] = nil - end - else - yield - end - end - - # Relation#explain needs to be able to collect the queries regardless of - # whether auto explain is enabled. This method serves that purpose. + # Relation#explain needs to be able to collect the queries. def collecting_queries_for_explain # :nodoc: current = Thread.current original, current[:available_queries_for_explain] = current[:available_queries_for_explain], [] @@ -68,20 +32,5 @@ module ActiveRecord end str end - - # Silences automatic EXPLAIN logging for the duration of the block. - # - # This has high priority, no EXPLAINs will be run even if downwards - # the threshold is set to 0. - # - # As the name of the method suggests this only applies to automatic - # EXPLAINs, manual calls to <tt>ActiveRecord::Relation#explain</tt> run. - def silence_auto_explain - current = Thread.current - original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false - yield - ensure - current[:available_queries_for_explain] = original - end end end diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb index 7f877a6471..32d35f0ec1 100644 --- a/activerecord/lib/active_record/integration.rb +++ b/activerecord/lib/active_record/integration.rb @@ -5,8 +5,10 @@ module ActiveRecord included do ## # :singleton-method: - # Indicates the format used to generate the timestamp format in the cache key. - # This is +:number+, by default. + # Indicates the format used to generate the timestamp in the cache key. + # Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>. + # + # This is +:nsec+, by default. class_attribute :cache_timestamp_format, :instance_writer => false self.cache_timestamp_format = :nsec end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 0022cfcd46..57ae78275d 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -69,11 +69,13 @@ module ActiveRecord # Returns true if this object hasn't been saved yet -- that is, a record # for the object doesn't exist in the data store yet; otherwise, returns false. def new_record? + sync_with_transaction_state @new_record end # Returns true if this object has been destroyed, otherwise returns false. def destroyed? + sync_with_transaction_state @destroyed end @@ -220,7 +222,7 @@ module ActiveRecord save end end - + alias update_attributes update # Updates its receiver just like +update+ but calls <tt>save!</tt> instead @@ -233,7 +235,7 @@ module ActiveRecord save! end end - + alias update_attributes! update! # Equivalent to <code>update_columns(name => value)</code>. @@ -366,6 +368,8 @@ module ActiveRecord # # triggers @brake.car.touch and @brake.car.corporation.touch # @brake.touch def touch(name = nil) + raise ActiveRecordError, "can not touch on a new record object" unless persisted? + attributes = timestamp_attributes_for_update_in_model attributes << name if name diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 5ddcaee6be..e04a3d0976 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -1,4 +1,3 @@ - module ActiveRecord module Querying delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :to => :all @@ -34,18 +33,16 @@ module ActiveRecord # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date] # # => [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...] def find_by_sql(sql, binds = []) - logging_query_plan do - result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds) - column_types = {} - - if result_set.respond_to? :column_types - column_types = result_set.column_types - else - ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`" - end + result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds) + column_types = {} - result_set.map { |record| instantiate(record, column_types) } + if result_set.respond_to? :column_types + column_types = result_set.column_types + else + ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`" end + + result_set.map { |record| instantiate(record, column_types) } end # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. @@ -58,10 +55,8 @@ module ActiveRecord # # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id" def count_by_sql(sql) - logging_query_plan do - sql = sanitize_conditions(sql) - connection.select_value(sql, "#{name} Count").to_i - end + sql = sanitize_conditions(sql) + connection.select_value(sql, "#{name} Count").to_i end end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index aceb70bc45..64eac3aca7 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -112,7 +112,18 @@ module ActiveRecord `config/application.rb` file and any `mass_assignment_sanitizer` options from your `config/environments/*.rb` files. - See http://guides.rubyonrails.org/security.html#mass-assignment for more information + See http://guides.rubyonrails.org/security.html#mass-assignment for more information. + EOF + end + + unless app.config.active_record.delete(:auto_explain_threshold_in_seconds).nil? + ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, [] + The Active Record auto explain feature has been removed. + + To disable this message remove the `active_record.auto_explain_threshold_in_seconds` + option from the `config/environments/*.rb` config file. + + See http://guides.rubyonrails.org/4_0_release_notes.html for more information. EOF end @@ -124,7 +135,7 @@ module ActiveRecord To disable this message remove the `observers` option from your `config/application.rb` or from your initializers. - See http://guides.rubyonrails.org/4_0_release_notes.html for more information + See http://guides.rubyonrails.org/4_0_release_notes.html for more information. EOF end ensure @@ -141,20 +152,11 @@ module ActiveRecord # and then establishes the connection. initializer "active_record.initialize_database" do |app| ActiveSupport.on_load(:active_record) do - unless ENV['DATABASE_URL'] - self.configurations = app.config.database_configuration - end + self.configurations = app.config.database_configuration establish_connection end end - initializer "active_record.validate_explain_support" do |app| - if app.config.active_record[:auto_explain_threshold_in_seconds] && - !ActiveRecord::Base.connection.supports_explain? - warn "auto_explain_threshold_in_seconds is set but will be ignored because your adapter does not support this feature. Please unset the configuration to avoid this warning." - end - end - # Expose database runtime to controller for logging. initializer "active_record.log_runtime" do |app| require "active_record/railties/controller_runtime" diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 259d0ff12b..f36af7182f 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -284,8 +284,6 @@ db_namespace = namespace :db do filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") current_config = ActiveRecord::Tasks::DatabaseTasks.current_config case current_config['adapter'] - when /mysql/, /postgresql/, /sqlite/ - ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) when 'oci', 'oracle' ActiveRecord::Base.establish_connection(current_config) File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump } @@ -296,7 +294,7 @@ db_namespace = namespace :db do db_string = firebird_db_string(current_config) sh "isql -a #{db_string} > #{filename}" else - raise "Task not supported by '#{current_config["adapter"]}'" + ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) end if ActiveRecord::Base.connection.supports_migrations? @@ -312,8 +310,6 @@ db_namespace = namespace :db do current_config = ActiveRecord::Tasks::DatabaseTasks.current_config filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") case current_config['adapter'] - when /mysql/, /postgresql/, /sqlite/ - ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename) when 'sqlserver' `sqlcmd -S #{current_config['host']} -d #{current_config['database']} -U #{current_config['username']} -P #{current_config['password']} -i #{filename}` when 'oci', 'oracle' @@ -326,7 +322,7 @@ db_namespace = namespace :db do db_string = firebird_db_string(current_config) sh "isql -i #{filename} #{db_string}" else - raise "Task not supported by '#{current_config['adapter']}'" + ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename) end end @@ -384,8 +380,6 @@ db_namespace = namespace :db do task :purge => [:environment, :load_config] do abcs = ActiveRecord::Base.configurations case abcs['test']['adapter'] - when /mysql/, /postgresql/, /sqlite/ - ActiveRecord::Tasks::DatabaseTasks.purge abcs['test'] when 'sqlserver' test = abcs.deep_dup['test'] test_database = test['database'] @@ -401,7 +395,7 @@ db_namespace = namespace :db do ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.recreate_database! else - raise "Task not supported by '#{abcs['test']['adapter']}'" + ActiveRecord::Tasks::DatabaseTasks.purge abcs['test'] end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index bcfcb061f2..0995750ecd 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -1,4 +1,3 @@ - module ActiveRecord # = Active Record Reflection module Reflection # :nodoc: diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 0053530f73..bc50802c4a 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -444,17 +444,7 @@ module ActiveRecord # # Post.where(published: true).load # => #<ActiveRecord::Relation> def load - unless loaded? - # We monitor here the entire execution rather than individual SELECTs - # because from the point of view of the user fetching the records of a - # relation is a single unit of work. You want to know if this call takes - # too long, not if the individual queries take too long. - # - # It could be the case that none of the queries involved surpass the - # threshold, and at the same time the sum of them all does. The user - # should get a query plan logged in that case. - logging_query_plan { exec_queries } - end + exec_queries unless loaded? self end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index f10c290755..7f95181c67 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -13,6 +13,12 @@ module ActiveRecord # # Person.count(:age, distinct: true) # # => counts the number of different age values + # + # If +count+ is used with +group+, it returns a Hash whose keys represent the aggregated column, + # and the values are the respective amounts: + # + # Person.group(:city).count + # # => { 'Rome' => 5, 'Paris' => 3 } def count(column_name = nil, options = {}) column_name, options = nil, column_name if column_name.is_a?(Hash) calculate(:count, column_name, options) diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 615309964c..00a506c3a7 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -12,7 +12,7 @@ module ActiveRecord delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, - :connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass + :connection, :columns_hash, :to => :klass module ClassSpecificRelation extend ActiveSupport::Concern diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 7ddaea1bb0..14520381c9 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -58,7 +58,7 @@ module ActiveRecord # order. The order will depend on the database implementation. # If an order is supplied it will be respected. # - # Person.take # returns an object fetched by SELECT * FROM people + # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1 # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5 # Person.where(["name LIKE '%?'", name]).take def take(limit = nil) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 537ebbef28..5cd015eba7 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -8,7 +8,7 @@ module ActiveRecord if value.is_a?(Hash) if value.empty? - queries << '1 = 2' + queries << '1=0' else table = Arel::Table.new(column, default_table.engine) association = klass.reflect_on_association(column.to_sym) @@ -98,6 +98,11 @@ module ActiveRecord when Class # FIXME: I think we need to deprecate this behavior attribute.eq(value.name) + when Integer, ActiveSupport::Duration + # Arel treats integers as literals, but they should be quoted when compared with strings + table = attribute.relation + column = table.engine.connection.schema_cache.columns_hash(table.name)[attribute.name.to_s] + attribute.eq(Arel::Nodes::SqlLiteral.new(table.engine.connection.quote(value, column))) else attribute.eq(value) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 42849d6bc9..225677085f 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -11,11 +11,11 @@ module ActiveRecord @scope = scope end - # Returns a new relation expressing WHERE + NOT condition - # according to the conditions in the arguments. + # Returns a new relation expressing WHERE + NOT condition according to + # the conditions in the arguments. # - # #not accepts conditions in one of these formats: String, Array, Hash. - # See #where for more details on each format. + # +not+ accepts conditions as a string, array, or hash. See #where for + # more details on each format. # # User.where.not("name = 'Jon'") # # SELECT * FROM users WHERE NOT (name = 'Jon') @@ -31,6 +31,10 @@ module ActiveRecord # # User.where.not(name: %w(Ko1 Nobu)) # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu') + # + # User.where.not(name: "Jon", role: "admin") + # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin' + # def not(opts, *rest) where_value = @scope.send(:build_where, opts, rest).map do |rel| case rel @@ -108,7 +112,8 @@ module ActiveRecord # # User.includes(:posts).where('posts.name = ?', 'example').references(:posts) def includes(*args) - args.empty? ? self : spawn.includes!(*args) + check_if_method_has_arguments!("includes", args) + spawn.includes!(*args) end def includes!(*args) # :nodoc: @@ -125,7 +130,8 @@ module ActiveRecord # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = # "users"."id" def eager_load(*args) - args.blank? ? self : spawn.eager_load!(*args) + check_if_method_has_arguments!("eager_load", args) + spawn.eager_load!(*args) end def eager_load!(*args) # :nodoc: @@ -138,7 +144,8 @@ module ActiveRecord # User.preload(:posts) # => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3) def preload(*args) - args.blank? ? self : spawn.preload!(*args) + check_if_method_has_arguments!("preload", args) + spawn.preload!(*args) end def preload!(*args) # :nodoc: @@ -155,7 +162,8 @@ module ActiveRecord # User.includes(:posts).where("posts.name = 'foo'").references(:posts) # # => Query now knows the string references posts, so adds a JOIN def references(*args) - args.blank? ? self : spawn.references!(*args) + check_if_method_has_arguments!("references", args) + spawn.references!(*args) end def references!(*args) # :nodoc: @@ -189,6 +197,16 @@ module ActiveRecord # Model.select(:field, :other_field, :and_one_more) # # => [#<Model field: "value", other_field: "value", and_one_more: "value">] # + # You can also use one or more strings, which will be used unchanged as SELECT fields. + # + # Model.select('field AS field_one', 'other_field AS field_two') + # # => [#<Model field: "value", other_field: "value">] + # + # If an alias was specified, it will be accessible from the resulting objects: + # + # Model.select('field AS field_one').first.field_one + # # => "value" + # # Accessing attributes of an object that do not have fields retrieved by a select # will throw <tt>ActiveModel::MissingAttributeError</tt>: # @@ -220,8 +238,12 @@ module ActiveRecord # # User.group(:name) # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>] + # + # User.group('name AS grouped_name, age') + # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>] def group(*args) - args.blank? ? self : spawn.group!(*args) + check_if_method_has_arguments!("group", args) + spawn.group!(*args) end def group!(*args) # :nodoc: @@ -251,7 +273,8 @@ module ActiveRecord # User.order(:name, email: :desc) # => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC def order(*args) - args.blank? ? self : spawn.order!(*args) + check_if_method_has_arguments!("order", args) + spawn.order!(*args) end def order!(*args) # :nodoc: @@ -276,7 +299,8 @@ module ActiveRecord # # generates a query with 'ORDER BY name ASC, id ASC'. def reorder(*args) - args.blank? ? self : spawn.reorder!(*args) + check_if_method_has_arguments!("reorder", args) + spawn.reorder!(*args) end def reorder!(*args) # :nodoc: @@ -292,8 +316,14 @@ module ActiveRecord # # User.joins(:posts) # => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" + # + # You can use strings in order to customize your joins: + # + # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id") + # => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id def joins(*args) - args.compact.blank? ? self : spawn.joins!(*args.flatten) + check_if_method_has_arguments!("joins", args) + spawn.joins!(*args.compact.flatten) end def joins!(*args) # :nodoc: @@ -439,8 +469,6 @@ module ActiveRecord end end - # #where! is identical to #where, except that instead of returning a new relation, it adds - # the condition to the existing relation. def where!(opts = :chain, *rest) # :nodoc: if opts == :chain WhereChain.new(self) @@ -458,6 +486,7 @@ module ActiveRecord # Order.having('SUM(price) > 30').group('user_id') def having(opts, *rest) opts.blank? ? self : spawn.having!(opts, *rest) + spawn.having!(opts, *rest) end def having!(opts, *rest) # :nodoc: @@ -605,7 +634,6 @@ module ActiveRecord spawn.from!(value, subquery_name) end - # Like #from, but modifies relation in place. def from!(value, subquery_name = nil) # :nodoc: self.from_value = [value, subquery_name] self @@ -894,5 +922,26 @@ module ActiveRecord end end + # Checks to make sure that the arguments are not blank. Note that if some + # blank-like object were initially passed into the query method, then this + # method will not raise an error. + # + # Example: + # + # Post.references() # => raises an error + # Post.references([]) # => does not raise an error + # + # This particular method should be called with a method_name and the args + # passed into that method as an input. For example: + # + # def references(*args) + # check_if_method_has_arguments!("references", args) + # ... + # end + def check_if_method_has_arguments!(method_name, args) + if args.blank? + raise ArgumentError, "The method .#{method_name}() must contain arguments." + end + end end end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 36bde44e7c..df090b972d 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -24,6 +24,7 @@ module ActiveRecord def dump(stream) header(stream) + extensions(stream) tables(stream) trailer(stream) stream @@ -66,6 +67,18 @@ HEADER stream.puts "end" end + def extensions(stream) + return unless @connection.supports_extensions? + extensions = @connection.extensions + if extensions.any? + stream.puts " # These are extensions that must be enabled in order to support this database" + extensions.each do |extension| + stream.puts " enable_extension #{extension.inspect}" + end + stream.puts + end + end + def tables(stream) @connection.tables.sort.each do |tbl| next if ['schema_migrations', ignore_tables].flatten.any? do |ignored| diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index 0c3fd1bd29..9746b1c3c2 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -1,4 +1,3 @@ - module ActiveRecord module Scoping extend ActiveSupport::Concern @@ -25,6 +24,5 @@ module ActiveRecord send("#{att}=", value) if respond_to?("#{att}=") end end - end end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 6835d0e01b..5bd481082e 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -1,4 +1,3 @@ - module ActiveRecord module Scoping module Default @@ -99,7 +98,7 @@ module ActiveRecord ) end - self.default_scopes = default_scopes + [scope] + self.default_scopes += [scope] end def build_default_scope # :nodoc: @@ -140,7 +139,6 @@ module ActiveRecord self.ignore_default_scope = false end end - end end end diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index cf4cf9e602..a610f479f2 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -42,21 +42,19 @@ module ActiveRecord # # All stored values are automatically available through accessors on the Active Record # object, but sometimes you want to specialize this behavior. This can be done by overwriting - # the default accessors (using the same name as the attribute) and calling - # <tt>read_store_attribute(store_attribute_name, attr_name)</tt> and - # <tt>write_store_attribute(store_attribute_name, attr_name, value)</tt> to actually - # change things. + # the default accessors (using the same name as the attribute) and calling <tt>super</tt> + # to actually change things. # # class Song < ActiveRecord::Base # # Uses a stored integer to hold the volume adjustment of the song # store :settings, accessors: [:volume_adjustment] # # def volume_adjustment=(decibels) - # write_store_attribute(:settings, :volume_adjustment, decibels.to_i) + # super(decibels.to_i) # end # # def volume_adjustment - # read_store_attribute(:settings, :volume_adjustment).to_i + # super.to_i # end # end module Store @@ -75,19 +73,30 @@ module ActiveRecord def store_accessor(store_attribute, *keys) keys = keys.flatten - keys.each do |key| - define_method("#{key}=") do |value| - write_store_attribute(store_attribute, key, value) - end - define_method(key) do - read_store_attribute(store_attribute, key) + _store_accessors_module.module_eval do + keys.each do |key| + define_method("#{key}=") do |value| + write_store_attribute(store_attribute, key, value) + end + + define_method(key) do + read_store_attribute(store_attribute, key) + end end end self.stored_attributes[store_attribute] ||= [] self.stored_attributes[store_attribute] |= keys end + + def _store_accessors_module + @_store_accessors_module ||= begin + mod = Module.new + include mod + mod + end + end end protected diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 67c7e714e6..4fa7cf8a7d 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -1,6 +1,7 @@ module ActiveRecord module Tasks # :nodoc: class DatabaseAlreadyExists < StandardError; end # :nodoc: + class DatabaseNotSupported < StandardError; end # :nodoc: module DatabaseTasks # :nodoc: extend self @@ -121,6 +122,9 @@ module ActiveRecord def class_for_adapter(adapter) key = @tasks.keys.detect { |pattern| adapter[pattern] } + unless key + raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter" + end @tasks[key] end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 4a608e4f7b..33718ef0e9 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -218,9 +218,8 @@ module ActiveRecord # after_commit :do_bar, on: :update # after_commit :do_baz, on: :destroy # - # Also, to have the callback fired on create and update, but not on destroy: - # - # after_commit :do_zoo, if: :persisted? + # after_commit :do_foo_bar, :on [:create, :update] + # after_commit :do_bar_baz, :on [:update, :destroy] # # Note that transactional fixtures do not play well with this feature. Please # use the +test_after_commit+ gem to have these hooks fired in tests. @@ -244,12 +243,14 @@ module ActiveRecord if options.is_a?(Hash) && options[:on] assert_valid_transaction_action(options[:on]) options[:if] = Array(options[:if]) - options[:if] << "transaction_include_action?(:#{options[:on]})" + fire_on = Array(options[:on]).map(&:to_sym) + options[:if] << "transaction_include_any_action?(#{fire_on})" end end - def assert_valid_transaction_action(action) - unless ACTIONS.include?(action.to_sym) + def assert_valid_transaction_action(actions) + actions = Array(actions) + if (actions - ACTIONS).any? raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}" end end @@ -286,8 +287,11 @@ module ActiveRecord end # Call the after_commit callbacks + # + # Ensure that it is not called if the object was never persisted (failed create), + # but call it after the commit of a destroyed object def committed! #:nodoc: - run_callbacks :commit + run_callbacks :commit if destroyed? || persisted? ensure clear_transaction_record_state end @@ -375,14 +379,16 @@ module ActiveRecord end # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. - def transaction_include_action?(action) #:nodoc: - case action - when :create - transaction_record_state(:new_record) - when :destroy - destroyed? - when :update - !(transaction_record_state(:new_record) || destroyed?) + def transaction_include_any_action?(actions) #:nodoc: + actions.any? do |action| + case action + when :create + transaction_record_state(:new_record) + when :destroy + destroyed? + when :update + !(transaction_record_state(:new_record) || destroyed?) + end end end end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index b67d70ede7..b965983fec 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -19,6 +19,9 @@ class MysqlConnectionTest < ActiveRecord::TestCase def test_connect_with_url run_without_connection do |orig| ar_config = ARTest.connection_config['arunit'] + + skip "This test doesn't work with custom socket location" if ar_config['socket'] + url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}" Klass.establish_connection(url) assert_equal ar_config['database'], Klass.connection.current_database diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index fa8f339f00..c03660957e 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -108,7 +108,7 @@ module ActiveRecord @connection.verify! new_connection_pid = @connection.query('select pg_backend_pid()') ensure - raw_connection_class.class_eval <<-CODE + raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1 alias query query_unfake undef query_fake CODE diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 7351ed9013..33c796191e 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -545,13 +545,13 @@ _SQL def test_update_bit_string new_bit_string = '11111111' - new_bit_string_varying = 'FF' + new_bit_string_varying = '11111110' assert @first_bit_string.bit_string = new_bit_string assert @first_bit_string.bit_string_varying = new_bit_string_varying assert @first_bit_string.save assert @first_bit_string.reload - assert_equal @first_bit_string.bit_string, new_bit_string - assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying + assert_equal new_bit_string, @first_bit_string.bit_string + assert_equal new_bit_string_varying, @first_bit_string.bit_string_varying end def test_update_oid diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 23bafde17b..6640f9b497 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -11,15 +11,23 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection - begin - @connection.transaction do - @connection.create_table('hstores') do |t| - t.hstore 'tags', :default => '' - end - end - rescue ActiveRecord::StatementInvalid + + unless @connection.supports_extensions? return skip "do not test on PG without hstore" end + + unless @connection.extension_enabled?('hstore') + @connection.enable_extension 'hstore' + @connection.commit_db_transaction + end + + @connection.reconnect! + + @connection.transaction do + @connection.create_table('hstores') do |t| + t.hstore 'tags', :default => '' + end + end @column = Hstore.columns.find { |c| c.name == 'tags' } end @@ -27,6 +35,32 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase @connection.execute 'drop table if exists hstores' end + def test_hstore_included_in_extensions + assert @connection.respond_to?(:extensions), "connection should have a list of extensions" + assert @connection.extensions.include?('hstore'), "extension list should include hstore" + end + + def test_hstore_enabled + assert @connection.extension_enabled?('hstore') + end + + def test_disable_hstore + if @connection.extension_enabled?('hstore') + @connection.disable_extension 'hstore' + assert_not @connection.extension_enabled?('hstore') + end + end + + def test_enable_hstore + if @connection.extension_enabled?('hstore') + @connection.disable_extension 'hstore' + end + + assert_not @connection.extension_enabled?('hstore') + @connection.enable_extension 'hstore' + assert @connection.extension_enabled?('hstore') + end + def test_column assert_equal :hstore, @column.type end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index be2d30641e..46f3c38ac5 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -73,6 +73,11 @@ class EagerAssociationTest < ActiveRecord::TestCase end end + def test_has_many_through_with_order + authors = Author.includes(:favorite_authors).to_a + assert_no_queries { authors.map(&:favorite_authors) } + end + def test_with_two_tables_in_from_without_getting_double_quoted posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order("posts.id").to_a assert_equal 2, posts.first.comments.size @@ -1162,4 +1167,10 @@ class EagerAssociationTest < ActiveRecord::TestCase Post.where('1 = 0').scoping { Comment.preload(:post).find(1).post } ) end + + test "preloading does not cache has many association subset when preloaded with a through association" do + author = Author.includes(:comments_with_order_and_conditions, :posts).first + assert_no_queries { assert_equal 2, author.comments_with_order_and_conditions.size } + assert_no_queries { assert_equal 5, author.posts.size, "should not cache a subset of the association" } + 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 fd6d531645..1ddd380f23 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -20,6 +20,8 @@ require 'models/car' require 'models/bulb' require 'models/engine' require 'models/categorization' +require 'models/minivan' +require 'models/speedometer' class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase class Invoice < ActiveRecord::Base @@ -1747,4 +1749,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase david.posts_with_special_categorizations = [] assert_equal [], david.posts_with_special_categorizations end + + test "does not duplicate associations when used with natural primary keys" do + speedometer = Speedometer.create!(id: '4') + speedometer.minivans.create!(minivan_id: 'a-van-red' ,name: 'a van', color: 'red') + + assert_equal 1, speedometer.minivans.to_a.size, "Only one association should be present:\n#{speedometer.minivans.to_a}" + assert_equal 1, speedometer.reload.minivans.to_a.size + end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 4f46459ab3..af1845c937 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -319,27 +319,12 @@ class BasicsTest < ActiveRecord::TestCase assert_equal(true, cb.frickinawesome) end - def test_first_or_create - parrot = Bird.first_or_create(:color => 'green', :name => 'parrot') - assert parrot.persisted? - the_same_parrot = Bird.first_or_create(:color => 'yellow', :name => 'macaw') - assert_equal parrot, the_same_parrot - end - - def test_first_or_create_bang - assert_raises(ActiveRecord::RecordInvalid) { Bird.first_or_create! } - parrot = Bird.first_or_create!(:color => 'green', :name => 'parrot') - assert parrot.persisted? - the_same_parrot = Bird.first_or_create!(:color => 'yellow', :name => 'macaw') - assert_equal parrot, the_same_parrot - end - - def test_first_or_initialize - parrot = Bird.first_or_initialize(:color => 'green', :name => 'parrot') - assert_kind_of Bird, parrot - assert !parrot.persisted? - assert parrot.new_record? - assert parrot.valid? + def test_create_after_initialize_with_array_param + cbs = CustomBulb.create([{ name: 'Dude' }, { name: 'Bob' }]) + assert_equal 'Dude', cbs[0].name + assert_equal 'Bob', cbs[1].name + assert cbs[0].frickinawesome + assert !cbs[1].frickinawesome end def test_load @@ -1462,6 +1447,13 @@ class BasicsTest < ActiveRecord::TestCase assert_match(/\/#{dev.id}$/, dev.cache_key) end + def test_touch_should_raise_error_on_a_new_object + company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals") + assert_raises(ActiveRecord::ActiveRecordError) do + company.touch :updated_at + end + end + def test_cache_key_format_is_precise_enough dev = Developer.first key = dev.cache_key diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index 52de0efe7f..c8dfc3244b 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -8,42 +8,55 @@ module ActiveRecord Resolver.new(spec, {}).spec.config end + def test_url_invalid_adapter + assert_raises(LoadError) do + resolve 'ridiculous://foo?encoding=utf8' + end + end + + # The abstract adapter is used simply to bypass the bit of code that + # checks that the adapter file can be required in. + def test_url_host_no_db - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) - spec = resolve 'mysql://foo?encoding=utf8' + spec = resolve 'abstract://foo?encoding=utf8' assert_equal({ - :adapter => "mysql", - :host => "foo", - :encoding => "utf8" }, spec) + adapter: "abstract", + host: "foo", + encoding: "utf8" }, spec) end def test_url_host_db - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) - spec = resolve 'mysql://foo/bar?encoding=utf8' + spec = resolve 'abstract://foo/bar?encoding=utf8' assert_equal({ - :adapter => "mysql", - :database => "bar", - :host => "foo", - :encoding => "utf8" }, spec) + adapter: "abstract", + database: "bar", + host: "foo", + encoding: "utf8" }, spec) end def test_url_port - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) - spec = resolve 'mysql://foo:123?encoding=utf8' + spec = resolve 'abstract://foo:123?encoding=utf8' assert_equal({ - :adapter => "mysql", - :port => 123, - :host => "foo", - :encoding => "utf8" }, spec) + adapter: "abstract", + port: 123, + host: "foo", + encoding: "utf8" }, spec) end def test_encoded_password - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) password = 'am@z1ng_p@ssw0rd#!' encoded_password = URI.encode_www_form_component(password) - spec = resolve "mysql://foo:#{encoded_password}@localhost/bar" + spec = resolve "abstract://foo:#{encoded_password}@localhost/bar" assert_equal password, spec[:password] end + + def test_descriptive_error_message_when_adapter_is_missing + error = assert_raise(LoadError) do + resolve(adapter: 'non-existing') + end + + assert_match "Could not load 'active_record/connection_adapters/non-existing_adapter'", error.message + end end end end diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index aa2a6d7509..b1d276f9eb 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -14,46 +14,9 @@ if ActiveRecord::Base.connection.supports_explain? base.connection end - def test_logging_query_plan_with_logger - base.logger.expects(:warn).with do |message| - message.starts_with?('EXPLAIN for:') - end - - with_threshold(0) do - Car.where(:name => 'honda').to_a - end - end - - def test_logging_query_plan_without_logger - original = base.logger - base.logger = nil - - class << base.logger - def warn; raise "Should not be called" end - end - - with_threshold(0) do - car = Car.where(:name => 'honda').first - assert_equal 'honda', car.name - end - ensure - base.logger = original - end - - def test_collect_queries_for_explain - base.auto_explain_threshold_in_seconds = nil - queries = Thread.current[:available_queries_for_explain] = [] - - with_threshold(0) do - Car.where(:name => 'honda').to_a - end - - sql, binds = queries[0] - assert_match "SELECT", sql - assert_match "honda", sql - assert_equal [], binds - ensure - Thread.current[:available_queries_for_explain] = nil + def test_relation_explain + message = Car.where(:name => 'honda').explain + assert_match(/^EXPLAIN for:/, message) end def test_collecting_queries_for_explain @@ -68,16 +31,6 @@ if ActiveRecord::Base.connection.supports_explain? assert_equal [cars(:honda)], result end - def test_logging_query_plan_when_counting_by_sql - base.logger.expects(:warn).with do |message| - message.starts_with?('EXPLAIN for:') - end - - with_threshold(0) do - Car.count_by_sql "SELECT COUNT(*) FROM cars WHERE name = 'honda'" - end - end - def test_exec_explain_with_no_binds sqls = %w(foo bar) binds = [[], []] @@ -113,25 +66,8 @@ if ActiveRecord::Base.connection.supports_explain? base.logger.expects(:warn).never - with_threshold(0) do - Car.where(:name => 'honda').to_a - end - end - - def test_silence_auto_explain - base.expects(:collecting_sqls_for_explain).never - base.logger.expects(:warn).never - base.silence_auto_explain do - with_threshold(0) { Car.all.to_a } - end + Car.where(:name => 'honda').to_a end - def with_threshold(threshold) - current_threshold = base.auto_explain_threshold_in_seconds - base.auto_explain_threshold_in_seconds = threshold - yield - ensure - base.auto_explain_threshold_in_seconds = current_threshold - end end end diff --git a/activerecord/test/cases/migration/rename_column_test.rb b/activerecord/test/cases/migration/columns_test.rb index 8f6918d06a..e52809f0f8 100644 --- a/activerecord/test/cases/migration/rename_column_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -2,7 +2,7 @@ require "cases/migration/helper" module ActiveRecord class Migration - class RenameColumnTest < ActiveRecord::TestCase + class ColumnsTest < ActiveRecord::TestCase include ActiveRecord::Migration::TestHelper self.use_transactional_fixtures = false @@ -86,8 +86,37 @@ module ActiveRecord assert_equal 1, connection.indexes('test_models').size rename_column "test_models", "hat_name", "name" - # FIXME: should we rename the index if it's name was autogenerated by rails? - assert_equal ['index_test_models_on_hat_name'], connection.indexes('test_models').map(&:name) + + assert_equal ['index_test_models_on_name'], connection.indexes('test_models').map(&:name) + end + + def test_rename_column_with_multi_column_index + add_column "test_models", :hat_size, :integer + add_column "test_models", :hat_style, :string, limit: 100 + add_index "test_models", ["hat_style", "hat_size"], unique: true + + rename_column "test_models", "hat_size", 'size' + if current_adapter? :OracleAdapter + assert_equal ['i_test_models_hat_style_size'], connection.indexes('test_models').map(&:name) + else + assert_equal ['index_test_models_on_hat_style_and_size'], connection.indexes('test_models').map(&:name) + end + + rename_column "test_models", "hat_style", 'style' + if current_adapter? :OracleAdapter + assert_equal ['i_test_models_style_size'], connection.indexes('test_models').map(&:name) + else + assert_equal ['index_test_models_on_style_and_size'], connection.indexes('test_models').map(&:name) + end + end + + def test_rename_column_does_not_rename_custom_named_index + add_column "test_models", :hat_name, :string + add_index :test_models, :hat_name, :name => 'idx_hat_name' + + assert_equal 1, connection.indexes('test_models').size + rename_column "test_models", "hat_name", "name" + assert_equal ['idx_hat_name'], connection.indexes('test_models').map(&:name) end def test_remove_column_with_index @@ -107,7 +136,7 @@ module ActiveRecord assert_equal 1, connection.indexes('test_models').size remove_column("test_models", "hat_size") - # Every database and/or database adapter has their own behavior + # Every database and/or database adapter has their own behavior # if it drops the multi-column index when any of the indexed columns dropped by remove_column. if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) assert_equal [], connection.indexes('test_models').map(&:name) @@ -197,6 +226,17 @@ module ActiveRecord assert_equal ['test_models_categories_idx'], connection.indexes('test_models').map(&:name) end + def test_change_column_with_long_index_name + table_name_prefix = 'test_models_' + long_index_name = table_name_prefix + ('x' * (connection.allowed_index_name_length - table_name_prefix.length)) + add_column "test_models", "category", :string + add_index :test_models, :category, name: long_index_name + + change_column "test_models", "category", :string, null: false, default: 'article' + + assert_equal [long_index_name], connection.indexes('test_models').map(&:name) + end + def test_change_column_default add_column "test_models", "first_name", :string connection.change_column_default "test_models", "first_name", "Tester" @@ -213,6 +253,20 @@ module ActiveRecord def test_remove_column_no_second_parameter_raises_exception assert_raise(ArgumentError) { connection.remove_column("funny") } end + + def test_removing_and_renaming_column_preserves_custom_primary_key + connection.create_table "my_table", primary_key: "my_table_id", force: true do |t| + t.integer "col_one" + t.string "col_two", limit: 128, null: false + end + + remove_column("my_table", "col_two") + rename_column("my_table", "col_one", "col_three") + + assert_equal 'my_table_id', connection.primary_key('my_table') + ensure + connection.drop_table(:my_table) rescue nil + end end end end diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb index a41f2c10f0..0e375af6e8 100644 --- a/activerecord/test/cases/migration/index_test.rb +++ b/activerecord/test/cases/migration/index_test.rb @@ -55,19 +55,31 @@ module ActiveRecord assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") } end - def test_add_index_name_length_limit - good_index_name = 'x' * connection.index_name_length + def test_add_index_works_with_long_index_names + connection.add_index(table_name, "foo", name: good_index_name) + + assert connection.index_name_exists?(table_name, good_index_name, false) + connection.remove_index(table_name, name: good_index_name) + end + + def test_add_index_does_not_accept_too_long_index_names too_long_index_name = good_index_name + 'x' - assert_raises(ArgumentError) { - connection.add_index(table_name, "foo", :name => too_long_index_name) + e = assert_raises(ArgumentError) { + connection.add_index(table_name, "foo", name: too_long_index_name) } + assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message) assert_not connection.index_name_exists?(table_name, too_long_index_name, false) connection.add_index(table_name, "foo", :name => good_index_name) + end + + def test_internal_index_with_name_matching_database_limit + good_index_name = 'x' * connection.index_name_length + connection.add_index(table_name, "foo", name: good_index_name, internal: true) assert connection.index_name_exists?(table_name, good_index_name, false) - connection.remove_index(table_name, :name => good_index_name) + connection.remove_index(table_name, name: good_index_name) end def test_index_symbol_names @@ -196,6 +208,12 @@ module ActiveRecord connection.remove_index("testings", "last_name") assert !connection.index_exists?("testings", "last_name") end + + private + def good_index_name + 'x' * connection.allowed_index_name_length + end + end end end diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index 21901bec3c..22dbd7c38b 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -63,7 +63,17 @@ module ActiveRecord connection.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter) assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") - assert connection.indexes(:octopi).first.columns.include?("url") + index = connection.indexes(:octopi).first + assert index.columns.include?("url") + assert_equal 'index_octopi_on_url', index.name + end + + def test_rename_table_does_not_rename_custom_named_index + add_index :test_models, :url, name: 'special_url_idx' + + rename_table :test_models, :octopi + + assert_equal ['special_url_idx'], connection.indexes(:octopi).map(&:name) end def test_rename_table_for_postgresql_should_also_rename_default_sequence diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index b936cca875..8156f99037 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -399,6 +399,14 @@ class PersistencesTest < ActiveRecord::TestCase assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') } end + def test_string_ids + # FIXME: Fix this failing test + skip "Failing test. We need this fixed before 4.0.0" + mv = Minivan.where(:minivan_id => 1234).first_or_initialize + assert mv.new_record? + assert_equal '1234', mv.minivan_id + end + def test_update_attribute_with_one_updated t = Topic.first t.update_attribute(:title, 'super_title') diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 3dd11ae89d..0ad05223d4 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -122,35 +122,35 @@ module ActiveRecord def test_quote_float float = 1.2 assert_equal float.to_s, @quoter.quote(float, nil) - assert_equal float.to_s, @quoter.quote(float, Object.new) + assert_equal float.to_s, @quoter.quote(float, FakeColumn.new(:float)) end def test_quote_fixnum fixnum = 1 assert_equal fixnum.to_s, @quoter.quote(fixnum, nil) - assert_equal fixnum.to_s, @quoter.quote(fixnum, Object.new) + assert_equal fixnum.to_s, @quoter.quote(fixnum, FakeColumn.new(:integer)) end def test_quote_bignum bignum = 1 << 100 assert_equal bignum.to_s, @quoter.quote(bignum, nil) - assert_equal bignum.to_s, @quoter.quote(bignum, Object.new) + assert_equal bignum.to_s, @quoter.quote(bignum, FakeColumn.new(:integer)) end def test_quote_bigdecimal bigdec = BigDecimal.new((1 << 100).to_s) assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, nil) - assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, Object.new) + assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, FakeColumn.new(:decimal)) end def test_dates_and_times @quoter.extend(Module.new { def quoted_date(value) 'lol' end }) assert_equal "'lol'", @quoter.quote(Date.today, nil) - assert_equal "'lol'", @quoter.quote(Date.today, Object.new) + assert_equal "'lol'", @quoter.quote(Date.today, FakeColumn.new(:date)) assert_equal "'lol'", @quoter.quote(Time.now, nil) - assert_equal "'lol'", @quoter.quote(Time.now, Object.new) + assert_equal "'lol'", @quoter.quote(Time.now, FakeColumn.new(:time)) assert_equal "'lol'", @quoter.quote(DateTime.now, nil) - assert_equal "'lol'", @quoter.quote(DateTime.now, Object.new) + assert_equal "'lol'", @quoter.quote(DateTime.now, FakeColumn.new(:datetime)) end def test_crazy_object diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index c43c7601a2..53cdf89b1f 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -108,5 +108,30 @@ module ActiveRecord assert_equal 4, Edge.where(blank).order("sink_id").to_a.size end end + + def test_where_with_integer_for_string_column + count = Post.where(:title => 0).count + assert_equal 0, count + end + + def test_where_with_float_for_string_column + count = Post.where(:title => 0.0).count + assert_equal 0, count + end + + def test_where_with_boolean_for_string_column + count = Post.where(:title => false).count + assert_equal 0, count + end + + def test_where_with_decimal_for_string_column + count = Post.where(:title => BigDecimal.new(0)).count + assert_equal 0, count + end + + def test_where_with_duration_for_string_column + count = Post.where(:title => 0.seconds).count + assert_equal 0, count + end end end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 7388324a0d..8e6c38706f 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -391,19 +391,19 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scope_with_inheritance wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal 50000, wheres[:salary] + assert_equal Arel.sql("50000"), wheres[:salary] end def test_default_scope_with_module_includes wheres = ModuleIncludedPoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal 50000, wheres[:salary] + assert_equal Arel.sql("50000"), wheres[:salary] end def test_default_scope_with_multiple_calls wheres = MultiplePoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal 50000, wheres[:salary] + assert_equal Arel.sql("50000"), wheres[:salary] end def test_scope_overwrites_default diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 379c0c0758..8298d7534c 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -321,6 +321,22 @@ class RelationTest < ActiveRecord::TestCase assert_equal 1, person_with_reader_and_post.size end + def test_no_arguments_to_query_methods_raise_errors + assert_raises(ArgumentError) { Topic.references() } + assert_raises(ArgumentError) { Topic.includes() } + assert_raises(ArgumentError) { Topic.preload() } + assert_raises(ArgumentError) { Topic.group() } + assert_raises(ArgumentError) { Topic.reorder() } + end + + def test_blank_like_arguments_to_query_methods_dont_raise_errors + assert_nothing_raised { Topic.references([]) } + assert_nothing_raised { Topic.includes([]) } + assert_nothing_raised { Topic.preload([]) } + assert_nothing_raised { Topic.group([]) } + assert_nothing_raised { Topic.reorder([]) } + end + def test_scoped_responds_to_delegated_methods relation = Topic.all diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index cae12e0e3a..bfecc0d1e9 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -1,6 +1,5 @@ require "cases/helper" - class SchemaDumperTest < ActiveRecord::TestCase def setup super @@ -231,6 +230,21 @@ class SchemaDumperTest < ActiveRecord::TestCase end if current_adapter?(:PostgreSQLAdapter) + def test_schema_dump_includes_extensions + connection = ActiveRecord::Base.connection + skip unless connection.supports_extensions? + + connection.stubs(:extensions).returns(['hstore']) + output = standard_dump + assert_match "# These are extensions that must be enabled", output + assert_match %r{enable_extension "hstore"}, output + + connection.stubs(:extensions).returns([]) + output = standard_dump + assert_no_match "# These are extensions that must be enabled", output + assert_no_match %r{enable_extension}, output + end + def test_schema_dump_includes_xml_shorthand_definition output = standard_dump if %r{create_table "postgresql_xml_data_type"} =~ output diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 43bf285ba9..3e32d866ee 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -35,6 +35,12 @@ class StoreTest < ActiveRecord::TestCase assert_equal '(123) 456-7890', @john.phone_number end + test "overriding a read accessor using super" do + @john.settings[:color] = nil + + assert_equal 'red', @john.color + end + test "updating the store will mark it as changed" do @john.color = 'red' assert @john.settings_changed? @@ -66,6 +72,12 @@ class StoreTest < ActiveRecord::TestCase assert_equal '1234567890', @john.settings[:phone_number] end + test "overriding a write accessor using super" do + @john.color = 'yellow' + + assert_equal 'blue', @john.color + end + test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do @john.json_data = ActiveSupport::HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy') @john.height = 'low' diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 659d5eae72..3bfbc92afd 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -31,6 +31,12 @@ module ActiveRecord ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz) ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => :foo}, "awesome-file.sql") end + + def test_unregistered_task + assert_raise(ActiveRecord::Tasks::DatabaseNotSupported) do + ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => :bar}, "awesome-file.sql") + end + end end class DatabaseTasksCreateTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 869892e33f..eb4ffd4498 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -5,9 +5,29 @@ class TransactionCallbacksTest < ActiveRecord::TestCase self.use_transactional_fixtures = false fixtures :topics + class ReplyWithCallbacks < ActiveRecord::Base + self.table_name = :topics + + belongs_to :topic, foreign_key: "parent_id" + + validates_presence_of :content + + after_commit :do_after_commit, on: :create + + def history + @history ||= [] + end + + def do_after_commit + history << :commit_on_create + end + end + class TopicWithCallbacks < ActiveRecord::Base self.table_name = :topics + has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id" + after_commit{|record| record.send(:do_after_commit, nil)} after_commit(:on => :create){|record| record.send(:do_after_commit, :create)} after_commit(:on => :update){|record| record.send(:do_after_commit, :update)} @@ -93,6 +113,13 @@ class TransactionCallbacksTest < ActiveRecord::TestCase assert_equal [:commit_on_create], @new_record.history end + def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record_if_create_succeeds_creating_through_association + topic = TopicWithCallbacks.create!(:title => "New topic", :written_on => Date.today) + reply = topic.replies.create + + assert_equal [], reply.history + end + def test_call_after_rollback_after_transaction_rollsback @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} @@ -285,3 +312,38 @@ class SaveFromAfterCommitBlockTest < ActiveRecord::TestCase assert_equal true, topic.record_updated end end + +class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + class TopicWithCallbacksOnMultipleActions < ActiveRecord::Base + self.table_name = :topics + + after_commit(on: [:create, :destroy]) { |record| record.history << :create_and_destroy } + after_commit(on: [:create, :update]) { |record| record.history << :create_and_update } + after_commit(on: [:update, :destroy]) { |record| record.history << :update_and_destroy } + + def clear_history + @history = [] + end + + def history + @history ||= [] + end + end + + def test_after_commit_on_multiple_actions + topic = TopicWithCallbacksOnMultipleActions.new + topic.save + assert_equal [:create_and_update, :create_and_destroy], topic.history + + topic.clear_history + topic.approved = true + topic.save + assert_equal [:update_and_destroy, :create_and_update], topic.history + + topic.clear_history + topic.destroy + assert_equal [:update_and_destroy, :create_and_destroy], topic.history + end +end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 546737b398..6d66342fa5 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -460,7 +460,7 @@ class TransactionTest < ActiveRecord::TestCase assert !transaction.state.committed? transaction.perform_rollback - + assert transaction.state.rolledback? assert !transaction.state.committed? end @@ -474,7 +474,7 @@ class TransactionTest < ActiveRecord::TestCase assert !transaction.state.committed? transaction.perform_commit - + assert !transaction.state.rolledback? assert transaction.state.committed? end diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb index 024fede266..4c3b71e8f9 100644 --- a/activerecord/test/models/admin/user.rb +++ b/activerecord/test/models/admin/user.rb @@ -27,4 +27,13 @@ class Admin::User < ActiveRecord::Base def phone_number=(value) write_store_attribute(:settings, :phone_number, value && value.gsub(/[^\d]/,'')) end + + def color + super || 'red' + end + + def color=(value) + value = 'blue' unless %w(black red green blue).include?(value) + super + end end diff --git a/activerecord/test/models/speedometer.rb b/activerecord/test/models/speedometer.rb index 0a7d38d8ec..497c3aba9a 100644 --- a/activerecord/test/models/speedometer.rb +++ b/activerecord/test/models/speedometer.rb @@ -1,4 +1,6 @@ class Speedometer < ActiveRecord::Base self.primary_key = :speedometer_id belongs_to :dashboard + + has_many :minivans end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index cd9835259a..d789b6cb7a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -540,6 +540,8 @@ ActiveRecord::Schema.define do create_table :price_estimates, :force => true do |t| t.string :estimate_of_type t.integer :estimate_of_id + t.string :thing_type + t.integer :thing_id t.integer :price end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 72f28aefc7..2515d378fd 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,19 +1,69 @@ ## Rails 4.0.0 (unreleased) ## +* Added `ActiveSupport::TimeWithZone#to_r` for `Time#at` compatibility. + + Before this change: + + Time.zone = 'Tokyo' + time = Time.zone.now + time == Time.at(time) # => false + + After the change: + + Time.zone = 'Tokyo' + time = Time.zone.now + time == Time.at(time) # => true + + *stopdropandrew* + +* `ActiveSupport::NumberHelper#number_to_human` returns the number unaltered when + the given units hash does not contain the needed key, e.g. when the number provided + is less than the largest key provided. + Fixes #9269. + + Examples: + + number_to_human(123, units: {}) # => 123 + number_to_human(123, units: { thousand: 'k' }) # => 123 + + *Michael Hoffman* + +* Added `beginning_of_minute` support to core ext calculations for `Time` and `DateTime`. + + *Gagan Awhad* + +* Add `:nsec` date format. + + *Jamie Gaskins* + +* `ActiveSupport::Gzip.compress` allows two optional arguments for compression + level and strategy. + + *Beyond* + +* Modify `TimeWithZone#as_json` to include 3 decimal places of sub-second accuracy + by default, which is optional as per the ISO8601 spec, but extremely useful. Also + the default behaviour of `Date#toJSON()` in recent versions of Chrome, Safari and + Firefox. + + *James Harton* + +* Improve `String#squish` to handle Unicode whitespace. *Antoine Lyset* + * Standardise on `to_time` returning an instance of `Time` in the local system timezone across `String`, `Time`, `Date`, `DateTime` and `ActiveSupport::TimeWithZone`. *Andrew White* * Extract `ActiveSupport::Testing::Performance` into https://github.com/rails/rails-perftest - You can add the gem to your Gemfile to keep using performance tests. + You can add the gem to your `Gemfile` to keep using performance tests. gem 'rails-perftest' *Yves Senn* -* Hash.from_xml raises when it encounters type="symbol" or type="yaml". - Use Hash.from_trusted_xml to parse this XML. +* `Hash.from_xml` raises when it encounters `type="symbol"` or `type="yaml"`. + Use `Hash.from_trusted_xml` to parse this XML. CVE-2013-0156 @@ -33,7 +83,7 @@ *Kelly Stannard* * It's now possible to compare `Date`, `DateTime`, `Time` and `TimeWithZone` - with `Infinity`. This allows to create date/time ranges with one infinite bound. + with `Float::INFINITY`. This allows to create date/time ranges with one infinite bound. Example: range = Range.new(Date.today, Float::INFINITY) @@ -66,13 +116,13 @@ * Remove surrogate unicode character encoding from `ActiveSupport::JSON.encode` The encoding scheme was broken for unicode characters outside the basic multilingual plane; - since json is assumed to be `UTF-8`, and we already force the encoding to `UTF-8`, + since json is assumed to be UTF-8, and we already force the encoding to UTF-8, simply pass through the un-encoded characters. *Brett Carter* * Deprecate `Time.time_with_date_fallback`, `Time.utc_time` and `Time.local_time`. - These methods were added to handle the limited range of Ruby's native Time + These methods were added to handle the limited range of Ruby's native `Time` implementation. Those limitations no longer apply so we are deprecating them in 4.0 and they will be removed in 4.1. @@ -80,16 +130,17 @@ * Deprecate `Date#to_time_in_current_zone` and add `Date#in_time_zone`. *Andrew White* -* Add `String#in_time_zone` method to convert a string to an ActiveSupport::TimeWithZone. *Andrew White* +* Add `String#in_time_zone` method to convert a string to an `ActiveSupport::TimeWithZone`. *Andrew White* * Deprecate `ActiveSupport::BasicObject` in favor of `ActiveSupport::ProxyObject`. - This class is used for proxy classes. It avoids confusion with Ruby's BasicObject + This class is used for proxy classes. It avoids confusion with Ruby's `BasicObject` class. *Francesco Rodriguez* -* Patched Marshal#load to work with constant autoloading. - Fixes autoloading with cache stores that relay on Marshal(MemCacheStore and FileStore). [fixes #8167] +* Patched `Marshal#load` to work with constant autoloading. Fixes autoloading + with cache stores that rely on `Marshal` (`MemCacheStore` and `FileStore`). + Fixes #8167. *Uriel Katz* @@ -104,9 +155,9 @@ *Olek Janiszewski* -* No longer proxy ActiveSupport::Multibyte#class. *Steve Klabnik* +* No longer proxy `ActiveSupport::Multibyte#class`. *Steve Klabnik* -* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. *Carlos Antonio da Silva* +* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from minitest instead. *Carlos Antonio da Silva* * `XmlMini.with_backend` now may be safely used with threads: @@ -121,18 +172,17 @@ *Nikita Afanasenko* -* Dependencies no longer trigger Kernel#autoload in remove_constant [fixes #8213]. *Xavier Noria* +* Dependencies no longer trigger `Kernel#autoload` in `remove_constant`. Fixes #8213. *Xavier Noria* -* Simplify mocha integration and remove monkey-patches, bumping mocha to 0.13.0. *James Mead* +* Simplify `mocha` integration and remove monkey-patches, bumping `mocha` to 0.13.0. *James Mead* -* `#as_json` isolates options when encoding a hash. - Fix #8182 +* `#as_json` isolates options when encoding a hash. Fixes #8182. *Yves Senn* -* Deprecate Hash#diff in favor of MiniTest's #diff. *Steve Klabnik* +* Deprecate `Hash#diff` in favor of minitest's #diff. *Steve Klabnik* -* Kernel#capture can catch output from subprocesses *Dmitry Vorotilin* +* `Kernel#capture` can catch output from subprocesses. *Dmitry Vorotilin* * `to_xml` conversions now use builder's `tag!` method instead of explicit invocation of `method_missing`. @@ -140,27 +190,30 @@ * Fixed timezone mapping of the Solomon Islands. *Steve Klabnik* -* Make callstack attribute optional in - ActiveSupport::Deprecation::Reporting methods `warn` and `deprecation_warning` +* Make callstack attribute optional in `ActiveSupport::Deprecation::Reporting` + methods `warn` and `deprecation_warning`. *Alexey Gaziev* -* Implement HashWithIndifferentAccess#replace so key? works correctly. *David Graham* +* Implement `HashWithIndifferentAccess#replace` so `key?` works correctly. *David Graham* + +* Handle the possible permission denied errors `atomic.rb` might trigger due to its `chown` + and `chmod` calls. -* Handle the possible Permission Denied errors atomic.rb might trigger due to its chown and chmod calls. *Daniele Sluijters* + *Daniele Sluijters* -* Hash#extract! returns only those keys that present in the receiver. +* `Hash#extract!` returns only those keys that present in the receiver. {a: 1, b: 2}.extract!(:a, :x) # => {:a => 1} *Mikhail Dieterle* -* Hash#extract! returns the same subclass, that the receiver is. I.e. - HashWithIndifferentAccess#extract! returns HashWithIndifferentAccess instance. +* `Hash#extract!` returns the same subclass, that the receiver is. I.e. + `HashWithIndifferentAccess#extract!` returns a `HashWithIndifferentAccess` instance. *Mikhail Dieterle* -* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead. *Brian Durand* +* Optimize `ActiveSupport::Cache::Entry` to reduce memory and processing overhead. *Brian Durand* * Tests tag the Rails log with the current test class and test case: @@ -169,7 +222,7 @@ *Jeremy Kemper* -* Add `logger.push_tags` and `.pop_tags` to complement logger.tagged: +* Add `logger.push_tags` and `.pop_tags` to complement `logger.tagged`: class Job def before @@ -205,6 +258,7 @@ class User include ActiveSupport::Configurable + config_accessor :hair_colors do [:brown, :black, :blonde, :red] end @@ -214,7 +268,7 @@ *Larry Lv* -* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of +* `ActiveSupport::Benchmarkable#silence` has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1. *Steve Klabnik* @@ -224,14 +278,14 @@ *Pranas Kiziela* -* ActiveSupport::Deprecation is now a class. It is possible to create an instance +* `ActiveSupport::Deprecation` is now a class. It is possible to create an instance of deprecator. Backwards compatibility has been preserved. You can choose which instance of the deprecator will be used. deprecate :method_name, deprecator: deprecator_instance - You can use ActiveSupport::Deprecation in your gem. + You can use `ActiveSupport::Deprecation` in your gem. require 'active_support/deprecation' require 'active_support/core_ext/module/deprecation' @@ -258,7 +312,7 @@ * `ERB::Util.html_escape` encodes single quote as `#39`. Decimal form has better support in old browsers. *Kalys Osmonov* * `ActiveSupport::Callbacks`: deprecate monkey patch of object callbacks. - Using the #filter method like this: + Using the `filter` method like this: before_filter MyFilter.new @@ -291,7 +345,7 @@ *Akira Matsuda* -* Replace deprecated `memcache-client` gem with `dalli` in ActiveSupport::Cache::MemCacheStore +* Replace deprecated `memcache-client` gem with `dalli` in `ActiveSupport::Cache::MemCacheStore`. *Guillermo Iguaran* @@ -305,14 +359,14 @@ *Erich Menge* -* Add String#indent. *fxn & Ace Suares* +* Add `String#indent`. *fxn & Ace Suares* * Inflections can now be defined per locale. `singularize` and `pluralize` accept locale as an extra argument. *David Celis* -* `Object#try` will now return nil instead of raise a NoMethodError if the +* `Object#try` will now return `nil` instead of raise a `NoMethodError` if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`. @@ -340,62 +394,64 @@ *Francesco Rodriguez* -* ActionView::Helpers::NumberHelper methods have been moved to ActiveSupport::NumberHelper and are now available via - Numeric#to_s. Numeric#to_s now accepts the formatting options :phone, :currency, :percentage, :delimited, - :rounded, :human, and :human_size. *Andrew Mutz* +* `ActionView::Helpers::NumberHelper` methods have been moved to `ActiveSupport::NumberHelper` and are now available via + `Numeric#to_s`. `Numeric#to_s` now accepts the formatting options `:phone`, `:currency`, `:percentage`, `:delimited`, + `:rounded`, `:human`, and `:human_size`. + + *Andrew Mutz* * Add `Hash#transform_keys`, `Hash#transform_keys!`, `Hash#deep_transform_keys`, and `Hash#deep_transform_keys!`. *Mark McSpadden* -* Changed xml type `datetime` to `dateTime` (with upper case letter `T`). *Angelo Capilleri* +* Changed XML type `datetime` to `dateTime` (with upper case letter `T`). *Angelo Capilleri* * Add `:instance_accessor` option for `class_attribute`. *Alexey Vakhov* * `constantize` now looks in the ancestor chain. *Marc-Andre Lafortune & Andrew White* -* Adds `Hash#deep_stringify_keys` and `Hash#deep_stringify_keys!` to convert all keys from a +Hash+ instance into strings *Lucas Húngaro* +* Adds `Hash#deep_stringify_keys` and `Hash#deep_stringify_keys!` to convert all keys from a `Hash` instance into strings. *Lucas Húngaro* -* Adds `Hash#deep_symbolize_keys` and `Hash#deep_symbolize_keys!` to convert all keys from a +Hash+ instance into symbols *Lucas Húngaro* +* Adds `Hash#deep_symbolize_keys` and `Hash#deep_symbolize_keys!` to convert all keys from a `Hash` instance into symbols. *Lucas Húngaro* * `Object#try` can't call private methods. *Vasiliy Ermolovich* * `AS::Callbacks#run_callbacks` remove `key` argument. *Francesco Rodriguez* -* `deep_dup` works more expectedly now and duplicates also values in +Hash+ instances and elements in +Array+ instances. *Alexey Gaziev* +* `deep_dup` works more expectedly now and duplicates also values in `Hash` instances and elements in `Array` instances. *Alexey Gaziev* -* Inflector no longer applies ice -> ouse to words like slice, police, ets *Wes Morgan* +* Inflector no longer applies ice -> ouse to words like "slice", "police", etc. *Wes Morgan* -* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations *twinturbo* +* 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* +* Make `Module#delegate` stop using `send` - can no longer delegate to private methods. *dasch* -* AS::Callbacks: deprecate `:rescuable` option. *Bogdan Gusiev* +* `ActiveSupport::Callbacks`: deprecate `:rescuable` option. *Bogdan Gusiev* -* Adds Integer#ordinal to get the ordinal suffix string of an integer. *Tim Gildea* +* Adds `Integer#ordinal` to get the ordinal suffix string of an integer. *Tim Gildea* -* AS::Callbacks: `:per_key` option is no longer supported +* `ActiveSupport::Callbacks`: `:per_key` option is no longer supported. *Bogdan Gusiev* -* `AS::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option. +* `ActiveSupport::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option. *Bogdan Gusiev* -* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva* +* Add `html_escape_once` to `ERB::Util`, and delegate the `escape_once` tag helper to it. *Carlos Antonio da Silva* -* Deprecates the compatibility method Module#local_constant_names, - use Module#local_constants instead (which returns symbols). *fxn* +* Deprecates the compatibility method `Module#local_constant_names`, + use `Module#local_constants` instead (which returns symbols). *Xavier Noria* -* Deletes the compatibility method Module#method_names, - use Module#methods from now on (which returns symbols). *fxn* +* Deletes the compatibility method `Module#method_names`, + use `Module#methods` from now on (which returns symbols). *Xavier Noria* -* Deletes the compatibility method Module#instance_method_names, - use Module#instance_methods from now on (which returns symbols). *fxn* +* Deletes the compatibility method `Module#instance_method_names`, + use `Module#instance_methods` from now on (which returns symbols). *Xavier Noria* -* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger - from Ruby stdlib. +* `BufferedLogger` is deprecated. Use `ActiveSupport::Logger`, or the logger + from the Ruby standard library. -* Unicode database updated to 6.1.0. + *Aaron Patterson* -* Adds `encode_big_decimal_as_string` option to force JSON serialization of BigDecimals as numeric instead - of wrapping them in strings for safety. +* Unicode database updated to 6.1.0. *Norman Clarke* -* Remove deprecated ActiveSupport::JSON::Variable. *Erich Menge* +* Adds `encode_big_decimal_as_string` option to force JSON serialization of `BigDecimal` as numeric instead + of wrapping them in strings for safety. * Optimize log subscribers to check log level before doing any processing. *Brian Durand* diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 4c9e59dbd2..64b23996e8 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -23,6 +23,6 @@ Gem::Specification.new do |s| s.add_dependency 'i18n', '~> 0.6' s.add_dependency 'multi_json', '~> 1.3' s.add_dependency 'tzinfo', '~> 0.3.33' - s.add_dependency 'minitest', '~> 4.1' + s.add_dependency 'minitest', '~> 4.2' s.add_dependency 'thread_safe','~> 0.1' end diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index c7dbfc95dc..512296554f 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -7,6 +7,7 @@ end require 'digest/md5' require 'active_support/core_ext/marshal' +require 'active_support/core_ext/array/extract_options' module ActiveSupport module Cache diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 9a2dc6e7c5..3c4bfc5f1e 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -29,9 +29,9 @@ class Class # # class Foo; end # class Bar < Foo; end - # class Baz < Foo; end + # class Baz < Bar; end # - # Foo.subclasses # => [Baz, Bar] + # Foo.subclasses # => [Bar] def subclasses subclasses, chain = [], descendants chain.each do |k| 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 1d3682eaf2..97aad008f5 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -124,6 +124,18 @@ class DateTime end alias :at_end_of_hour :end_of_hour + # Returns a new DateTime representing the start of the minute (hh:mm:00). + def beginning_of_minute + change(:sec => 0) + end + alias :at_beginning_of_minute :beginning_of_minute + + # Returns a new DateTime representing the end of the minute (hh:mm:59). + def end_of_minute + change(:sec => 59) + end + alias :at_end_of_minute :end_of_minute + # Adjusts DateTime to UTC by adding its offset value; offset is set to 0. # # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600 diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 1079ddde98..534bbe3c42 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -1,35 +1,43 @@ class Object - # Invokes the public method identified by the symbol +method+, passing it any arguments - # and/or the block specified, just like the regular Ruby <tt>Object#public_send</tt> does. + # Invokes the public method whose name goes as first argument just like + # +public_send+ does, except that if the receiver does not respond to it the + # call returns +nil+ rather than raising an exception. # - # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised - # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass. + # This method is defined to be able to write # - # This is also true if the receiving object does not implemented the tried method. It will - # return +nil+ in that case as well. - # - # If try is called without a method to call, it will yield any given block with the object. + # @person.try(:name) # - # Please also note that +try+ is defined on +Object+, therefore it won't work with - # subclasses of +BasicObject+. For example, using try with +SimpleDelegator+ will - # delegate +try+ to target instead of calling it on delegator itself. + # instead of # - # Without +try+ - # @person && @person.name - # or # @person ? @person.name : nil # - # With +try+ - # @person.try(:name) + # +try+ returns +nil+ when called on +nil+ regardless of whether it responds + # to the method: + # + # nil.try(:to_i) # => nil, rather than 0 + # + # Arguments and blocks are forwarded to the method if invoked: + # + # @posts.try(:each_slice, 2) do |a, b| + # ... + # end + # + # The number of arguments in the signature must match. If the object responds + # to the method the call is attempted and +ArgumentError+ is still raised + # otherwise. # - # +try+ also accepts arguments and/or a block, for the method it is trying - # Person.try(:find, 1) - # @people.try(:collect) {|p| p.name} + # If +try+ is called without arguments it yields the receiver to a given + # block unless it is +nil+: # - # Without a method argument try will yield to the block unless the receiver is nil. - # @person.try { |p| "#{p.first_name} #{p.last_name}" } + # @person.try do |p| + # ... + # end # - # +try+ behaves like +Object#public_send+, unless called on +NilClass+. + # Please also note that +try+ is defined on +Object+, therefore it won't work + # with instances of classes that do not have +Object+ among their ancestors, + # like direct subclasses of +BasicObject+. For example, using +try+ with + # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on + # delegator itself. def try(*a, &b) if a.empty? && block_given? yield self diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index e05447439a..a1b3f79748 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -3,6 +3,8 @@ class String # the string, and then changing remaining consecutive whitespace # groups into one space each. # + # Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E). + # # %{ Multi-line # string }.squish # => "Multi-line string" # " foo bar \n \t boo".squish # => "foo bar boo" @@ -12,8 +14,9 @@ class String # Performs a destructive squish. See String#squish. def squish! - strip! - gsub!(/\s+/, ' ') + gsub!(/\A[[:space:]]+/, '') + gsub!(/[[:space:]]+\z/, '') + gsub!(/[[:space:]]+/, ' ') self end diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 1f95f62229..a3ce7dbe3f 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -188,6 +188,21 @@ class Time end alias :at_end_of_hour :end_of_hour + # Returns a new Time representing the start of the minute (x:xx:00) + def beginning_of_minute + change(:sec => 0) + end + alias :at_beginning_of_minute :beginning_of_minute + + # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9) + def end_of_minute + change( + :sec => 59, + :usec => Rational(999999999, 1000) + ) + end + alias :at_end_of_minute :end_of_minute + # Returns a Range representing the whole day of the current time. def all_day beginning_of_day..end_of_day diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb index 6ef33ab683..b837c879bb 100644 --- a/activesupport/lib/active_support/gzip.rb +++ b/activesupport/lib/active_support/gzip.rb @@ -25,9 +25,9 @@ module ActiveSupport end # Compresses a string using gzip. - def self.compress(source) + def self.compress(source, level=Zlib::DEFAULT_COMPRESSION, strategy=Zlib::DEFAULT_STRATEGY) output = Stream.new - gz = Zlib::GzipWriter.new(output) + gz = Zlib::GzipWriter.new(output, level, strategy) gz.write(source) gz.close output.string diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb index 188653bd9b..22521a8e93 100644 --- a/activesupport/lib/active_support/i18n.rb +++ b/activesupport/lib/active_support/i18n.rb @@ -1,4 +1,7 @@ begin + require 'active_support/core_ext/hash/deep_merge' + require 'active_support/core_ext/hash/except' + require 'active_support/core_ext/hash/slice' require 'i18n' require 'active_support/lazy_load_hooks' rescue LoadError => e diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index b7dc0689b0..ce40a7d689 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -1,5 +1,6 @@ require 'openssl' require 'base64' +require 'active_support/core_ext/array/extract_options' module ActiveSupport # MessageEncryptor is a simple way to encrypt values which get stored diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index f49ca47f14..cbc1608349 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -11,7 +11,7 @@ module ActiveSupport NORMALIZATION_FORMS = [:c, :kc, :d, :kd] # The Unicode version that is supported by the implementation - UNICODE_VERSION = '6.1.0' + UNICODE_VERSION = '6.2.0' # The default normalization used for operations that require # normalization. It can be set to any of the normalizations diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 2191471daa..cc935e6cb9 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -580,7 +580,7 @@ module ActiveSupport unit = case units when Hash - units[DECIMAL_UNITS[display_exponent]] + units[DECIMAL_UNITS[display_exponent]] || '' when String, Symbol I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i) else diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index e4f8959e7a..d70d971538 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,4 +1,6 @@ require 'rbconfig' +require 'minitest/parallel_each' + module ActiveSupport module Testing class RemoteError < StandardError @@ -43,39 +45,6 @@ module ActiveSupport module Isolation require 'thread' - # Recent versions of MiniTest (such as the one shipped with Ruby 2.0) already define - # a ParallelEach class. - unless defined? ParallelEach - class ParallelEach - include Enumerable - - # default to 2 cores - CORES = (ENV['TEST_CORES'] || 2).to_i - - def initialize list - @list = list - @queue = SizedQueue.new CORES - end - - def grep pattern - self.class.new super - end - - def each - threads = CORES.times.map { - Thread.new { - while job = @queue.pop - yield job - end - } - } - @list.each { |i| @queue << i } - CORES.times { @queue << nil } - threads.each(&:join) - end - end - end - def self.included(klass) #:nodoc: klass.extend(Module.new { def test_methods diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index ff13efa990..98c866ac43 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -154,7 +154,7 @@ module ActiveSupport # # => "2005/02/01 15:15:10 +0000" def as_json(options = nil) if ActiveSupport::JSON::Encoding.use_standard_json_time_format - xmlschema + xmlschema(3) else %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) end @@ -317,6 +317,10 @@ module ActiveSupport end alias_method :tv_sec, :to_i + def to_r + utc.to_r + end + # Return an instance of Time in the system timezone. def to_time utc.to_time diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat Binary files differindex df17a8cccf..2571faa019 100644 --- a/activesupport/lib/active_support/values/unicode_tables.dat +++ b/activesupport/lib/active_support/values/unicode_tables.dat diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 24e62cc2b9..59ca2a4059 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -88,6 +88,14 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2005,2,4,19,59,59), DateTime.civil(2005,2,4,19,30,10).end_of_hour end + def test_beginning_of_minute + assert_equal DateTime.civil(2005,2,4,19,30,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_minute + end + + def test_end_of_minute + assert_equal DateTime.civil(2005,2,4,19,30,59), DateTime.civil(2005,2,4,19,30,10).end_of_minute + end + def test_end_of_month assert_equal DateTime.civil(2005,3,31,23,59,59), DateTime.civil(2005,3,20,10,10,10).end_of_month assert_equal DateTime.civil(2005,2,28,23,59,59), DateTime.civil(2005,2,20,10,10,10).end_of_month diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 68918b040f..bff155f045 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -229,10 +229,11 @@ class StringInflectionsTest < ActiveSupport::TestCase end def test_string_squish - original = %{ A string with tabs(\t\t), newlines(\n\n), and - many spaces( ). } + original = %{\u180E\u180E A string surrounded by unicode mongolian vowel separators, + with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces( ). \u180E\u180E} - expected = "A string with tabs( ), newlines( ), and many spaces( )." + expected = "A string surrounded by unicode mongolian vowel separators, " + + "with tabs( ), newlines( ), unicode nextlines( ) and many spaces( )." # Make sure squish returns what we expect: assert_equal original.squish, expected diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 43c92003dc..2864d7a57f 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -121,6 +121,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005,2,4,19,0,0), Time.local(2005,2,4,19,30,10).beginning_of_hour end + def test_beginning_of_minute + assert_equal Time.local(2005,2,4,19,30,0), Time.local(2005,2,4,19,30,10).beginning_of_minute + end + def test_end_of_day assert_equal Time.local(2007,8,12,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,12,10,10,10).end_of_day with_env_tz 'US/Eastern' do @@ -137,6 +141,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005,2,4,19,59,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_hour end + def test_end_of_minute + assert_equal Time.local(2005,2,4,19,30,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_minute + end + def test_last_year assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).last_year end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 18eca4cd8b..0f5699fd63 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -75,7 +75,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_to_json_with_use_standard_json_time_format_config_set_to_true old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, true - assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(@twz) + assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(@twz) ensure ActiveSupport.use_standard_json_time_format = old end @@ -326,6 +326,17 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal 946684800, twz.to_i end + def test_to_r + result = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii']).to_r + assert_equal Rational(946684800, 1), result + assert_kind_of Rational, result + end + + def test_time_at + time = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone['Hawaii']) + assert_equal time, Time.at(time) + end + def test_to_time with_env_tz 'US/Eastern' do assert_equal Time, @twz.to_time.class @@ -539,6 +550,20 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal "Fri, 31 Dec 1999 19:59:59 EST -05:00", twz.end_of_hour.inspect end + def test_beginning_of_minute + utc = Time.utc(2000, 1, 1, 0, 30, 10) + twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) + assert_equal "Fri, 31 Dec 1999 19:30:10 EST -05:00", twz.inspect + assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", twz.beginning_of_hour.inspect + end + + def test_end_of_minute + utc = Time.utc(2000, 1, 1, 0, 30, 10) + twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) + assert_equal "Fri, 31 Dec 1999 19:30:10 EST -05:00", twz.inspect + assert_equal "Fri, 31 Dec 1999 19:30:59 EST -05:00", twz.end_of_minute.inspect + end + def test_since assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect end diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb index 75a0505899..0e3cf3b429 100644 --- a/activesupport/test/gzip_test.rb +++ b/activesupport/test/gzip_test.rb @@ -4,6 +4,12 @@ require 'active_support/core_ext/object/blank' class GzipTest < ActiveSupport::TestCase def test_compress_should_decompress_to_the_same_value assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World")) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::NO_COMPRESSION)) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::BEST_SPEED)) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", Zlib::BEST_COMPRESSION)) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, Zlib::FILTERED)) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, Zlib::HUFFMAN_ONLY)) + assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World", nil, nil)) end def test_compress_should_return_a_binary_string @@ -12,4 +18,16 @@ class GzipTest < ActiveSupport::TestCase assert_equal Encoding.find('binary'), compressed.encoding assert !compressed.blank?, "a compressed blank string should not be blank" end + + def test_compress_should_return_gzipped_string_by_compression_level + source_string = "Hello World"*100 + + gzipped_by_speed = ActiveSupport::Gzip.compress(source_string, Zlib::BEST_SPEED) + assert_equal 1, Zlib::GzipReader.new(StringIO.new(gzipped_by_speed)).level + + gzipped_by_best_compression = ActiveSupport::Gzip.compress(source_string, Zlib::BEST_COMPRESSION) + assert_equal 9, Zlib::GzipReader.new(StringIO.new(gzipped_by_best_compression)).level + + assert_equal true, (gzipped_by_best_compression.bytesize < gzipped_by_speed.bytesize) + end end diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index 5f54587f93..1fadef3637 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -301,6 +301,13 @@ module ActiveSupport end end + def test_number_to_human_with_custom_units_that_are_missing_the_needed_key + [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| + assert_equal '123', number_helper.number_to_human(123, units: { thousand: 'k'}) + assert_equal '123', number_helper.number_to_human(123, units: {}) + end + end + def test_number_to_human_with_custom_format [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| assert_equal '123 times Thousand', number_helper.number_to_human(123456, :format => "%n times %u") diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index 14ba4e0076..6aea9d56f1 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -2,6 +2,7 @@ require 'abstract_unit' require 'active_support/json' require 'active_support/core_ext/object/to_json' require 'active_support/core_ext/hash/indifferent_access' +require 'active_support/core_ext/array/extract_options' class OrderedHashTest < ActiveSupport::TestCase def setup diff --git a/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb index ed2667ac58..d169e9452c 100644 --- a/guides/code/getting_started/config/environments/development.rb +++ b/guides/code/getting_started/config/environments/development.rb @@ -22,10 +22,6 @@ Blog::Application.configure do # Only use best-standards-support built into browsers. config.action_dispatch.best_standards_support = :builtin - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL). - config.active_record.auto_explain_threshold_in_seconds = 0.5 - # Raise an error on page load if there are pending migrations config.active_record.migration_error = :page_load diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb index 58dab2a319..368a735122 100644 --- a/guides/code/getting_started/config/environments/production.rb +++ b/guides/code/getting_started/config/environments/production.rb @@ -72,10 +72,6 @@ Blog::Application.configure do # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL). - # config.active_record.auto_explain_threshold_in_seconds = 0.5 - # Disable automatic flushing of the log to improve performance. # config.autoflush_log = false diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index a678dd9d90..9c157ec0b3 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -3,7 +3,7 @@ Ruby on Rails 4.0 Release Notes Highlights in Rails 4.0: -* Ruby 1.9.3 only +* Ruby 2.0 preferred; 1.9.3+ required * Strong Parameters * Turbolinks * Russian Doll Caching diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 7260a48c8c..da155628f3 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -116,9 +116,9 @@ When this form is submitted, the value of `params[:client]` will be `{"name" => Note that the `params` hash is actually an instance of `ActiveSupport::HashWithIndifferentAccess`, which acts like a hash that lets you use symbols and strings interchangeably as keys. -### JSON/XML parameters +### JSON parameters -If you're writing a web service application, you might find yourself more comfortable on accepting parameters in JSON or XML format. Rails will automatically convert your parameters into `params` hash, which you'll be able to access like you would normally do with form data. +If you're writing a web service application, you might find yourself more comfortable on accepting parameters in JSON format. Rails will automatically convert your parameters into `params` hash, which you'll be able to access like you would normally do with form data. So for example, if you are sending this JSON parameter: @@ -128,7 +128,7 @@ So for example, if you are sending this JSON parameter: You'll get `params[:company]` as `{ :name => "acme", "address" => "123 Carrot Street" }`. -Also, if you've turned on `config.wrap_parameters` in your initializer or calling `wrap_parameters` in your controller, you can safely omit the root element in the JSON/XML parameter. The parameters will be cloned and wrapped in the key according to your controller's name by default. So the above parameter can be written as: +Also, if you've turned on `config.wrap_parameters` in your initializer or calling `wrap_parameters` in your controller, you can safely omit the root element in the JSON parameter. The parameters will be cloned and wrapped in the key according to your controller's name by default. So the above parameter can be written as: ```json { "name": "acme", "address": "123 Carrot Street" } @@ -142,6 +142,8 @@ And assume that you're sending the data to `CompaniesController`, it would then You can customize the name of the key or specific parameters you want to wrap by consulting the [API documentation](http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html) +NOTE: A support for parsing XML parameters has been extracted into a gem named `actionpack-xml_parser` + ### Routing Parameters The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id` will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL: diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 516457bcd3..bb42fab101 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -342,19 +342,17 @@ By using the `after_commit` callback we can account for this case. ```ruby class PictureFile < ActiveRecord::Base - attr_accessor :delete_file + after_commit :delete_picture_file_from_disk, :on => [:destroy] - after_destroy do |picture_file| - picture_file.delete_file = picture_file.filepath - end - - after_commit do |picture_file| - if picture_file.delete_file && File.exist?(picture_file.delete_file) - File.delete(picture_file.delete_file) - picture_file.delete_file = nil + def delete_picture_file_from_disk + if File.exist?(filepath) + File.delete(filepath) end end end ``` +NOTE: the `:on` option specifies when a callback will be fired. If you +don't supply the `:on` option the callback will fire for every action. + The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback. diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 62d6294ae5..bc3b1669d2 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1609,45 +1609,6 @@ EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1) under MySQL. -### Automatic EXPLAIN - -Active Record is able to run EXPLAIN automatically on slow queries and log its -output. This feature is controlled by the configuration parameter - -```ruby -config.active_record.auto_explain_threshold_in_seconds -``` - -If set to a number, any query exceeding those many seconds will have its EXPLAIN -automatically triggered and logged. In the case of relations, the threshold is -compared to the total time needed to fetch records. So, a relation is seen as a -unit of work, no matter whether the implementation of eager loading involves -several queries under the hood. - -A threshold of `nil` disables automatic EXPLAINs. - -The default threshold in development mode is 0.5 seconds, and `nil` in test and -production modes. - -INFO. Automatic EXPLAIN gets disabled if Active Record has no logger, regardless -of the value of the threshold. - -#### Disabling Automatic EXPLAIN - -Automatic EXPLAIN can be selectively silenced with `ActiveRecord::Base.silence_auto_explain`: - -```ruby -ActiveRecord::Base.silence_auto_explain do - # no automatic EXPLAIN is triggered here -end -``` - -That may be useful for queries you know are slow but fine, like a heavyweight -report of an admin interface. - -As its name suggests, `silence_auto_explain` only silences automatic EXPLAINs. -Explicit calls to `ActiveRecord::Relation#explain` run. - ### Interpreting EXPLAIN Interpretation of the output of EXPLAIN is beyond the scope of this guide. The diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index eaa47d4ebf..32641d04c1 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -618,6 +618,35 @@ class GoodnessValidator < ActiveModel::Validator end ``` +Note that the validator will be initialized *only once* for the whole application +life cycle, and not on each validation run, so be careful about using instance +variables inside it. + +If your validator is complex enough that you want instance variables, you can +easily use a plain old Ruby object instead: + +```ruby +class Person < ActiveRecord::Base + validate do |person| + GoodnessValidator.new(person).validate + end +end + +class GoodnessValidator + def initialize(person) + @person = person + end + + def validate + if some_complex_condition_involving_ivars_and_private_methods? + @person.errors[:base] << "This person is evil" + end + end + + # … +end +``` + ### `validates_each` This helper validates attributes against a block. It doesn't have a predefined diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 3c1bb0f132..f02b377832 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1233,6 +1233,8 @@ The method `squish` strips leading and trailing whitespace, and substitutes runs There's also the destructive version `String#squish!`. +Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E). + NOTE: Defined in `active_support/core_ext/string/filters.rb`. ### `truncate` diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index e52264f296..a270ec7a7e 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -99,14 +99,14 @@ end This method generates a cache key that depends on all products and can be used in the view: -```ruby +```erb <% cache(cache_key_for_products) do %> All available products: <% end %> ``` You can also use an Active Record model as the cache key: -```ruby +```erb <% Product.all.each do |p| %> <% cache(p) do %> <%= link_to p.name, product_url(p) %> @@ -118,7 +118,7 @@ Behind the scenes, a method called `cache_key` will be invoked on the model and You can also combine the two schemes which is called "Russian Doll Caching": -```ruby +```erb <% cache(cache_key_for_products) do %> All available products: <% Product.all.each do |p| %> diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 2790a4740a..9d1fb03fab 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -378,7 +378,7 @@ Active Record version 4.0.0.beta Action Pack version 4.0.0.beta Action Mailer version 4.0.0.beta Active Support version 4.0.0.beta -Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::EncryptedCookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport +Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::EncryptedCookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp Environment development Database adapter sqlite3 diff --git a/guides/source/configuring.md b/guides/source/configuring.md index c1f31fd2b0..dbbeec7126 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -207,7 +207,6 @@ Every Rails application comes with a standard set of middleware which it uses in * `ActionDispatch::ParamsParser` parses out parameters from the request into `params`. * `Rack::MethodOverride` allows the method to be overridden if `params[:_method]` is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types. * `ActionDispatch::Head` converts HEAD requests to GET requests and serves them as so. -* `ActionDispatch::BestStandardsSupport` enables "best standards support" so that IE8 renders some elements correctly. Besides these usual middleware, you can add your own by using the `config.middleware.use` method: @@ -230,13 +229,13 @@ config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns Middlewares can also be completely swapped out and replaced with others: ```ruby -config.middleware.swap ActionDispatch::BestStandardsSupport, Magical::Unicorns +config.middleware.swap ActionController::Failsafe, Lifo::Failsafe ``` They can also be removed from the stack completely: ```ruby -config.middleware.delete ActionDispatch::BestStandardsSupport +config.middleware.delete "Rack::MethodOverride" ``` ### Configuring i18n @@ -269,8 +268,6 @@ config.middleware.delete ActionDispatch::BestStandardsSupport * `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is true by default. -* `config.active_record.auto_explain_threshold_in_seconds` configures the threshold for automatic EXPLAINs (`nil` disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode. - * +config.active_record.cache_timestamp_format+ controls the format of the timestamp value in the cache key. Default is +:number+. The MySQL adapter adds one additional configuration option: diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index baef620c6c..7909a00c47 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -85,7 +85,7 @@ You can also run any single test separately: $ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb ``` -You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server. +You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server. ### Warnings diff --git a/guides/source/engines.md b/guides/source/engines.md index 459aa8d57e..00939c4ff2 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -28,7 +28,7 @@ Engines can also be isolated from their host applications. This means that an ap It's important to keep in mind at all times that the application should **always** take precedence over its engines. An application is the object that has final say in what goes on in the universe (with the universe being the application's environment) where the engine should only be enhancing it, rather than changing it drastically. -To see demonstrations of other engines, check out [Devise](https://github.com/plataformatec/devise), an engine that provides authentication for its parent applications, or [Forem](https://github.com/radar/forem), an engine that provides forum functionality. There's also [Spree](https://github.com/spree/spree) which provides an e-commerce platform, and [RefineryCMS](https://github.com/resolve/refinerycms), a CMS engine. +To see demonstrations of other engines, check out [Devise](https://github.com/plataformatec/devise), an engine that provides authentication for its parent applications, or [Forem](https://github.com/radar/forem), an engine that provides forum functionality. There's also [Spree](https://github.com/spree/spree) which provides an e-commerce platform, and [RefineryCMS](https://github.com/refinery/refinerycms), a CMS engine. Finally, engines would not have been possible without the work of James Adam, Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever meet them, don't forget to say thanks! @@ -57,7 +57,7 @@ The `--full` option tells the generator that you want to create an engine, inclu end ``` * A file at `lib/blorgh/engine.rb` which is identical in function to a standard Rails application's `config/application.rb` file: - + ```ruby module Blorgh class Engine < ::Rails::Engine @@ -72,12 +72,12 @@ The `--mountable` option tells the generator that you want to create a "mountabl * A namespaced `ApplicationHelper` stub * A layout view template for the engine * Namespace isolation to `config/routes.rb`: - + ```ruby Blorgh::Engine.routes.draw do end ``` - + * Namespace isolation to `lib/blorgh/engine.rb`: ```ruby @@ -395,7 +395,7 @@ def create @post = Post.find(params[:post_id]) @comment = @post.comments.create(params[:comment]) flash[:notice] = "Comment has been created!" - redirect_to post_path + redirect_to posts_path end ``` @@ -650,6 +650,14 @@ self.author = Blorgh.user_class.find_or_create_by(name: author_name) Resulting in something a little shorter, and more implicit in its behavior. The `user_class` method should always return a `Class` object. +Since we changed the `user_class` method to no longer return a +`String` but a `Class` we must also modify our `belongs_to` definition +in the `Blorgh::Post` model: + +```ruby +belongs_to :author, class_name: Blorgh.user_class.to_s +``` + To set this configuration setting within the application, an initializer should be used. By using an initializer, the configuration will be set up before the application starts and calls the engine's models which may depend on this configuration setting existing. Create a new initializer at `config/initializers/blorgh.rb` inside the application where the `blorgh` engine is installed and put this content in it: @@ -789,7 +797,7 @@ module Blorgh::Concerns::Models::Post extend ActiveSupport::Concern # 'included do' causes the included code to be evaluated in the - # context where it is included (post.rb), rather than be + # context where it is included (post.rb), rather than be # executed in the module's context (blorgh/concerns/models/post). included do attr_accessor :author_name diff --git a/guides/source/generators.md b/guides/source/generators.md index 8b91dfc5a5..d7c789e2d8 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -310,7 +310,7 @@ In Rails 3.0 and above, generators don't just look in the source root for templa ```erb module <%= class_name %>Helper - attr_reader :<%= plural_name %>, <%= plural_name.singularize %> + attr_reader :<%= plural_name %>, :<%= plural_name.singularize %> end ``` @@ -412,7 +412,7 @@ This command will generate the `Thud` application, and then apply the template t Templates don't have to be stored on the local system, the `-m` option also supports online templates: ```bash -$ rails new thud -m https://gist.github.com/722911.txt +$ rails new thud -m https://gist.github.com/radar/722911/raw/ ``` Whilst the final section of this guide doesn't cover how to generate the most awesome template known to man, it will take you through the methods available at your disposal so that you can develop it yourself. These same methods are also available for generators. diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index c394f30c38..87f5e43157 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -21,7 +21,7 @@ application from scratch. It does not assume that you have any prior experience with Rails. However, to get the most out of it, you need to have some prerequisites installed: -* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or higher +* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer * The [RubyGems](http://rubygems.org/) packaging system * To learn more about RubyGems, please read the [RubyGems User Guide](http://docs.rubygems.org/read/book/1) * A working installation of the [SQLite3 Database](http://www.sqlite.org) @@ -84,7 +84,7 @@ current version of Ruby installed: ```bash $ ruby -v -ruby 1.9.3p327 +ruby 1.9.3p385 ``` To install Rails, use the `gem install` command provided by RubyGems: diff --git a/guides/source/kindle/KINDLE.md b/guides/source/kindle/KINDLE.md index 08937e053e..8c4fad18aa 100644 --- a/guides/source/kindle/KINDLE.md +++ b/guides/source/kindle/KINDLE.md @@ -10,7 +10,7 @@ ## Resources * [Stack Overflow: Kindle Periodical Format](http://stackoverflow.com/questions/5379565/kindle-periodical-format) - * Example Periodical [.ncx](https://gist.github.com/808c971ed087b839d462) and [.opf](https://gist.github.com/d6349aa8488eca2ee6d0) + * Example Periodical [.ncx](https://gist.github.com/mipearson/808c971ed087b839d462) and [.opf](https://gist.github.com/mipearson/d6349aa8488eca2ee6d0) * [Kindle Publishing Guidelines](http://kindlegen.s3.amazonaws.com/AmazonKindlePublishingGuidelines.pdf) * [KindleGen & Kindle Previewer](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000234621) diff --git a/guides/source/migrations.md b/guides/source/migrations.md index cefbc3b829..d738d847e9 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -344,6 +344,16 @@ create_join_table :products, :categories, column_options: {null: true} will create the `product_id` and `category_id` with the `:null` option as `true`. +`create_join_table` also accepts a block, which you can use to add indices +(which are not created by default) or additional columns: + +```ruby +create_join_table :products, :categories do |t| + t.index :products + t.index :categories +end +``` + ### Changing Tables A close cousin of `create_table` is `change_table`, used for changing existing @@ -444,7 +454,7 @@ class ExampleMigration < ActiveRecord::Migration end ``` -Using `reversible` will insure that the instructions are executed in the +Using `reversible` will ensure that the instructions are executed in the right order too. If the previous example migration is reverted, the `down` block will be run after the `home_page_url` column is removed and right before the table `products` is dropped. diff --git a/guides/source/plugins.md b/guides/source/plugins.md index f8f04c3c67..695f25f8a9 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -86,7 +86,7 @@ Run `rake` to run the test. This test should fail because we haven't implemented Great - now you are ready to start development. -Then in `lib/yaffle.rb` require `lib/core_ext`: +Then in `lib/yaffle.rb` add `require "yaffle/core_ext"`: ```ruby # yaffle/lib/yaffle.rb diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index a6119eb433..d8477d89e3 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -137,7 +137,6 @@ use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag -use ActionDispatch::BestStandardsSupport run MyApp::Application.routes ``` @@ -215,7 +214,6 @@ And to remove browser related middleware, ```ruby # config/application.rb -config.middleware.delete "ActionDispatch::BestStandardsSupport" config.middleware.delete "Rack::MethodOverride" ``` @@ -307,10 +305,6 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Adds ETag header on all String bodies. ETags are used to validate cache. - **`ActionDispatch::BestStandardsSupport`** - -* Enables “best standards support” so that IE8 renders some elements correctly. - TIP: It's possible to use any of the above middlewares in your custom Rack stack. ### Using Rack Builder diff --git a/guides/source/routing.md b/guides/source/routing.md index 14f23d4020..4614169653 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -832,6 +832,19 @@ will recognize incoming paths beginning with `/photos` but route to the `Images` NOTE: Use `photos_path`, `new_photo_path`, etc. to generate paths for this resource. +For namespaced controllers you can use the directory notation. For example: + +```ruby +resources :user_permissions, controller: 'admin/user_permissions' +``` + +This will route to the `Admin::UserPermissions` controller. + +NOTE: Only the directory notation is supported. specifying the +controller with ruby constant notation (eg. `:controller => +'Admin::UserPermissions'`) can lead to routing problems and results in +a warning. + ### Specifying Constraints You can use the `:constraints` option to specify a required format on the implicit `id`. For example: diff --git a/guides/source/testing.md b/guides/source/testing.md index 39a44794a7..540197e6e7 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -774,10 +774,13 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai Brief Note About `Test::Unit` ----------------------------- -Ruby ships with a boat load of libraries. One little gem of a library is `Test::Unit`, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in `Test::Unit::Assertions`. The class `ActiveSupport::TestCase` which we have been using in our unit and functional tests extends `Test::Unit::TestCase`, allowing +Ruby ships with a boat load of libraries. Ruby 1.8 provides `Test::Unit`, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in `Test::Unit::Assertions`. The class `ActiveSupport::TestCase` which we have been using in our unit and functional tests extends `Test::Unit::TestCase`, allowing us to use all of the basic assertions in our tests. +Ruby 1.9 introduced `MiniTest`, an updated version of `Test::Unit` which provides a backwards compatible API for `Test::Unit`. You could also use `MiniTest` in Ruby 1.8 by installing the `minitest` gem. + NOTE: For more information on `Test::Unit`, refer to [test/unit Documentation](http://ruby-doc.org/stdlib/libdoc/test/unit/rdoc/) +For more information on `MiniTest`, refer to [Minitest](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/minitest/unit/rdoc/) Setup and Teardown ------------------ diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index b4a59fe3da..915a008a92 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -16,11 +16,11 @@ The best way to be sure that your application still works after upgrading is to Rails generally stays close to the latest released Ruby version when it's released: -* Rails 3 and above requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. -* Rails 3.2.x will be the last branch to support Ruby 1.8.7. -* Rails 4 will support only Ruby 1.9.3. +* Rails 3 and above require Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially. You should upgrade as early as possible. +* Rails 3.2.x is the last branch to support Ruby 1.8.7. +* Rails 4 prefers Ruby 2.0 and requires 1.9.3 or newer. -TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump on to 1.9.2 or 1.9.3 for smooth sailing. +TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing. Upgrading from Rails 3.2 to Rails 4.0 ------------------------------------- @@ -45,6 +45,12 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep * Rails 4.0 has changed `serialized_attributes` and `attr_readonly` to class methods only. Now you shouldn't use instance methods, it's deprecated. You must change them, e.g. `self.serialized_attributes` to `self.class.serialized_attributes`. +* Rails 4.0 has removed `attr_accessible` and `attr_protected` feature in favor of Strong Parameters. You can use the [Protected Attributes gem](https://github.com/rails/protected_attributes) to a smoothly upgrade path. + +### Active Resource + +Rails 4.0 extracted Active Resource to its our gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile. + ### Active Model * Rails 4.0 has changed how errors attach with the `ActiveModel::Validations::ConfirmationValidator`. Now when confirmation validations fail the error will be attached to `:#{attribute}_confirmation` instead of `attribute`. @@ -82,6 +88,19 @@ becomes get 'こんにちは', controller: 'welcome', action: 'index' ``` +* Rails 4.0 has removed ActionDispatch::BestStandardsSupport middleware, !DOCTYPE html already triggers standards mode per http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx and ChromeFrame header has been moved to `config.action_dispatch.default_headers` + +Remember you must also remove any references to the middleware from your application code, for example: + +```ruby +# Raise exception +config.middleware.insert_before(Rack::Lock, ActionDispatch::BestStandardsSupport) +``` + +Also check your environment settings for `config.action_dispatch.best_standards_support` and remove it if present. + +* In Rails 4.0, precompiling assets no longer automatically copies non-JS/CSS assets from `vendor/assets` and `lib/assets`. Rails application and engine developers should put these assets in `app/assets` or configure `config.assets.precompile`. + ### Active Support Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`. diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 03ef770352..7c4192ee26 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -111,7 +111,7 @@ paintIt = (element, backgroundColor, textColor) -> element.style.color = textColor $ -> - $("a[data-color]").click -> + $("a[data-background-color]").click -> backgroundColor = $(this).data("background-color") textColor = $(this).data("text-color") paintIt(this, backgroundColor, textColor) diff --git a/rails.gemspec b/rails.gemspec index 34957c9455..770847e87e 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -27,5 +27,5 @@ Gem::Specification.new do |s| s.add_dependency 'railties', version s.add_dependency 'bundler', '>= 1.2.2', '< 2.0' - s.add_dependency 'sprockets-rails', '~> 2.0.0.rc1' + s.add_dependency 'sprockets-rails', '~> 2.0.0.rc3' end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index c6ffe30ad3..2d197e21ea 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,19 +1,39 @@ ## Rails 4.0.0 (unreleased) ## +* Ability to use a custom builder by passing `--builder` (or `-b`) has been removed. Consider + using application template instead. See this guide for more detail: + http://guides.rubyonrails.org/rails_application_templates.html + + *Prem Sichanugrist* + +* fix rake db:* tasks to work with DATABASE_URL and without config/database.yml + + *Terence Lee* + +* Add notice message for destroy action in scaffold generator. + + *Rahul P. Chaudhari* + +* Add two new test rake tasks to speed up full test runs. + + * `test:all`: run tests quickly by merging all types and not resetting db. + * `test:all:db`: run tests quickly, but also reset db. + + *Ryan Davis* + +* Add `--rc` option to support the load of a custom rc file during the generation of a new app. + + *Amparo Luna* + +* Add `--no-rc` option to skip the loading of railsrc file during the generation of a new app. + + *Amparo Luna* + * Fixes database.yml when creating a new rails application with '.' Fix #8304 *Jeremy W. Rowe* -* Deprecate the `eager_load_paths` configuration and alias it to `autoload_paths`. - Since the default in Rails 4.0 is to run in 'threadsafe' mode we need to eager - load all of the paths in `autoload_paths`. This may have unintended consequences - if you have added 'lib' to `autoload_paths` such as loading unneeded code or - code intended only for development and/or test environments. If this applies to - your application you should thoroughly check what is being eager loaded. - - *Andrew White* - * Restore Rails::Engine::Railties#engines with deprecation to ensure compatibility with gems such as Thinking Sphinx Fix #8551 diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb index 8937e10db3..44f4d3dabc 100644 --- a/railties/lib/rails/app_rails_loader.rb +++ b/railties/lib/rails/app_rails_loader.rb @@ -3,12 +3,16 @@ require 'pathname' module Rails module AppRailsLoader RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"] - EXECUTABLE = 'bin/rails' + EXECUTABLES = ['bin/rails', 'script/rails'] def self.exec_app_rails - cwd = Dir.pwd - return unless in_rails_application_or_engine? || in_rails_application_or_engine_subdirectory? - exec RUBY, EXECUTABLE, *ARGV if in_rails_application_or_engine? + cwd = Dir.pwd + + exe = find_executable + exe ||= find_executable_in_parent_path + return unless exe + + exec RUBY, exe, *ARGV if find_executable Dir.chdir("..") do # Recurse in a chdir block: if the search fails we want to be sure # the application is generated in the original working directory. @@ -18,12 +22,16 @@ module Rails # could not chdir, no problem just return end - def self.in_rails_application_or_engine? - File.exists?(EXECUTABLE) && File.read(EXECUTABLE) =~ /(APP|ENGINE)_PATH/ + def self.find_executable + EXECUTABLES.find do |exe| + File.exists?(exe) && File.read(exe) =~ /(APP|ENGINE)_PATH/ + end end - def self.in_rails_application_or_engine_subdirectory?(path = Pathname.new(Dir.pwd)) - File.exists?(File.join(path, EXECUTABLE)) || !path.root? && in_rails_application_or_engine_subdirectory?(path.parent) + def self.find_executable_in_parent_path(path = Pathname.new(Dir.pwd)) + EXECUTABLES.find do |exe| + File.exists?(File.join(path, exe)) || !path.root? && find_executable_in_parent_path(path.parent) + end end end end diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 05cc49d40a..5af7de720c 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -365,10 +365,6 @@ module Rails middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" - - if config.action_dispatch.best_standards_support - middleware.use ::ActionDispatch::BestStandardsSupport, config.action_dispatch.best_standards_support - end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 2c7ddd86e7..17763b39c5 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -97,12 +97,16 @@ module Rails self end - # Loads and returns the contents of the #database_configuration_file. The - # contents of the file are processed via ERB before being sent through - # YAML::load. + # Loads and returns the configuration of the database. + # First, looks at If ENV['DATABASE_URL'] if it's not present it uses the #paths["config/database"] + # The contents of the file are processed via ERB before being sent through YAML::load. def database_configuration - require 'erb' - YAML.load ERB.new(IO.read(paths["config/database"].first)).result + if ENV['DATABASE_URL'] + {Rails.env => ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.connection_url_to_hash(ENV['DATABASE_URL']).stringify_keys} + else + require 'erb' + YAML.load ERB.new(IO.read(paths["config/database"].first)).result + end rescue Psych::SyntaxError => e raise "YAML syntax error occurred while parsing #{paths["config/database"].first}. " \ "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 872d78d9a4..3ae60312c5 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -89,13 +89,6 @@ module Rails ActionDispatch::Reloader.to_cleanup(&callback) end end - - # Disable dependency loading during request cycle - initializer :disable_dependency_loading do - if config.eager_load && config.cache_classes - ActiveSupport::Dependencies.unhook! - end - end end end end diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb index b717b026de..e5341ac436 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -3,9 +3,6 @@ require 'rails/app_rails_loader' # If we are inside a Rails application this method performs an exec and thus # the rest of this script is not run. -# -# TODO: when we hit this, advise adding ./bin to $PATH instead. Then the -# app's `rails` executable is run immediately. Rails::AppRailsLoader.exec_app_rails require 'rails/ruby_version_check' diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb index 2d9708e5b5..2ff29418c6 100644 --- a/railties/lib/rails/commands/application.rb +++ b/railties/lib/rails/commands/application.rb @@ -9,12 +9,19 @@ if ARGV.first != "new" ARGV[0] = "--help" else ARGV.shift - railsrc = File.join(File.expand_path("~"), ".railsrc") - if File.exist?(railsrc) - extra_args_string = File.open(railsrc).read - extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten - puts "Using #{extra_args.join(" ")} from #{railsrc}" - ARGV.insert(1, *extra_args) + unless ARGV.delete("--no-rc") + customrc = ARGV.index{ |x| x.include?("--rc=") } + railsrc = if customrc + File.expand_path(ARGV.delete_at(customrc).gsub(/--rc=/, "")) + else + File.join(File.expand_path("~"), '.railsrc') + end + if File.exist?(railsrc) + extra_args_string = File.read(railsrc) + extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten + puts "Using #{extra_args.join(" ")} from #{railsrc}" + ARGV.insert(1, *extra_args) + end end end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 5fa7f043c6..15d13d5f28 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -27,11 +27,11 @@ module Rails # # Middlewares can also be completely swapped out and replaced with others: # - # config.middleware.swap ActionDispatch::BestStandardsSupport, Magical::Unicorns + # config.middleware.swap ActionDispatch::Flash, Magical::Unicorns # # And finally they can also be removed from the stack completely: # - # config.middleware.delete ActionDispatch::BestStandardsSupport + # config.middleware.delete ActionDispatch::Flash # class MiddlewareStackProxy def initialize diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 8a6d1f34aa..46a6485c44 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -34,8 +34,9 @@ module Rails # == Configuration # # Besides the +Railtie+ configuration which is shared across the application, in a - # <tt>Rails::Engine</tt> you can access <tt>autoload_paths</tt> and <tt>autoload_once_paths</tt>, - # which, differently from a <tt>Railtie</tt>, are scoped to the current engine. + # <tt>Rails::Engine</tt> you can access <tt>autoload_paths</tt>, <tt>eager_load_paths</tt> + # and <tt>autoload_once_paths</tt>, which, differently from a <tt>Railtie</tt>, are scoped to + # the current engine. # # class MyEngine < Rails::Engine # # Add a load path for this specific Engine @@ -455,9 +456,9 @@ module Rails end # Eager load the application by loading all ruby - # files inside autoload_paths. + # files inside eager_load paths. def eager_load! - config.autoload_paths.each do |load_path| + config.eager_load_paths.each do |load_path| matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/ Dir.glob("#{load_path}/**/*.rb").sort.each do |file| require_dependency file.sub(matcher, '\1') @@ -557,6 +558,7 @@ module Rails # Freeze so future modifications will fail rather than do nothing mysteriously config.autoload_paths.freeze + config.eager_load_paths.freeze config.autoload_once_paths.freeze end @@ -669,7 +671,7 @@ module Rails end def _all_autoload_paths #:nodoc: - @_all_autoload_paths ||= (config.autoload_paths + config.autoload_once_paths).uniq + @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq end def _all_load_paths #:nodoc: diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 2b23d8be38..10d1821709 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -4,7 +4,7 @@ module Rails class Engine class Configuration < ::Rails::Railtie::Configuration attr_reader :root - attr_writer :middleware, :autoload_once_paths, :autoload_paths + attr_writer :middleware, :eager_load_paths, :autoload_once_paths, :autoload_paths def initialize(root=nil) super() @@ -39,16 +39,16 @@ module Rails @paths ||= begin paths = Rails::Paths::Root.new(@root) - paths.add "app", autoload: true, glob: "*" + paths.add "app", eager_load: true, glob: "*" paths.add "app/assets", glob: "*" - paths.add "app/controllers", autoload: true - paths.add "app/helpers", autoload: true - paths.add "app/models", autoload: true - paths.add "app/mailers", autoload: true + paths.add "app/controllers", eager_load: true + paths.add "app/helpers", eager_load: true + paths.add "app/models", eager_load: true + paths.add "app/mailers", eager_load: true paths.add "app/views" - paths.add "app/controllers/concerns", autoload: true - paths.add "app/models/concerns", autoload: true + paths.add "app/controllers/concerns", eager_load: true + paths.add "app/models/concerns", eager_load: true paths.add "lib", load_path: true paths.add "lib/assets", glob: "*" @@ -76,13 +76,7 @@ module Rails end def eager_load_paths - ActiveSupport::Deprecation.warn "eager_load_paths is deprecated and all autoload_paths are now eagerly loaded." - autoload_paths - end - - def eager_load_paths=(paths) - ActiveSupport::Deprecation.warn "eager_load_paths is deprecated and all autoload_paths are now eagerly loaded." - self.autoload_paths = paths + @eager_load_paths ||= paths.eager_load end def autoload_once_paths diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index ca3652c703..3080b055a0 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -19,11 +19,8 @@ module Rails argument :app_path, type: :string def self.add_shared_options_for(name) - class_option :builder, type: :string, aliases: '-b', - desc: "Path to a #{name} builder (can be a filesystem path or URL)" - class_option :template, type: :string, aliases: '-m', - desc: "Path to an #{name} template (can be a filesystem path or URL)" + desc: "Path to some #{name} template (can be a filesystem path or URL)" class_option :skip_gemfile, type: :boolean, default: false, desc: "Don't create a Gemfile" @@ -61,6 +58,12 @@ module Rails class_option :skip_test_unit, type: :boolean, aliases: '-T', default: false, desc: 'Skip Test::Unit files' + class_option :rc, type: :string, default: false, + desc: "Path to file containing extra configuration options for rails command" + + class_option :no_rc, type: :boolean, default: false, + desc: 'Skip loading of extra configuration options from .railsrc file' + class_option :help, type: :boolean, aliases: '-h', group: :rails, desc: 'Show this help message and quit' end @@ -75,17 +78,6 @@ module Rails def builder @builder ||= begin - if path = options[:builder] - if URI(path).is_a?(URI::HTTP) - contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read } - else - contents = open(File.expand_path(path, @original_wd)) {|io| io.read } - end - - prok = eval("proc { #{contents} }", TOPLEVEL_BINDING, path, 1) - instance_eval(&prok) - end - builder_class = get_builder_class builder_class.send(:include, ActionMethods) builder_class.new(self) @@ -204,7 +196,7 @@ module Rails # Gems used only for assets and not required # in production environments by default. group :assets do - gem 'sprockets-rails', '~> 2.0.0.rc1' + gem 'sprockets-rails', '~> 2.0.0.rc3' gem 'sass-rails', '~> 4.0.0.beta' gem 'coffee-rails', '~> 4.0.0.beta' diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb index 5bf98bb6e0..cd69a017dd 100644 --- a/railties/lib/rails/generators/migration.rb +++ b/railties/lib/rails/generators/migration.rb @@ -52,7 +52,7 @@ module Rails if destination && options.force? remove_file(destination) elsif destination - raise Error, "Another migration is already named #{@migration_file_name}: #{destination}" + raise Error, "Another migration is already named #{@migration_file_name}: #{destination}. Use --force to remove the old migration file and replace it." end destination = File.join(migration_dir, "#{@migration_number}_#{@migration_file_name}.rb") end diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index d149413e2e..daf399a538 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -20,9 +20,6 @@ module <%= app_const_base %> # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. - # Custom directories with classes and modules you want to be autoloadable. - # config.autoload_paths += %W(#{config.root}/extras) - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml index 53620dc8e2..7ef89d6608 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml @@ -5,18 +5,18 @@ # gem install activerecord-sqlserver-adapter # # Ensure the activerecord adapter and db driver gems are defined in your Gemfile -# gem 'tiny_tds' -# gem 'activerecord-sqlserver-adapter' +# gem 'tiny_tds' +# gem 'activerecord-sqlserver-adapter' # # You should make sure freetds is configured correctly first. # freetds.conf contains host/port/protocol_versions settings. # http://freetds.schemamania.org/userguide/freetdsconf.htm # # A typical Microsoft server -# [mssql] +# [mssql] # host = mssqlserver.yourdomain.com -# port = 1433 -# tds version = 7.1 +# port = 1433 +# tds version = 7.1 # If you can connect with "tsql -S servername", your basic FreeTDS installation is working. # 'man tsql' for more info 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 bd0a0d44b8..d0e62d09cc 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 @@ -19,14 +19,7 @@ # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log - # Only use best-standards-support built into browsers. - config.action_dispatch.best_standards_support = :builtin - <%- unless options.skip_active_record? -%> - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL). - config.active_record.auto_explain_threshold_in_seconds = 0.5 - # Raise an error on page load if there are pending migrations config.active_record.migration_error = :page_load <%- 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 0ab91d9864..5669fe6d64 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 @@ -76,12 +76,6 @@ # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify - <%- unless options.skip_active_record? -%> - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL). - # config.active_record.auto_explain_threshold_in_seconds = 0.5 - <%- end -%> - # Disable automatic flushing of the log to improve performance. # config.autoflush_log = false diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index e813437d75..72281a2fef 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -47,7 +47,7 @@ class <%= controller_class_name %>Controller < ApplicationController # DELETE <%= route_url %>/1 def destroy @<%= orm_instance.destroy %> - redirect_to <%= index_helper %>_url + redirect_to <%= index_helper %>_url, notice: <%= "'#{human_name} was successfully destroyed.'" %> end private diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 80ba144441..de6795eda2 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -5,13 +5,13 @@ module Rails # paths by a Hash like API. It requires you to give a physical path on initialization. # # root = Root.new "/rails" - # root.add "app/controllers", autoload: true + # 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 <tt>Rails::Paths::Path</tt> object back like below: # # path = root["app/controllers"] - # path.autoload? # => true + # path.eager_load? # => true # path.is_a?(Rails::Paths::Path) # => true # # The +Path+ object is simply an enumerable and allows you to easily add extra paths: @@ -30,7 +30,7 @@ module Rails # root["config/routes"].inspect # => ["config/routes.rb"] # # The +add+ method accepts the following options as arguments: - # autoload, autoload_once and glob. + # eager_load, autoload, autoload_once and glob. # # Finally, the +Path+ object also provides a few helpers: # @@ -85,8 +85,7 @@ module Rails end def eager_load - ActiveSupport::Deprecation.warn "eager_load is deprecated and all autoload_paths are now eagerly loaded." - filter_by(:autoload?) + filter_by(:eager_load?) end def autoload_paths @@ -125,13 +124,9 @@ module Rails @glob = options[:glob] options[:autoload_once] ? autoload_once! : skip_autoload_once! + options[:eager_load] ? eager_load! : skip_eager_load! options[:autoload] ? autoload! : skip_autoload! options[:load_path] ? load_path! : skip_load_path! - - if !options.key?(:autoload) && options.key?(:eager_load) - ActiveSupport::Deprecation.warn "the :eager_load option is deprecated and all :autoload paths are now eagerly loaded." - options[:eager_load] ? autoload! : skip_autoload! - end end def children @@ -148,37 +143,22 @@ module Rails expanded.last end - %w(autoload_once autoload load_path).each do |m| + %w(autoload_once eager_load autoload load_path).each do |m| class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{m}! # def autoload! - @#{m} = true # @autoload = true + def #{m}! # def eager_load! + @#{m} = true # @eager_load = true end # end # - def skip_#{m}! # def skip_autoload! - @#{m} = false # @autoload = false + def skip_#{m}! # def skip_eager_load! + @#{m} = false # @eager_load = false end # end # - def #{m}? # def autoload? - @#{m} # @autoload + def #{m}? # def eager_load? + @#{m} # @eager_load end # end RUBY end - def eager_load! - ActiveSupport::Deprecation.warn "eager_load paths are deprecated and all autoload paths are now eagerly loaded." - autoload! - end - - def skip_eager_load! - ActiveSupport::Deprecation.warn "eager_load paths are deprecated and all autoload paths are now eagerly loaded." - skip_autoload! - end - - def eager_load? - ActiveSupport::Deprecation.warn "eager_load paths are deprecated and all autoload paths are now eagerly loaded." - autoload? - end - def each(&block) @paths.each(&block) end diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index 4536fedaa3..3b7f358a5b 100644 --- a/railties/lib/rails/ruby_version_check.rb +++ b/railties/lib/rails/ruby_version_check.rb @@ -2,12 +2,12 @@ if RUBY_VERSION < '1.9.3' desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" abort <<-end_message - Rails 4 requires Ruby 1.9.3+. + Rails 4 prefers to run on Ruby 2.0. You're running #{desc} - Please upgrade to continue. + Please upgrade to Ruby 1.9.3 or newer to continue. end_message end diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index ea6c074bdc..0057b0f887 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -102,7 +102,7 @@ namespace :doc do # desc "Generate Rails Guides" task :guides do # FIXME: Reaching outside lib directory is a bad idea - require File.expand_path('../../../../guides/rails_guides', __FILE__) + require File.expand_path('../../../../../guides/rails_guides', __FILE__) RailsGuides::Generator.new(Rails.root.join("doc/guides")).generate end end diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index f0d46fd959..44485d9b14 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -71,6 +71,18 @@ namespace :test do end end + # Inspired by: http://ngauthier.com/2012/02/quick-tests-with-bash.html + desc "Run tests quickly by merging all types and not resetting db" + Rake::TestTask.new(:all) do |t| + t.libs << "test" + t.pattern = "test/**/*_test.rb" + end + + namespace :all do + desc "Run tests quickly, but also reset db" + task :db => %w[db:test:prepare test:all] + end + Rake::TestTask.new(recent: "test:prepare") do |t| since = TEST_CHANGES_SINCE touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } + diff --git a/railties/test/app_rails_loader_test.rb b/railties/test/app_rails_loader_test.rb index 87e0ad7bd7..63ed9eaef0 100644 --- a/railties/test/app_rails_loader_test.rb +++ b/railties/test/app_rails_loader_test.rb @@ -2,40 +2,47 @@ require 'abstract_unit' require 'rails/app_rails_loader' class AppRailsLoaderTest < ActiveSupport::TestCase - test "is in a rails application if bin/rails exists and contains APP_PATH" do - File.stubs(:exists?).returns(true) - File.stubs(:read).with('bin/rails').returns('APP_PATH') - assert Rails::AppRailsLoader.in_rails_application_or_engine? - end - test "is not in a rails application if bin/rails exists but doesn't contain APP_PATH" do - File.stubs(:exists?).returns(true) - File.stubs(:read).with('bin/rails').returns('railties bin/rails') - assert !Rails::AppRailsLoader.in_rails_application_or_engine? + setup do + File.stubs(:exists?).returns(false) end - test "is in a rails application if parent directory has bin/rails containing APP_PATH" do - File.stubs(:exists?).with("/foo/bar/bin/rails").returns(false) - File.stubs(:exists?).with("/foo/bin/rails").returns(true) - File.stubs(:read).with('/foo/bin/rails').returns('APP_PATH') - assert Rails::AppRailsLoader.in_rails_application_or_engine_subdirectory?(Pathname.new("/foo/bar")) - end + ['bin/rails', 'script/rails'].each do |exe| + test "is in a rails application if #{exe} exists and contains APP_PATH" do + File.stubs(:exists?).with(exe).returns(true) + File.stubs(:read).with(exe).returns('APP_PATH') + assert Rails::AppRailsLoader.find_executable + end - test "is not in a rails application if at the root directory and doesn't have bin/rails" do - Pathname.any_instance.stubs(:root?).returns true - assert !Rails::AppRailsLoader.in_rails_application_or_engine? - end + test "is not in a rails application if #{exe} exists but doesn't contain APP_PATH" do + File.stubs(:exists?).with(exe).returns(true) + File.stubs(:read).with(exe).returns("railties #{exe}") + assert !Rails::AppRailsLoader.find_executable + end - test "is in a rails engine if parent directory has bin/rails containing ENGINE_PATH" do - File.stubs(:exists?).with("/foo/bar/bin/rails").returns(false) - File.stubs(:exists?).with("/foo/bin/rails").returns(true) - File.stubs(:read).with('/foo/bin/rails').returns('ENGINE_PATH') - assert Rails::AppRailsLoader.in_rails_application_or_engine_subdirectory?(Pathname.new("/foo/bar")) - end + test "is in a rails application if parent directory has #{exe} containing APP_PATH" do + File.stubs(:exists?).with("/foo/bar/#{exe}").returns(false) + File.stubs(:exists?).with("/foo/#{exe}").returns(true) + File.stubs(:read).with("/foo/#{exe}").returns('APP_PATH') + assert Rails::AppRailsLoader.find_executable_in_parent_path(Pathname.new("/foo/bar")) + end + + test "is not in a rails application if at the root directory and doesn't have #{exe}" do + Pathname.any_instance.stubs(:root?).returns true + assert !Rails::AppRailsLoader.find_executable + end + + test "is in a rails engine if parent directory has #{exe} containing ENGINE_PATH" do + File.stubs(:exists?).with("/foo/bar/#{exe}").returns(false) + File.stubs(:exists?).with("/foo/#{exe}").returns(true) + File.stubs(:read).with("/foo/#{exe}").returns('ENGINE_PATH') + assert Rails::AppRailsLoader.find_executable_in_parent_path(Pathname.new("/foo/bar")) + end - test "is in a rails engine if bin/rails exists containing ENGINE_PATH" do - File.stubs(:exists?).returns(true) - File.stubs(:read).with('bin/rails').returns('ENGINE_PATH') - assert Rails::AppRailsLoader.in_rails_application_or_engine? + test "is in a rails engine if #{exe} exists containing ENGINE_PATH" do + File.stubs(:exists?).with(exe).returns(true) + File.stubs(:read).with(exe).returns('ENGINE_PATH') + assert Rails::AppRailsLoader.find_executable + end end end diff --git a/railties/test/application/initializers/boot_test.rb b/railties/test/application/initializers/boot_test.rb deleted file mode 100644 index 7eda15894e..0000000000 --- a/railties/test/application/initializers/boot_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -require "isolation/abstract_unit" - -module ApplicationTests - class BootTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - def setup - # build_app - # boot_rails - end - - def teardown - # teardown_app - end - - test "booting rails sets the load paths correctly" do - # This test is pending reworking the boot process - end - end -end diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb index 595f58bd91..31811e7f92 100644 --- a/railties/test/application/initializers/load_path_test.rb +++ b/railties/test/application/initializers/load_path_test.rb @@ -64,7 +64,7 @@ module ApplicationTests add_to_config <<-RUBY config.root = "#{app_path}" - config.autoload_paths << "#{app_path}/lib" + config.eager_load_paths << "#{app_path}/lib" RUBY require "#{app_path}/config/environment" diff --git a/railties/test/application/middleware/best_practices_test.rb b/railties/test/application/middleware/best_practices_test.rb deleted file mode 100644 index f6783c6ca2..0000000000 --- a/railties/test/application/middleware/best_practices_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'isolation/abstract_unit' - -module ApplicationTests - class BestPracticesTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - def setup - build_app - boot_rails - require 'rack/test' - extend Rack::Test::Methods - simple_controller - end - - def teardown - teardown_app - end - - test "simple controller in production mode returns best standards" do - get '/foo' - assert_equal "IE=Edge,chrome=1", last_response.headers["X-UA-Compatible"] - end - - test "simple controller in development mode leaves out Chrome" do - app("development") - get "/foo" - assert_equal "IE=Edge", last_response.headers["X-UA-Compatible"] - end - end -end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index c5a0d02fe1..73ef2046c0 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -45,8 +45,7 @@ module ApplicationTests "ActionDispatch::ParamsParser", "Rack::Head", "Rack::ConditionalGet", - "Rack::ETag", - "ActionDispatch::BestStandardsSupport" + "Rack::ETag" ], middleware end diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index f6a77a0d17..4029984ce9 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -54,11 +54,11 @@ module ApplicationTests assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first end - test "booting up Rails yields a list of paths that will be eager loaded" do - autoload_paths = @paths.autoload_paths - assert autoload_paths.include?(root("app/controllers")) - assert autoload_paths.include?(root("app/helpers")) - assert autoload_paths.include?(root("app/models")) + test "booting up Rails yields a list of paths that are eager" do + eager_load = @paths.eager_load + assert eager_load.include?(root("app/controllers")) + assert eager_load.include?(root("app/helpers")) + assert eager_load.include?(root("app/models")) end test "environments has a glob equal to the current environment" do diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index ccb47663d4..820b838702 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -21,6 +21,8 @@ module ApplicationTests def set_database_url ENV['DATABASE_URL'] = "sqlite3://:@localhost/#{database_url_db_name}" + # ensure it's using the DATABASE_URL + FileUtils.rm_rf("#{app_path}/config/database.yml") end def expected @@ -166,9 +168,16 @@ module ApplicationTests end test 'db:test:load_structure with database_url' do - require "#{app_path}/config/environment" - set_database_url - db_test_load_structure + old_rails_env = ENV["RAILS_ENV"] + ENV["RAILS_ENV"] = 'test' + + begin + require "#{app_path}/config/environment" + set_database_url + db_test_load_structure + ensure + ENV["RAILS_ENV"] = old_rails_env + end end end end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index a8275a2e76..09f2ad1209 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -1,5 +1,6 @@ # coding:utf-8 require "isolation/abstract_unit" +require "active_support/core_ext/string/strip" module ApplicationTests class RakeTest < ActiveSupport::TestCase @@ -140,7 +141,24 @@ module ApplicationTests get '/cart', to: 'cart#show' end RUBY - assert_equal "cart GET /cart(.:format) cart#show\n", Dir.chdir(app_path){ `rake routes` } + + output = Dir.chdir(app_path){ `rake routes` } + assert_equal "Prefix Verb URI Pattern Controller#Action\ncart GET /cart(.:format) cart#show\n", output + end + + def test_rake_routes_displays_message_when_no_routes_are_defined + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + end + RUBY + + assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path){ `rake routes` } + You don't have any routes defined! + + Please add some routes in config/routes.rb. + + For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html. + MESSAGE end def test_logger_is_flushed_when_exiting_production_rake_tasks diff --git a/railties/test/fixtures/lib/app_builders/empty_builder.rb b/railties/test/fixtures/lib/app_builders/empty_builder.rb deleted file mode 100644 index babd9c2461..0000000000 --- a/railties/test/fixtures/lib/app_builders/empty_builder.rb +++ /dev/null @@ -1,2 +0,0 @@ -class AppBuilder -end
\ No newline at end of file diff --git a/railties/test/fixtures/lib/app_builders/simple_builder.rb b/railties/test/fixtures/lib/app_builders/simple_builder.rb deleted file mode 100644 index 993d3a2aa2..0000000000 --- a/railties/test/fixtures/lib/app_builders/simple_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AppBuilder - def gitignore - create_file ".gitignore", <<-R.strip -foobar - R - end -end diff --git a/railties/test/fixtures/lib/app_builders/tweak_builder.rb b/railties/test/fixtures/lib/app_builders/tweak_builder.rb deleted file mode 100644 index cb50be01cb..0000000000 --- a/railties/test/fixtures/lib/app_builders/tweak_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AppBuilder < Rails::AppBuilder - def gitignore - create_file ".gitignore", <<-R.strip -foobar - R - end -end diff --git a/railties/test/fixtures/lib/plugin_builders/empty_builder.rb b/railties/test/fixtures/lib/plugin_builders/empty_builder.rb deleted file mode 100644 index 5c5607621c..0000000000 --- a/railties/test/fixtures/lib/plugin_builders/empty_builder.rb +++ /dev/null @@ -1,2 +0,0 @@ -class PluginBuilder -end diff --git a/railties/test/fixtures/lib/plugin_builders/simple_builder.rb b/railties/test/fixtures/lib/plugin_builders/simple_builder.rb deleted file mode 100644 index 08f6c5535d..0000000000 --- a/railties/test/fixtures/lib/plugin_builders/simple_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class PluginBuilder - def gitignore - create_file ".gitignore", <<-R.strip -foobar - R - end -end diff --git a/railties/test/fixtures/lib/plugin_builders/spec_builder.rb b/railties/test/fixtures/lib/plugin_builders/spec_builder.rb deleted file mode 100644 index 2721429599..0000000000 --- a/railties/test/fixtures/lib/plugin_builders/spec_builder.rb +++ /dev/null @@ -1,19 +0,0 @@ -class PluginBuilder < Rails::PluginBuilder - def test - create_file "spec/spec_helper.rb" - append_file "Rakefile", <<-EOF -# spec tasks in rakefile - -task default: :spec - EOF - end - - def generate_test_dummy - dummy_path("spec/dummy") - super - end - - def skip_test_unit? - true - end -end diff --git a/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb b/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb deleted file mode 100644 index 1e801409a4..0000000000 --- a/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class PluginBuilder < Rails::PluginBuilder - def gitignore - create_file ".gitignore", <<-R.strip -foobar - R - end -end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index b2deeb011d..0697035871 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -70,7 +70,7 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_application_new_exits_with_non_zero_code_on_invalid_application_name - quietly { system 'rails new test' } + quietly { system 'rails new test --no-rc' } assert_equal false, $?.success? end @@ -342,15 +342,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generated_environments_file_for_auto_explain - run_generator [destination_root, "--skip-active-record"] - %w(development production).each do |env| - assert_file "config/environments/#{env}.rb" do |file| - assert_no_match %r(auto_explain_threshold_in_seconds), file - end - end - end - def test_pretend_option output = run_generator [File.join(destination_root, "myapp"), "--pretend"] assert_no_match(/run bundle install/, output) @@ -366,28 +357,3 @@ protected assert_file "Gemfile", /^gem\s+["']#{gem}["']$/ end end - -class CustomAppGeneratorTest < Rails::Generators::TestCase - include GeneratorsTestHelper - tests Rails::Generators::AppGenerator - - arguments [destination_root] - include SharedCustomGeneratorTests - -protected - def default_files - ::DEFAULT_APP_FILES - end - - def builders_dir - "app_builders" - end - - def builder_class - :AppBuilder - end - - def action(*args, &block) - silence(:stdout) { generator.send(*args, &block) } - end -end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 4bf5a2f08b..904c1db57e 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -371,38 +371,3 @@ protected ::DEFAULT_PLUGIN_FILES end end - -class CustomPluginGeneratorTest < Rails::Generators::TestCase - include GeneratorsTestHelper - tests Rails::Generators::PluginNewGenerator - - destination File.join(Rails.root, "tmp/bukkits") - arguments [destination_root] - include SharedCustomGeneratorTests - - def test_overriding_test_framework - FileUtils.cd(destination_root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/plugin_builders/spec_builder.rb"]) - assert_file 'spec/spec_helper.rb' - assert_file 'spec/dummy' - assert_file 'Rakefile', /task default: :spec/ - assert_file 'Rakefile', /# spec tasks in rakefile/ - end - -protected - def default_files - ::DEFAULT_PLUGIN_FILES - end - - def builder_class - :PluginBuilder - end - - def builders_dir - "plugin_builders" - end - - def action(*args, &block) - silence(:stdout){ generator.send(*args, &block) } - end -end diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index c34ce285e3..013cb78252 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -39,6 +39,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_instance_method :destroy, content do |m| assert_match(/@user\.destroy/, m) + assert_match(/User was successfully destroyed/, m) end assert_instance_method :set_user, content do |m| diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index d203afed5c..2724882a23 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -83,7 +83,7 @@ module SharedGeneratorTests end def test_template_is_executed_when_supplied - path = "http://gist.github.com/103208.txt" + path = "https://gist.github.com/josevalim/103208/raw/" template = %{ say "It works!" } template.instance_eval "def read; self; end" # Make the string respond to read @@ -92,7 +92,7 @@ module SharedGeneratorTests end def test_template_is_executed_when_supplied_an_https_path - path = "https://gist.github.com/103208.txt" + path = "https://gist.github.com/josevalim/103208/raw/" template = %{ say "It works!" } template.instance_eval "def read; self; end" # Make the string respond to read @@ -140,61 +140,3 @@ module SharedGeneratorTests assert_no_file('app/mailers/.keep') end end - -module SharedCustomGeneratorTests - def setup - Rails.application = TestApp::Application - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - end - - def teardown - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - Object.class_eval do - remove_const :AppBuilder if const_defined?(:AppBuilder) - remove_const :PluginBuilder if const_defined?(:PluginBuilder) - end - Rails.application = TestApp::Application.instance - end - - def test_builder_option_with_empty_app_builder - FileUtils.cd(destination_root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/empty_builder.rb"]) - default_files.each{ |path| assert_no_file path } - end - - def test_builder_option_with_simple_plugin_builder - FileUtils.cd(destination_root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/simple_builder.rb"]) - (default_files - ['.gitignore']).each{ |path| assert_no_file path } - assert_file ".gitignore", "foobar" - end - - def test_builder_option_with_relative_path - here = File.expand_path(File.dirname(__FILE__)) - FileUtils.cd(here) - run_generator([destination_root, "-b", "../fixtures/lib/#{builders_dir}/simple_builder.rb"]) - FileUtils.cd(destination_root) - (default_files - ['.gitignore']).each{ |path| assert_no_file path } - assert_file ".gitignore", "foobar" - end - - def test_builder_option_with_tweak_plugin_builder - FileUtils.cd(destination_root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/tweak_builder.rb"]) - default_files.each{ |path| assert_file path } - assert_file ".gitignore", "foobar" - end - - def test_builder_option_with_http - url = "http://gist.github.com/103208.txt" - template = "class #{builder_class}; end" - template.instance_eval "def read; self; end" # Make the string respond to read - - generator([destination_root], builder: url).expects(:open).with(url, 'Accept' => 'application/x-thor-template').returns(template) - quietly { generator.invoke_all } - - default_files.each{ |path| assert_no_file(path) } - end -end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 7ae1b5ccfd..68d96bae94 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -279,7 +279,7 @@ Module.new do environment = File.expand_path('../../../../load_paths', __FILE__) require_environment = "-r #{environment}" - `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{app_template_path} --skip-gemfile` + `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{app_template_path} --skip-gemfile --no-rc` File.open("#{app_template_path}/config/boot.rb", 'w') do |f| f.puts "require '#{environment}'" f.puts "require 'rails/all'" diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index 6f860469fd..12f18b9dbf 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -135,48 +135,56 @@ class PathsTest < ActiveSupport::TestCase assert_equal 2, @root.autoload_once.size end - test "marking a path as eager loaded is deprecated" do + test "it is possible to mark a path as eager loaded" do @root["app"] = "/app" - assert_deprecated{ @root["app"].eager_load! } - assert_deprecated{ assert @root["app"].eager_load? } - assert_deprecated{ assert @root.eager_load.include?(@root["app"].to_a.first) } + @root["app"].eager_load! + assert @root["app"].eager_load? + assert @root.eager_load.include?(@root["app"].to_a.first) end - test "skipping a path from eager loading is deprecated" do + test "it is possible to skip a path from eager loading" do @root["app"] = "/app" - assert_deprecated{ @root["app"].eager_load! } - assert_deprecated{ assert @root["app"].eager_load? } + @root["app"].eager_load! + assert @root["app"].eager_load? - assert_deprecated{ @root["app"].skip_eager_load! } - assert_deprecated{ assert !@root["app"].eager_load? } - assert_deprecated{ assert !@root.eager_load.include?(@root["app"].to_a.first) } + @root["app"].skip_eager_load! + assert !@root["app"].eager_load? + assert !@root.eager_load.include?(@root["app"].to_a.first) end - test "adding a path with eager_load option is deprecated" do - assert_deprecated{ @root.add "app", with: "/app", eager_load: true } - assert_deprecated{ assert @root["app"].eager_load? } - assert_deprecated{ assert @root.eager_load.include?("/app") } + test "it is possible to add a path without assignment and mark it as eager" do + @root.add "app", with: "/app", eager_load: true + assert @root["app"].eager_load? + assert @root.eager_load.include?("/app") end - test "adding multiple paths with eager_load option is deprecated" do - assert_deprecated{ @root.add "app", with: ["/app", "/app2"], eager_load: true } - assert_deprecated{ assert @root["app"].eager_load? } - assert_deprecated{ assert @root.eager_load.include?("/app") } - assert_deprecated{ assert @root.eager_load.include?("/app2") } + test "it is possible to add multiple paths without assignment and mark them as eager" do + @root.add "app", with: ["/app", "/app2"], eager_load: true + assert @root["app"].eager_load? + assert @root.eager_load.include?("/app") + assert @root.eager_load.include?("/app2") + end + + test "it is possible to create a path without assignment and mark it both as eager and load once" do + @root.add "app", with: "/app", eager_load: true, autoload_once: true + assert @root["app"].eager_load? + assert @root["app"].autoload_once? + assert @root.eager_load.include?("/app") + assert @root.autoload_once.include?("/app") end test "making a path eager more than once only includes it once in @root.eager_paths" do @root["app"] = "/app" - assert_deprecated{ @root["app"].eager_load! } - assert_deprecated{ @root["app"].eager_load! } - assert_deprecated{ assert_equal 1, @root.eager_load.select {|p| p == @root["app"].expanded.first }.size } + @root["app"].eager_load! + @root["app"].eager_load! + assert_equal 1, @root.eager_load.select {|p| p == @root["app"].expanded.first }.size end test "paths added to a eager_load path should be added to the eager_load collection" do @root["app"] = "/app" - assert_deprecated{ @root["app"].eager_load! } + @root["app"].eager_load! @root["app"] << "/app2" - assert_deprecated{ assert_equal 2, @root.eager_load.size } + assert_equal 2, @root.eager_load.size end test "it should be possible to add a path's default glob" do |