diff options
429 files changed, 4879 insertions, 4535 deletions
diff --git a/.travis.yml b/.travis.yml index 18b0100c62..ba5526c009 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,3 @@ notifications: rooms: - secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI=" bundler_args: --path vendor/bundle -matrix: - allow_failures: - - rvm: 2.0.0 @@ -8,11 +8,12 @@ gem 'mocha', '~> 0.13.0', require: false gem 'rack-test', github: 'brynary/rack-test' gem 'rack-cache', '~> 1.2' gem 'bcrypt-ruby', '~> 3.0.0' -gem 'jquery-rails', '~> 2.1.4', github: 'rails/jquery-rails' +gem 'jquery-rails', '~> 2.2.0', github: 'rails/jquery-rails' gem 'turbolinks' gem 'coffee-rails', github: 'rails/coffee-rails' -gem 'thread_safe', '~> 0.1' +# TODO: Release thor +gem 'thor', github: 'wycats/thor', branch: 'master' gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders', branch: 'master' @@ -40,10 +40,10 @@ task :install => :gem do version = File.read("RAILS_VERSION").strip (PROJECTS - ["railties"]).each do |project| puts "INSTALLING #{project}" - system("gem install #{project}/pkg/#{project}-#{version}.gem --no-ri --no-rdoc") + system("gem install #{project}/pkg/#{project}-#{version}.gem --local --no-ri --no-rdoc") end - system("gem install railties/pkg/railties-#{version}.gem --no-ri --no-rdoc") - system("gem install pkg/rails-#{version}.gem --no-ri --no-rdoc") + system("gem install railties/pkg/railties-#{version}.gem --local --no-ri --no-rdoc") + system("gem install pkg/rails-#{version}.gem --local --no-ri --no-rdoc") end desc "Generate documentation for the Rails framework" diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 9feca324a3..43bfa536c1 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,5 +1,10 @@ ## Rails 4.0.0 (unreleased) ## +* Eager loading made to use relation's in_clause_length instead of host's one. + Fix #8474 + + *Boris Staal* + * Explicit multipart messages no longer set the order of the MIME parts. *Nate Berkopec* diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc index 59c33b7940..9feb2add5b 100644 --- a/actionmailer/README.rdoc +++ b/actionmailer/README.rdoc @@ -98,7 +98,7 @@ Example: class Mailman < ActionMailer::Base def receive(email) - page = Page.find_by_address(email.to.first) + page = Page.find_by(address: email.to.first) page.emails.create( subject: email.subject, body: email.body ) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index a3c0b2c6bd..9ba606c045 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -530,7 +530,7 @@ module ActionMailer # The resulting Mail::Message will have the following in its header: # # X-Special-Domain-Specific-Header: SecretValue - def headers(args=nil) + def headers(args = nil) if args @_message.headers(args) else @@ -660,7 +660,7 @@ module ActionMailer # format.html # end # - def mail(headers={}, &block) + def mail(headers = {}, &block) @_mail_was_called = true m = @_message diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb index cbb2778fae..e8883a8235 100644 --- a/actionmailer/lib/action_mailer/collector.rb +++ b/actionmailer/lib/action_mailer/collector.rb @@ -20,7 +20,7 @@ module ActionMailer end alias :all :any - def custom(mime, options={}) + def custom(mime, options = {}) options.reverse_merge!(content_type: mime.to_s) @context.formats = [mime.to_sym] options[:body] = block_given? ? yield : @default_render.call diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb index 3ab37d8142..6452bf616c 100644 --- a/actionmailer/lib/action_mailer/test_helper.rb +++ b/actionmailer/lib/action_mailer/test_helper.rb @@ -4,9 +4,9 @@ module ActionMailer # # def test_emails # assert_emails 0 - # ContactMailer.deliver_contact + # ContactMailer.welcome.deliver # assert_emails 1 - # ContactMailer.deliver_contact + # ContactMailer.welcome.deliver # assert_emails 2 # end # @@ -15,12 +15,12 @@ module ActionMailer # # def test_emails_again # assert_emails 1 do - # ContactMailer.deliver_contact + # ContactMailer.welcome.deliver # end # # assert_emails 2 do - # ContactMailer.deliver_contact - # ContactMailer.deliver_contact + # ContactMailer.welcome.deliver + # ContactMailer.welcome.deliver # end # end def assert_emails(number) @@ -38,7 +38,7 @@ module ActionMailer # # def test_emails # assert_no_emails - # ContactMailer.deliver_contact + # ContactMailer.welcome.deliver # assert_emails 1 # end # diff --git a/actionmailer/lib/rails/generators/mailer/USAGE b/actionmailer/lib/rails/generators/mailer/USAGE index 7470289fd3..323bb8a87f 100644 --- a/actionmailer/lib/rails/generators/mailer/USAGE +++ b/actionmailer/lib/rails/generators/mailer/USAGE @@ -10,9 +10,8 @@ Example: ======== rails generate mailer Notifications signup forgot_password invoice - creates a Notifications mailer class, views, test, and fixtures: + creates a Notifications mailer class, views, and test: Mailer: app/mailers/notifications.rb - Views: app/views/notifications/signup.erb [...] + Views: app/views/notifications/signup.text.erb [...] Test: test/mailers/notifications_test.rb - Fixtures: test/fixtures/notifications/signup [...] diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index d2380c0881..7309d42f17 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,133 @@ ## Rails 4.0.0 (unreleased) ## +* Add javascript based routing path matcher to `/rails/info/routes`. + Routes can now be filtered by whether or not they match a path. + + *Richard Schneeman* + +* Given + + params.permit(:name) + + `:name` passes if it is a key of `params` whose value is a permitted scalar. + + Similarly, given + + params.permit(tags: []) + + `:tags` passes if it is a key of `params` whose value is an array of + permitted scalars. + + Permitted scalars filtering happens at any level of nesting. + + *Xavier Noria* + +* `BestStandardsSupport` no longer duplicates `X-UA-Compatible` values on + each request to prevent header size from blowing up. + + *Edward Anderson* + +* 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: + + 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 + behavior so it's not required: + + resources :projects, defaults: { bucket_type: 'projects' } + + This changes existing behavior slightly in that any routes which only differ + in their defaults will match the first route rather than the closest match. + + *Andrew White* + +* Add support for routing constraints other than Regexp and String. + For example this now allows the use of arrays like this: + + get '/foo/:action', to: 'foo', constraints: { subdomain: %w[www admin] } + + or constraints where the request method returns an Fixnum like this: + + get '/foo', to: 'foo#index', constraints: { port: 8080 } + + Note that this only applies to constraints on the request - path constraints + still need to be specified as Regexps as the various constraints are compiled + into a single Regexp. + + *Andrew White* + +* Fix a bug in integration tests where setting the port via a url passed to + the process method was ignored when constructing the request environment. + + *Andrew White* + +* Allow `:selected` to be set on `date_select` tag helper. + + *Colin Burn-Murdoch* + +* Fixed json params parsing regression for non-object JSON content. + + *Dylan Smith* + +* Extract `ActionDispatch::PerformanceTest` into https://github.com/rails/rails-perftest + You can add the gem to your Gemfile to keep using performance tests. + + gem 'rails-perftest' + + *Yves Senn* + +* Added view_cache_dependency API for declaring dependencies that affect + cache digest computation. + + *Jamis Buck* + +* `image_submit_tag` will set `alt` attribute from image source if not + specified. + + *Nihad Abbasov* + +* Do not generate local variables for partials without object or collection. + Previously rendering a partial without giving `:object` or `:collection` + would generate a local variable with the partial name by default. + + *Carlos Antonio da Silva* + +* Return the last valid, non-private IP address from the X-Forwarded-For, + Client-IP and Remote-Addr headers, in that order. Document the rationale + for that decision, and describe the options that can be passed to the + RemoteIp middleware to change it. + Fix #7979 + + *André Arko*, *Steve Klabnik*, *Alexey Gaziev* + +* Do not append second slash to `root_url` when using `trailing_slash: true` + Fix #8700 + + Example: + # before + root_url # => http://test.host// + + # after + root_url # => http://test.host/ + + *Yves Senn* + +* Allow to toggle dumps on error pages. + + *Gosha Arinich* + +* Fix a bug in `content_tag_for` that prevents it from working without a block. + + *Jasl* + +* Change the stylesheet of exception pages for development mode. + Additionally display also the line of code and fragment that raised + the exception in all exceptions pages. + + *Guillermo Iguaran + Jorge Cuadrado* + * Do not append `charset=` parameter when `head` is called with a `:content_type` option. Fix #8661. @@ -111,7 +239,7 @@ *Drew Ulmer* -* No sort Hash options in `grouped_options_for_select`. *Sergey Kojin* +* Do not sort Hash options in `grouped_options_for_select`. *Sergey Kojin* * Accept symbols as `send_data :disposition` value *Elia Schito* @@ -159,14 +287,15 @@ * Render every partial with a new `ActionView::PartialRenderer`. This resolves issues when rendering nested partials. - Fix #8197 + Fix #8197. *Yves Senn* * Introduce `ActionView::Template::Handlers::ERB.escape_whitelist`. This is a list of mime types where template text is not html escaped by default. It prevents `Jack & Joe` from rendering as `Jack & Joe` for the whitelisted mime types. The default whitelist - contains text/plain. Fix #7976 + contains `text/plain`. + Fix #7976. *Joost Baaij* @@ -182,12 +311,13 @@ 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\" /> - Fix #8108 + 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 + returned value if any. + Fix #8086. *Nikita Afanasenko* @@ -196,21 +326,21 @@ *Pavel Nikitin* -* Only non-js/css under app/assets path will be included in default config.assets.precompile. +* Only non-js/css under `app/assets` path will be included in default `config.assets.precompile`. *Josh Peek* -* Remove support for the RAILS_ASSET_ID environment configuration +* Remove support for the `RAILS_ASSET_ID` environment configuration (no longer needed now that we have the asset pipeline). *Josh Peek* -* Remove old asset_path configuration (no longer needed now that we have the asset pipeline). +* Remove old `asset_path` configuration (no longer needed now that we have the asset pipeline). *Josh Peek* * `assert_template` can be used to assert on the same template with different locals - Fix #3675 + Fix #3675. *Yves Senn* @@ -218,10 +348,10 @@ *Josh Peek* -* Accept :remote as symbolic option for `link_to` helper. *Riley Lynch* +* Accept `:remote` as symbolic option for `link_to` helper. *Riley Lynch* * Warn when the `:locals` option is passed to `assert_template` outside of a view test case - Fix #3415 + Fix #3415. *Yves Senn* @@ -241,24 +371,24 @@ *Francesco Rodriguez* -* Failsafe exception returns text/plain. *Steve Klabnik* +* 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 +* Rename internal variables on `ActionController::TemplateAssertions` to prevent + naming collisions. `@partials`, `@templates` and `@layouts` are now prefixed with an underscore. + Fix #7459. *Yves Senn* -* `resource` and `resources` don't modify the passed options hash - Fix #7777 +* `resource` and `resources` don't modify the passed options hash. + Fix #7777. *Yves Senn* -* Precompiled assets include aliases from foo.js to foo/index.js and vice versa. +* Precompiled assets include aliases from `foo.js` to `foo/index.js` and vice versa. # Precompiles phone-<digest>.css and aliases phone/index.css to phone.css. config.assets.precompile = [ 'phone.css' ] @@ -296,8 +426,8 @@ *Nihad Abbasov* -* Deprecate Mime::Type#verify_request? and Mime::Type.browser_generated_types, - since they are no longer used inside of Rails, they will be removed in Rails 4.1 +* Deprecate `Mime::Type#verify_request?` and `Mime::Type.browser_generated_types`, + since they are no longer used inside of Rails, they will be removed in Rails 4.1. *Michael Grosser* @@ -313,11 +443,12 @@ * Remove Integration between `attr_accessible`/`attr_protected` and `ActionController::ParamsWrapper`. ParamWrapper now wraps all the parameters returned - by the class method attribute_names + by the class method `attribute_names`. *Guillermo Iguaran* -* Fix #7646, the log now displays the correct status code when an exception is raised. +* Log now displays the correct status code when an exception is raised. + Fix #7646. *Yves Senn* @@ -355,7 +486,7 @@ *Sergey Nartimov* -* Add .ruby template handler, this handler simply allows arbitrary Ruby code as a template. *Guillermo Iguaran* +* Add `.ruby` template handler, this handler simply allows arbitrary Ruby code as a template. *Guillermo Iguaran* * Add `separator` option for `ActionView::Helpers::TextHelper#excerpt`: @@ -376,17 +507,20 @@ end end -* Add automatic template digests to all `CacheHelper#cache` calls (originally spiked in the cache_digests plugin) *DHH* +* Add automatic template digests to all `CacheHelper#cache` calls (originally spiked in the `cache_digests` plugin) *DHH* * When building a URL fails, add missing keys provided by Journey. Failed URL generation now returns a 500 status instead of a 404. *Richard Schneeman* -* Deprecate availbility of `ActionView::RecordIdentifier` in controllers by default. - It's view specific and can be easily included in controller manually if someone - really needs it. RecordIdentifier will be removed from `ActionController::Base` - in Rails 4.1. *Piotr Sarnacki* +* Deprecate availability of `ActionView::RecordIdentifier` in controllers by default. + It's view specific and can be easily included in controllers manually if someone + really needs it. Also deprecate calling `ActionController::RecordIdentifier.dom_id` and + `dom_class` directly, in favor of `ActionView::RecordIdentifier.dom_id` and `dom_class`. + `RecordIdentifier` will be removed from `ActionController::Base` in Rails 4.1. + + *Piotr Sarnacki* * Fix `ActionView::RecordIdentifier` to work as a singleton. *Piotr Sarnacki* @@ -470,7 +604,7 @@ *Egor Homakov* -* Allow data attributes to be set as a first-level option for form_for, so you can write `form_for @record, data: { behavior: 'autosave' }` instead of `form_for @record, html: { data: { behavior: 'autosave' } }` *DHH* +* Allow data attributes to be set as a first-level option for `form_for`, so you can write `form_for @record, data: { behavior: 'autosave' }` instead of `form_for @record, html: { data: { behavior: 'autosave' } }` *DHH* * Deprecate `button_to_function` and `link_to_function` helpers. @@ -526,7 +660,9 @@ *Carlos Galdino + Rafael Mendonça França* -* Show routes in exception page while debugging a `RoutingError` in development. *Richard Schneeman and Mattt Thompson* +* Show routes in exception page while debugging a `RoutingError` in development. + + *Richard Schneeman + Mattt Thompson + Yves Senn* * Add `ActionController::Flash.add_flash_types` method to allow people to register their own flash types. e.g.: @@ -576,7 +712,7 @@ *Sergey Nartimov* -* change a way of ordering helpers from several directories. Previously, +* Change a way of ordering helpers from several directories. Previously, when loading helpers from multiple paths, all of the helpers files were gathered into one array an then they were sorted. Helpers from different directories should not be mixed before loading them to make loading more @@ -612,7 +748,7 @@ * Add `time_field` and `time_field_tag` helpers which render an `input[type="time"]` tag. *Alex Soulim* -* Removed old text_helper apis for highlight, excerpt and word_wrap *Jeremy Walker* +* 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* @@ -623,7 +759,7 @@ * Remove `:mouseover` option from `image_tag` helper. *Rafael Mendonça França* -* The `select` method (select tag) forces :include_blank if `required` is true and +* The `select` method (select tag) forces `:include_blank` if `required` is true and `display size` is one and `multiple` is not true. *Angelo Capilleri* * Copy literal route constraints to defaults so that url generation know about them. @@ -645,7 +781,7 @@ * Make current object and counter (when it applies) variables accessible when rendering templates with :object / :collection. *Carlos Antonio da Silva* -* JSONP now uses mimetype text/javascript instead of application/json. *omjokine* +* JSONP now uses mimetype `text/javascript` instead of `application/json`. *omjokine* * Allow to lazy load `default_form_builder` by passing a `String` instead of a constant. *Piotr Sarnacki* @@ -658,14 +794,14 @@ * Add `index` method to FormBuilder class. *Jorge Bejar* -* Remove the leading \n added by textarea on assert_select. *Santiago Pastorino* +* 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, so if you need such behavior, you can either set it to `true` or explicitly pass - `authenticity_token: true` in form options + `authenticity_token: true` in form options. -* Added ActionDispatch::SSL middleware that when included force all the requests to be under HTTPS protocol. *Rafael Mendonça França* +* Added `ActionDispatch::SSL` middleware that when included force all the requests to be under HTTPS protocol. *Rafael Mendonça França* * Add `include_hidden` option to select tag. With `include_hidden: false` select with `multiple` attribute doesn't generate hidden input with blank value. *Vasiliy Ermolovich* diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc index ccd0193515..29a7fcf0f0 100644 --- a/actionpack/README.rdoc +++ b/actionpack/README.rdoc @@ -10,7 +10,7 @@ It consists of several modules: * Action Dispatch, which parses information about the web request, handles routing as defined by the user, and does advanced processing related to HTTP - such as MIME-type negotiation, decoding parameters in POST/PUT bodies, + such as MIME-type negotiation, decoding parameters in POST, PATCH, or PUT bodies, handling HTTP caching logic, cookies and sessions. * Action Controller, which provides a base controller class that can be diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index c65870cac6..f00fc46b2e 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'builder', '~> 3.1.0' - s.add_dependency 'rack', '~> 1.4.1' + s.add_dependency 'rack', '~> 1.4.3' s.add_dependency 'rack-test', '~> 0.6.1' s.add_dependency 'erubis', '~> 2.7.0' diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 73799e8085..91864f2a35 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -209,7 +209,7 @@ module AbstractController _write_layout_method end - delegate :_layout_conditions, :to => "self.class" + delegate :_layout_conditions, to: :class module ClassMethods def inherited(klass) # :nodoc: diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 1a13d7af29..9cacb3862b 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -42,7 +42,6 @@ module ActionController autoload :Integration, 'action_controller/deprecated/integration_test' autoload :IntegrationTest, 'action_controller/deprecated/integration_test' - autoload :PerformanceTest, 'action_controller/deprecated/performance_test' autoload :Routing, 'action_controller/deprecated' autoload :TestCase, 'action_controller/test_case' autoload :TemplateAssertions, 'action_controller/test_case' diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 2892e093af..cf2cda039d 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -70,12 +70,26 @@ module ActionController config_accessor :perform_caching self.perform_caching = true if perform_caching.nil? + + class_attribute :_view_cache_dependencies + self._view_cache_dependencies = [] + helper_method :view_cache_dependencies if respond_to?(:helper_method) + end + + module ClassMethods + def view_cache_dependency(&dependency) + self._view_cache_dependencies += [dependency] + 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 + protected # Convenience accessor. def cache(key, options = {}, &block) diff --git a/actionpack/lib/action_controller/deprecated/performance_test.rb b/actionpack/lib/action_controller/deprecated/performance_test.rb deleted file mode 100644 index c7ba5a2fe7..0000000000 --- a/actionpack/lib/action_controller/deprecated/performance_test.rb +++ /dev/null @@ -1,3 +0,0 @@ -ActionController::PerformanceTest = ActionDispatch::PerformanceTest - -ActiveSupport::Deprecation.warn 'ActionController::PerformanceTest is deprecated and will be removed, use ActionDispatch::PerformanceTest instead.' diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 3f9b382a11..eddee08545 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/class/attribute' - module ActionController module ConditionalGet extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 896238b7dc..e295002b16 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -228,7 +228,7 @@ module ActionController end def decode_credentials(header) - HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair| + ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair| key, value = pair.split('=', 2) [key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')] end] diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 6bf306ac5b..d04fbae150 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -82,7 +82,7 @@ module ActionController #:nodoc: # (by name) if it does not already exist, without web-services, it might look like this: # # def create - # @company = Company.find_or_create_by_name(params[:company][:name]) + # @company = Company.find_or_create_by(name: params[:company][:name]) # @person = @company.people.create(params[:person]) # # redirect_to(person_list_url) @@ -92,7 +92,7 @@ module ActionController #:nodoc: # # def create # company = params[:person].delete(:company) - # @company = Company.find_or_create_by_name(company[:name]) + # @company = Company.find_or_create_by(name: company[:name]) # @person = @company.people.create(params[:person]) # # respond_to do |format| @@ -120,7 +120,7 @@ module ActionController #:nodoc: # Note, however, the extra bit at the top of that action: # # company = params[:person].delete(:company) - # @company = Company.find_or_create_by_name(company[:name]) + # @company = Company.find_or_create_by(name: company[:name]) # # This is because the incoming XML document (if a web-service request is in process) can only contain a # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded): @@ -227,7 +227,7 @@ module ActionController #:nodoc: # i.e. its +show+ action. # 2. If there are validation errors, the response # renders a default action, which is <tt>:new</tt> for a - # +post+ request or <tt>:edit</tt> for +put+. + # +post+ request or <tt>:edit</tt> for +patch+ or +put+. # Thus an example like this - # # respond_to :html, :xml @@ -320,7 +320,7 @@ module ActionController #:nodoc: # 2. <tt>:action</tt> - overwrites the default render action used after an # unsuccessful html +post+ request. def respond_with(*resources, &block) - raise "In order to use respond_with, first you need to declare the formats your " << + raise "In order to use respond_with, first you need to declare the formats your " \ "controller responds to in the class level" if self.class.mimes_for_respond_to.empty? if collector = retrieve_collector_from_mimes(&block) diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 091facfd8d..59b91a240e 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -32,7 +32,7 @@ module ActionController # redirect_to :back # redirect_to proc { edit_post_url(@post) } # - # The redirection happens as a "302 Moved" header unless otherwise specified. + # The redirection happens as a "302 Found" header unless otherwise specified. # # redirect_to post_url(@post), status: :found # redirect_to action: 'atom', status: :moved_permanently @@ -65,7 +65,6 @@ module ActionController def redirect_to(options = {}, response_status = {}) #:doc: raise ActionControllerError.new("Cannot redirect to nil!") unless options raise AbstractController::DoubleRenderError if response_body - logger.debug { "Redirected by #{caller(1).first rescue "unknown"}" } if logger self.status = _extract_redirect_to_status(options, response_status) self.location = _compute_redirect_to_location(options) diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 0b3c438ec2..73e9b5660d 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -26,7 +26,7 @@ module ActionController #:nodoc: # # class PostsController # def index - # @posts = Post.scoped + # @posts = Post.all # render stream: true # end # end @@ -51,9 +51,9 @@ module ActionController #:nodoc: # # def dashboard # # Allow lazy execution of the queries - # @posts = Post.scoped - # @pages = Page.scoped - # @articles = Article.scoped + # @posts = Post.all + # @pages = Page.all + # @articles = Article.all # render stream: true # end # diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 8faa5f8a13..0d84fbb62b 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,4 +1,3 @@ -require 'active_support/concern' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/wrap' require 'active_support/rescuable' @@ -20,6 +19,20 @@ module ActionController end end + # Raised when a supplied parameter is not expected. + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => ActionController::UnpermittedParameters: found unexpected keys: a, b + class UnpermittedParameters < IndexError + attr_reader :params # :nodoc: + + def initialize(params) # :nodoc: + @params = params + super("found unpermitted parameters: #{params.join(", ")}") + end + end + # == Action Controller \Parameters # # Allows to choose which attributes should be whitelisted for mass updating @@ -41,13 +54,18 @@ module ActionController # permitted.class # => ActionController::Parameters # permitted.permitted? # => true # - # Person.first.update_attributes!(permitted) + # Person.first.update!(permitted) # # => #<Person id: 1, name: "Francesco", age: 22, role: "user"> # - # It provides a +permit_all_parameters+ option that controls the top-level - # behaviour of new instances. If it's +true+, all the parameters will be - # permitted by default. The default value for +permit_all_parameters+ - # option is +false+. + # It provides two options that controls the top-level behavior of new instances: + # + # * +permit_all_parameters+ - If it's +true+, all the parameters will be + # permitted by default. The default is +false+. + # * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters + # that are not explicitly permitted are found. The values can be <tt>:log</tt> to + # write a message on the logger or <tt>:raise</tt> to raise + # ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt> + # in test and development environments, +false+ otherwise. # # params = ActionController::Parameters.new # params.permitted? # => false @@ -57,6 +75,16 @@ module ActionController # params = ActionController::Parameters.new # params.permitted? # => true # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => {} + # + # ActionController::Parameters.action_on_unpermitted_parameters = :raise + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b + # # <tt>ActionController::Parameters</tt> is inherited from # <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means # that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>. @@ -66,6 +94,11 @@ module ActionController # params["key"] # => "value" class Parameters < ActiveSupport::HashWithIndifferentAccess cattr_accessor :permit_all_parameters, instance_accessor: false + cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false + + # Never raise an UnpermittedParameters exception because of these params + # are present. They are added by Rails and it's of no concern. + NEVER_UNPERMITTED_PARAMS = %w( controller action ) # Returns a new instance of <tt>ActionController::Parameters</tt>. # Also, sets the +permitted+ attribute to the default value of @@ -151,6 +184,20 @@ module ActionController # permitted.has_key?(:age) # => true # permitted.has_key?(:role) # => false # + # Only permitted scalars pass the filter. For example, given + # + # params.permit(:name) + # + # +:name+ passes it is a key of +params+ whose associated value is of type + # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+, + # +Date+, +Time+, +DateTime+, +StringIO+, or +IO+. 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: + # + # params.permit(tags: []) + # # You can also use +permit+ on nested parameters, like: # # params = ActionController::Parameters.new({ @@ -197,32 +244,15 @@ module ActionController filters.flatten.each do |filter| case filter - when Symbol, String then - if has_key?(filter) - _value = self[filter] - params[filter] = _value unless Hash === _value - end - keys.grep(/\A#{Regexp.escape(filter)}\(\d+[if]?\)\z/) { |key| params[key] = self[key] } + when Symbol, String + permitted_scalar_filter(params, filter) when Hash then - filter = filter.with_indifferent_access - - self.slice(*filter.keys).each do |key, values| - return unless values - - key = key.to_sym - - params[key] = each_element(values) do |value| - # filters are a Hash, so we expect value to be a Hash too - next if filter.is_a?(Hash) && !value.is_a?(Hash) - - value = self.class.new(value) if !value.respond_to?(:permit) - - value.permit(*Array.wrap(filter[key])) - end - end + hash_filter(params, filter) end end + unpermitted_parameters!(params) + params.permit! end @@ -301,6 +331,97 @@ module ActionController yield object end end + + def unpermitted_parameters!(params) + unpermitted_keys = unpermitted_keys(params) + if unpermitted_keys.any? + case self.class.action_on_unpermitted_parameters + when :log + ActionController::Base.logger.debug "Unpermitted parameters: #{unpermitted_keys.join(", ")}" + when :raise + raise ActionController::UnpermittedParameters.new(unpermitted_keys) + end + end + end + + def unpermitted_keys(params) + self.keys - params.keys - NEVER_UNPERMITTED_PARAMS + end + + # + # --- Filtering ---------------------------------------------------------- + # + + # This is a white list of permitted scalar types that includes the ones + # supported in XML and JSON requests. + # + # This list is in particular used to filter ordinary requests, String goes + # as first element to quickly short-circuit the common case. + # + # If you modify this collection please update the API of +permit+ above. + PERMITTED_SCALAR_TYPES = [ + String, + Symbol, + NilClass, + Numeric, + TrueClass, + FalseClass, + Date, + Time, + # DateTimes are Dates, we document the type but avoid the redundant check. + StringIO, + IO, + ] + + def permitted_scalar?(value) + PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)} + end + + def permitted_scalar_filter(params, key) + if has_key?(key) && permitted_scalar?(self[key]) + params[key] = self[key] + end + + keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k| + if permitted_scalar?(self[k]) + params[k] = self[k] + end + end + end + + def array_of_permitted_scalars?(value) + if value.is_a?(Array) + value.all? {|element| permitted_scalar?(element)} + end + end + + def array_of_permitted_scalars_filter(params, key) + if has_key?(key) && array_of_permitted_scalars?(self[key]) + params[key] = self[key] + end + end + + def hash_filter(params, filter) + filter = filter.with_indifferent_access + + # Slicing filters out non-declared keys. + slice(*filter.keys).each do |key, value| + return unless value + + if filter[key] == [] + # Declaration { comment_ids: [] }. + array_of_permitted_scalars_filter(params, key) + else + # Declaration { user: :name } or { user: [:name, :age, { adress: ... }] }. + params[key] = each_element(value) do |element| + if element.is_a?(Hash) + element = self.class.new(element) unless element.respond_to?(:permit) + element.permit(*Array.wrap(filter[key])) + end + end + end + end + end end # == Strong \Parameters @@ -329,7 +450,7 @@ module ActionController # # into a 400 Bad Request reply. # def update # redirect_to current_account.people.find(params[:id]).tap { |person| - # person.update_attributes!(person_params) + # person.update!(person_params) # } # end # @@ -374,12 +495,6 @@ module ActionController extend ActiveSupport::Concern include ActiveSupport::Rescuable - included do - rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception| - render text: "Required parameter missing: #{parameter_missing_exception.param}", status: :bad_request - end - end - # Returns a new ActionController::Parameters object that # has been instantiated with the <tt>request.parameters</tt>. def params diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 3e44155f73..5379547c57 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -20,22 +20,27 @@ module ActionController end initializer "action_controller.parameters_config" do |app| - ActionController::Parameters.permit_all_parameters = app.config.action_controller.delete(:permit_all_parameters) { false } + options = app.config.action_controller + + ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false } + ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do + (Rails.env.test? || Rails.env.development?) ? :log : false + end end initializer "action_controller.set_configs" do |app| paths = app.config.paths options = app.config.action_controller - options.logger ||= Rails.logger - options.cache_store ||= Rails.cache + options.logger ||= Rails.logger + options.cache_store ||= Rails.cache - options.javascripts_dir ||= paths["public/javascripts"].first - options.stylesheets_dir ||= paths["public/stylesheets"].first + options.javascripts_dir ||= paths["public/javascripts"].first + options.stylesheets_dir ||= paths["public/stylesheets"].first # Ensure readers methods get compiled - options.asset_host ||= app.config.asset_host - options.relative_url_root ||= app.config.relative_url_root + options.asset_host ||= app.config.asset_host + options.relative_url_root ||= app.config.relative_url_root ActiveSupport.on_load(:action_controller) do include app.routes.mounted_helpers diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index b49128c184..d598bac467 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -1,19 +1,30 @@ -require 'active_support/deprecation' require 'action_view/record_identifier' module ActionController module RecordIdentifier - MESSAGE = 'method will no longer be included by default in controllers since Rails 4.1. ' + - 'If you would like to use it in controllers, please include ' + - 'ActionView::RecodIdentifier module.' + MODULE_MESSAGE = 'Calling ActionController::RecordIdentifier.%s is deprecated and ' \ + 'will be removed in Rails 4.1, please call using ActionView::RecordIdentifier instead.' + INSTANCE_MESSAGE = '%s method will no longer be included by default in controllers ' \ + 'since Rails 4.1. If you would like to use it in controllers, please include ' \ + 'ActionView::RecordIdentifier module.' def dom_id(record, prefix = nil) - ActiveSupport::Deprecation.warn('dom_id ' + MESSAGE) + ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_id') ActionView::RecordIdentifier.dom_id(record, prefix) end def dom_class(record, prefix = nil) - ActiveSupport::Deprecation.warn('dom_class ' + MESSAGE) + ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_class') + ActionView::RecordIdentifier.dom_class(record, prefix) + end + + def self.dom_id(record, prefix = nil) + ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_id') + ActionView::RecordIdentifier.dom_id(record, prefix) + end + + def self.dom_class(record, prefix = nil) + ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_class') ActionView::RecordIdentifier.dom_class(record, prefix) end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 331d15d403..d8206b573d 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -81,8 +81,7 @@ module ActionController # # assert that the "_customer" partial was rendered with a specific object # assert_template partial: '_customer', locals: { customer: @customer } def assert_template(options = {}, message = nil) - # Force body to be read in case the - # template is being streamed + # Force body to be read in case the template is being streamed. response.body case options diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index b35761fb4a..9ab048b756 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -97,7 +97,6 @@ module ActionDispatch autoload :Assertions autoload :Integration autoload :IntegrationTest, 'action_dispatch/testing/integration' - autoload :PerformanceTest autoload :TestProcess autoload :TestRequest autoload :TestResponse diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 02ab49b44e..289e204ac8 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/object/duplicable' +require 'action_dispatch/http/parameter_filter' module ActionDispatch module Http diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 57660e93c4..89a7b12818 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -122,7 +122,7 @@ module ActionDispatch def valid_accept_header (xhr? && (accept || content_mime_type)) || - (accept && accept !~ BROWSER_LIKE_ACCEPTS) + (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) end def use_accept_header diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 6610315da7..25edd196c3 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -72,7 +72,7 @@ module ActionDispatch end end - # Convert nested Hash to HashWithIndifferentAccess + # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess def normalize_parameters(value) case value when Hash diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index d60c8775af..7b04d6e851 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -1,12 +1,16 @@ -require 'tempfile' require 'stringio' -require 'strscan' -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/string/access' require 'active_support/inflector' require 'action_dispatch/http/headers' require 'action_controller/metal/exceptions' +require 'rack/request' +require 'action_dispatch/http/cache' +require 'action_dispatch/http/mime_negotiation' +require 'action_dispatch/http/parameters' +require 'action_dispatch/http/filter_parameters' +require 'action_dispatch/http/upload' +require 'action_dispatch/http/url' +require 'active_support/core_ext/array/conversions' module ActionDispatch class Request < Rack::Request @@ -280,15 +284,14 @@ module ActionDispatch LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip end - protected - # Remove nils from the params hash def deep_munge(hash) - hash.each_value do |v| + hash.each do |k, v| case v when Array v.grep(Hash) { |x| deep_munge(x) } v.compact! + hash[k] = nil if v.empty? when Hash deep_munge(v) end @@ -297,6 +300,8 @@ module ActionDispatch hash end + protected + def parse_query(qs) deep_munge(super) end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 79437d6e85..8a97248eb3 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -19,7 +19,7 @@ module ActionDispatch # its interface is available directly. attr_accessor :tempfile - # TODO. + # A string with the headers of the multipart request. attr_accessor :headers def initialize(hash) # :nodoc: @@ -75,7 +75,7 @@ module ActionDispatch end module Upload # :nodoc: - # Convert nested Hash to HashWithIndifferentAccess and replace + # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess and replace # file upload hash with UploadedFile objects def normalize_parameters(value) if Hash === value && value.has_key?(:tempfile) diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index bced7d84c0..43f26d696d 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -32,7 +32,11 @@ module ActionDispatch params.reject! { |_,v| v.to_param.nil? } result = build_host_url(options) - result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) + if options[:trailing_slash] && !path.ends_with?('/') + result << path.sub(/(\?|\z)/) { "/" + $& } + else + result << path + end result << "?#{params.to_query}" unless params.empty? result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor] result diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 4a344f71af..82c55660ea 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -1,3 +1,5 @@ +require 'action_controller/metal/exceptions' + module ActionDispatch module Journey # The Formatter class is used for formatting URLs. For example, parameters @@ -27,7 +29,10 @@ module ActionDispatch return [route.format(parameterized_parts), params] end - raise Router::RoutingError.new "missing required keys: #{missing_keys}" + message = "No route matches #{constraints.inspect}" + message << " missing required keys: #{missing_keys.inspect}" if name + + raise ActionController::UrlGenerationError, message end def clear @@ -37,19 +42,16 @@ module ActionDispatch private def extract_parameterized_parts(route, options, recall, parameterize = nil) - constraints = recall.merge(options) - data = constraints.dup + parameterized_parts = recall.merge(options) keys_to_keep = route.parts.reverse.drop_while { |part| !options.key?(part) || (options[part] || recall[part]).nil? } | route.required_parts - (data.keys - keys_to_keep).each do |bad_key| - data.delete(bad_key) + (parameterized_parts.keys - keys_to_keep).each do |bad_key| + parameterized_parts.delete(bad_key) end - parameterized_parts = data.dup - if parameterize parameterized_parts.each do |k, v| parameterized_parts[k] = parameterize.call(k, v) diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index d18efd863a..063302e0f2 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -1,7 +1,7 @@ module ActionDispatch module Journey # :nodoc: class Route # :nodoc: - attr_reader :app, :path, :verb, :defaults, :ip, :name + attr_reader :app, :path, :defaults, :name attr_reader :constraints alias :conditions :constraints @@ -12,15 +12,11 @@ module ActionDispatch # +path+ is a path constraint. # +constraints+ is a hash of constraints to be applied to this route. def initialize(name, app, path, constraints, defaults = {}) - constraints = constraints.dup @name = name @app = app @path = path - @verb = constraints[:request_method] || // - @ip = constraints.delete(:ip) || // @constraints = constraints - @constraints.keep_if { |_,v| Regexp === v || String === v } @defaults = defaults @required_defaults = nil @required_parts = nil @@ -30,11 +26,11 @@ module ActionDispatch end def ast - return @decorated_ast if @decorated_ast - - @decorated_ast = path.ast - @decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self } - @decorated_ast + @decorated_ast ||= begin + decorated_ast = path.ast + decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self } + decorated_ast + end end def requirements # :nodoc: @@ -45,11 +41,11 @@ module ActionDispatch end def segments - @path.names + path.names end def required_keys - path.required_names.map { |x| x.to_sym } + required_defaults.keys + required_parts + required_defaults.keys end def score(constraints) @@ -83,12 +79,38 @@ module ActionDispatch @required_parts ||= path.required_names.map { |n| n.to_sym } end + def required_default?(key) + (constraints[:required_defaults] || []).include?(key) + end + def required_defaults - @required_defaults ||= begin - matches = parts - @defaults.dup.delete_if { |k,_| matches.include?(k) } + @required_defaults ||= @defaults.dup.delete_if do |k,_| + parts.include?(k) || !required_default?(k) end end + + def matches?(request) + constraints.all? do |method, value| + next true unless request.respond_to?(method) + + case value + when Regexp, String + value === request.send(method).to_s + when Array + value.include?(request.send(method)) + else + value === request.send(method) + end + end + end + + def ip + constraints[:ip] || // + end + + def verb + constraints[:request_method] || // + end end end end diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 1fc45a2109..31868b1814 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -131,11 +131,7 @@ module ActionDispatch } routes.concat get_routes_as_head(routes) - routes.sort_by!(&:precedence).select! { |r| - r.constraints.all? { |k, v| v === req.send(k) } && - r.verb === req.request_method - } - routes.reject! { |r| req.ip && !(r.ip === req.ip) } + routes.sort_by!(&:precedence).select! { |r| r.matches?(req) } routes.map! { |r| match_data = r.path.match(req.path_info) diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb index 32829a1f20..a99d6d0d6a 100644 --- a/actionpack/lib/action_dispatch/journey/routes.rb +++ b/actionpack/lib/action_dispatch/journey/routes.rb @@ -16,12 +16,12 @@ module ActionDispatch end def length - @routes.length + routes.length end alias :size :length def last - @routes.last + routes.last end def each(&block) @@ -33,24 +33,23 @@ module ActionDispatch end def partitioned_routes - @partitioned_routes ||= routes.partition { |r| - r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? } - } + @partitioned_routes ||= routes.partition do |r| + r.path.anchored && r.ast.grep(Nodes::Symbol).all?(&:default_regexp?) + end end def ast - return @ast if @ast - return if partitioned_routes.first.empty? - - asts = partitioned_routes.first.map { |r| r.ast } - @ast = Nodes::Or.new(asts) + @ast ||= begin + asts = partitioned_routes.first.map(&:ast) + Nodes::Or.new(asts) unless asts.empty? + end end def simulator - return @simulator if @simulator - - gtg = GTG::Builder.new(ast).transition_table - @simulator = GTG::Simulator.new(gtg) + @simulator ||= begin + gtg = GTG::Builder.new(ast).transition_table + GTG::Simulator.new(gtg) + end end # Add a route to the routing table. diff --git a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb index d338996240..94efeb79fa 100644 --- a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb +++ b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb @@ -17,7 +17,9 @@ module ActionDispatch status, headers, body = @app.call(env) if headers["X-UA-Compatible"] && @header - headers["X-UA-Compatible"] << "," << @header.to_s + unless headers["X-UA-Compatible"][@header] + headers["X-UA-Compatible"] << "," << @header.to_s + end else headers["X-UA-Compatible"] = @header end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 121a11c8e1..6ecbb03784 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -15,7 +15,7 @@ module ActionDispatch # being written will be sent out with the response. Reading a cookie does not get # the cookie object itself back, just the value it holds. # - # Examples for writing: + # Examples of writing: # # # Sets a simple session cookie. # # This cookie will be deleted when the user's browser is closed. @@ -38,7 +38,7 @@ module ActionDispatch # # You can also chain these methods: # cookies.permanent.signed[:login] = "XJ-122" # - # Examples for reading: + # Examples of reading: # # cookies[:user_name] # => "david" # cookies.size # => 2 diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index ac8f6af7fe..64230ff1ae 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -2,7 +2,6 @@ require 'action_dispatch/http/request' require 'action_dispatch/middleware/exception_wrapper' require 'action_dispatch/routing/inspector' - module ActionDispatch # This middleware is responsible for logging exceptions and # showing a debugging page in case the request is local. @@ -15,18 +14,17 @@ module ActionDispatch end def call(env) - begin - response = (_, headers, body = @app.call(env)) - - if headers['X-Cascade'] == 'pass' - body.close if body.respond_to?(:close) - raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" - end - rescue Exception => exception - raise exception if env['action_dispatch.show_exceptions'] == false + _, headers, body = response = @app.call(env) + + if headers['X-Cascade'] == 'pass' + body.close if body.respond_to?(:close) + raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" end - exception ? render_exception(env, exception) : response + response + rescue Exception => exception + raise exception if env['action_dispatch.show_exceptions'] == false + render_exception(env, exception) end private @@ -42,12 +40,11 @@ module ActionDispatch :application_trace => wrapper.application_trace, :framework_trace => wrapper.framework_trace, :full_trace => wrapper.full_trace, - :routes => formatted_routes(exception), + :routes_inspector => routes_inspector(exception), :source_extract => wrapper.source_extract, :line_number => wrapper.line_number, :file => wrapper.file ) - file = "rescues/#{wrapper.rescue_template}" body = template.render(:template => file, :layout => 'rescues/layout') render(wrapper.status_code, body) @@ -85,11 +82,9 @@ module ActionDispatch @stderr_logger ||= ActiveSupport::Logger.new($stderr) end - def formatted_routes(exception) - return false unless @routes_app.respond_to?(:routes) - if exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error) - inspector = ActionDispatch::Routing::RoutesInspector.new - inspector.collect_routes(@routes_app.routes.routes) + def routes_inspector(exception) + if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)) + ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes) end end end diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index ee69c8b49d..7489ce8028 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -1,5 +1,4 @@ require 'action_controller/metal/exceptions' -require 'active_support/core_ext/exception' require 'active_support/core_ext/class/attribute_accessors' module ActionDispatch @@ -13,7 +12,8 @@ module ActionDispatch 'ActionController::NotImplemented' => :not_implemented, 'ActionController::UnknownFormat' => :not_acceptable, 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, - 'ActionController::BadRequest' => :bad_request + 'ActionController::BadRequest' => :bad_request, + 'ActionController::ParameterMissing' => :bad_request ) cattr_accessor :rescue_templates @@ -97,7 +97,7 @@ module ActionDispatch if File.exists?(full_path) File.open(full_path, "r") do |file| start = [line - 3, 0].max - lines = file.lines.drop(start).take(6) + lines = file.each_line.drop(start).take(6) Hash[*(start+1..(lines.count+start)).zip(lines).flatten] end end diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index f24e9b8e18..7e56feb90a 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -59,12 +59,12 @@ module ActionDispatch @flash[k] end - # Convenience accessor for flash.now[:alert]= + # Convenience accessor for <tt>flash.now[:alert]=</tt>. def alert=(message) self[:alert] = message end - # Convenience accessor for flash.now[:notice]= + # Convenience accessor for <tt>flash.now[:notice]=</tt>. def notice=(message) self[:notice] = message end @@ -82,7 +82,7 @@ module ActionDispatch else new end - + flash.tap(&:sweep) end @@ -169,6 +169,14 @@ module ActionDispatch # vanish when the current action is done. # # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>. + # + # Also, brings two convenience accessors: + # + # flash.now.alert = "Beware now!" + # # Equivalent to flash.now[:alert] = "Beware now!" + # + # flash.now.notice = "Good luck now!" + # # Equivalent to flash.now[:notice] = "Good luck now!" def now @now ||= FlashNow.new(self) end @@ -199,22 +207,22 @@ module ActionDispatch @discard.replace @flashes.keys end - # Convenience accessor for flash[:alert] + # Convenience accessor for <tt>flash[:alert]</tt>. def alert self[:alert] end - # Convenience accessor for flash[:alert]= + # Convenience accessor for <tt>flash[:alert]=</tt>. def alert=(message) self[:alert] = message end - # Convenience accessor for flash[:notice] + # Convenience accessor for <tt>flash[:notice]</tt>. def notice self[:notice] end - # Convenience accessor for flash[:notice]= + # Convenience accessor for <tt>flash[:notice]=</tt>. def notice=(message) self[:notice] = message end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 2c98ca03a8..0898ad82dd 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -47,14 +47,12 @@ module ActionDispatch when Proc strategy.call(request.raw_post) when :xml_simple, :xml_node - data = Hash.from_xml(request.raw_post) || {} + data = request.deep_munge(Hash.from_xml(request.body.read) || {}) data.with_indifferent_access - when :yaml - YAML.load(request.raw_post) when :json - data = ActiveSupport::JSON.decode(request.raw_post) + data = ActiveSupport::JSON.decode(request.body) data = {:_json => data} unless data.is_a?(Hash) - data.with_indifferent_access + request.deep_munge(data).with_indifferent_access else false end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 5abf8f2802..4e36c9bb49 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -1,23 +1,59 @@ 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. + # {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 + # at GetIp#calculate_ip. + # + # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2] + # requires. Some Rack servers simply drop preceeding headers, and only report + # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers]. + # If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn) + # then you should test your Rack server to make sure your data is good. + # + # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. + # This middleware assumes that there is at least one proxy sitting around + # and setting headers with the client's remote IP address. If you don't use + # a proxy, because you are hosted on e.g. Heroku without SSL, any client can + # claim to have any IP address by setting the X-Forwarded-For header. If you + # care about that, then you need to explicitly drop or ignore those headers + # sometime before this middleware runs. class RemoteIp - class IpSpoofAttackError < StandardError ; end + class IpSpoofAttackError < StandardError; end - # IP addresses that are "trusted proxies" that can be stripped from - # the comma-delimited list in the X-Forwarded-For header. See also: - # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces - # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses. + # The default trusted IPs list simply includes IP addresses that are + # guaranteed by the IP specification to be private addresses. Those will + # not be the ultimate client IP in production, and so are discarded. See + # http://en.wikipedia.org/wiki/Private_network for details. TRUSTED_PROXIES = %r{ - ^127\.0\.0\.1$ | # localhost - ^::1$ | - ^(10 | # private IP 10.x.x.x - 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255 - 192\.168 | # private IP 192.168.x.x - fc00:: # private IP fc00 - )\. + ^127\.0\.0\.1$ | # localhost IPv4 + ^::1$ | # localhost IPv6 + ^fc00: | # private IPv6 range fc00 + ^10\. | # private IPv4 range 10.x.x.x + ^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255 + ^192\.168\. # private IPv4 range 192.168.x.x }x attr_reader :check_ip, :proxies + # Create a new +RemoteIp+ middleware instance. + # + # The +check_ip_spoofing+ option is on by default. When on, an exception + # is raised if it looks like the client is trying to lie about its own IP + # address. It makes sense to turn off this check on sites aimed at non-IP + # clients (like WAP devices), or behind proxies that set headers in an + # incorrect or confusing way (like AWS ELB). + # + # The +custom_trusted+ argument can take a regex, which will be used + # instead of +TRUSTED_PROXIES+, or a string, which will be used in addition + # to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the + # middle (or at the beginning) of the X-Forwarded-For list, with your proxy + # servers after it. If your proxies aren't removed, pass them in via the + # +custom_trusted+ parameter. That way, the middleware will ignore those + # IP addresses, and return the one that you want. def initialize(app, check_ip_spoofing = true, custom_proxies = nil) @app = app @check_ip = check_ip_spoofing @@ -31,15 +67,23 @@ module ActionDispatch end end + # Since the IP address may not be needed, we store the object here + # without calculating the IP to keep from slowing down the majority of + # requests. For those requests that do need to know the IP, the + # GetIp#calculate_ip method will calculate the memoized client IP address. def call(env) env["action_dispatch.remote_ip"] = GetIp.new(env, self) @app.call(env) end + # The GetIp class exists as a way to defer processing of the request data + # into an actual IP address. If the ActionDispatch::Request#remote_ip method + # is called, this class will calculate the value and then memoize it. class GetIp - # IP v4 and v6 (with compression) validation regexp - # https://gist.github.com/1289635 + # 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]. 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 (^( @@ -63,62 +107,78 @@ module ActionDispatch }x def initialize(env, middleware) - @env = env - @middleware = middleware - @ip = nil + @env = env + @check_ip = middleware.check_ip + @proxies = middleware.proxies end - # Determines originating IP address. REMOTE_ADDR is the standard - # but will be wrong if the user is behind a proxy. Proxies will set - # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those. - # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of - # multiple chained proxies. The first address which is in this list - # if it's not a known proxy will be the originating IP. - # Format of HTTP_X_FORWARDED_FOR: - # client_ip, proxy_ip1, proxy_ip2... - # http://en.wikipedia.org/wiki/X-Forwarded-For + # Sort through the various IP address headers, looking for the IP most + # likely to be the address of the actual remote client making this + # request. + # + # REMOTE_ADDR will be correct if the request is made directly against the + # Ruby process, on e.g. Heroku. When the request is proxied by another + # server like HAProxy or Nginx, the IP address that made the original + # request will be put in an X-Forwarded-For header. If there are multiple + # proxies, that header may contain a list of IPs. Other proxy services + # set the Client-Ip header instead, so we check that too. + # + # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/], + # while the first IP in the list is likely to be the "originating" IP, + # it could also have been set by the client maliciously. + # + # In order to find the first address that is (probably) accurate, we + # take the list of IPs, remove known and trusted proxies, and then take + # the last address left, which was presumably set by one of those proxies. def calculate_ip - client_ip = @env['HTTP_CLIENT_IP'] - forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first - remote_addrs = ips_from('REMOTE_ADDR') - - check_ip = client_ip && @middleware.check_ip - if check_ip && forwarded_ip != client_ip + # Set by the Rack web server, this is a single value. + remote_addr = ips_from('REMOTE_ADDR').last + + # Could be a CSV list and/or repeated headers that were concatenated. + client_ips = ips_from('HTTP_CLIENT_IP').reverse + forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse + + # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set. + # If they are both set, it means that this request passed through two + # proxies with incompatible IP header conventions, and there is no way + # for us to determine which header is the right one after the fact. + # Since we have no idea, we give up and explode. + should_check_ip = @check_ip && client_ips.last + if should_check_ip && !forwarded_ips.include?(client_ips.last) # We don't know which came from the proxy, and which from the user - raise IpSpoofAttackError, "IP spoofing attack?!" \ - "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \ + raise IpSpoofAttackError, "IP spoofing attack?! " + + "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " + "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" end - client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten - if client_ips.present? - client_ips.first - else - # If there is no client ip we can return first valid proxy ip from REMOTE_ADDR - remote_addrs.find { |ip| valid_ip? ip } - end + # We assume these things about the IP headers: + # + # - X-Forwarded-For will be a list of IPs, one per proxy, or blank + # - Client-Ip is propagated from the outermost proxy, or is blank + # - REMOTE_ADDR will be the IP that made the request to Rack + ips = [forwarded_ips, client_ips, remote_addr].flatten.compact + + # If every single IP option is in the trusted list, just return REMOTE_ADDR + filter_proxies(ips).first || remote_addr end + # Memoizes the value returned by #calculate_ip and returns it for + # ActionDispatch::Request to use. def to_s @ip ||= calculate_ip end - private + protected def ips_from(header) - @env[header] ? @env[header].strip.split(/[,\s]+/) : [] - end - - def valid_ip?(ip) - ip =~ VALID_IP - end - - def not_a_proxy?(ip) - ip !~ @middleware.proxies + # Split the comma-separated list into an array of strings + ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : [] + # Only return IPs that are valid according to the regex + ips.select{ |ip| ip =~ VALID_IP } end - def remove_proxies(ips) - ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) } + def filter_proxies(ips) + ips.reject { |ip| ip =~ @proxies } end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index ce5f89ee5b..1e6ed624b0 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -21,15 +21,12 @@ module ActionDispatch # # Session options: # - # * <tt>:secret</tt>: An application-wide key string or block returning a - # string called per generated digest. The block is called with the - # CGI::Session instance as an argument. It's important that the secret - # is not vulnerable to a dictionary attack. Therefore, you should choose - # a secret consisting of random numbers and letters and more than 30 - # characters. + # * <tt>:secret</tt>: An application-wide key string. It's important that + # the secret is not vulnerable to a dictionary attack. Therefore, you + # should choose a secret consisting of random numbers and letters and + # more than 30 characters. # # secret: '449fe2e7daee471bffae2fd8dc02313d' - # secret: Proc.new { User.current_user.secret_key } # # * <tt>:digest</tt>: The message digest algorithm used to verify session # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL, @@ -39,21 +36,38 @@ module ActionDispatch # "rake secret" and set the key in config/initializers/secret_token.rb. # # Note that changing digest or secret invalidates all existing sessions! - class CookieStore < Rack::Session::Cookie + class CookieStore < Rack::Session::Abstract::ID include Compatibility include StaleSessionCheck include SessionObject - # Override rack's method + def initialize(app, options={}) + super(app, options.merge!(:cookie_only => true)) + end + def destroy_session(env, session_id, options) - new_sid = super + new_sid = generate_sid unless options[:drop] # Reset hash and Assign the new session id env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {} new_sid end + def load_session(env) + stale_session_check! do + data = unpacked_cookie_data(env) + data = persistent_session_id!(data) + [data["session_id"], data] + end + end + private + def extract_session_id(env) + stale_session_check! do + unpacked_cookie_data(env)["session_id"] + end + end + def unpacked_cookie_data(env) env["action_dispatch.request.unsigned_session_cookie"] ||= begin stale_session_check! do @@ -65,6 +79,12 @@ module ActionDispatch end end + def persistent_session_id!(data, sid=nil) + data ||= {} + data["session_id"] ||= sid || generate_sid + data + end + def set_session(env, sid, session_data, options) session_data["session_id"] = sid session_data diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 2b37a8d026..fcc5bc12c4 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -16,9 +16,9 @@ module ActionDispatch # catches the exceptions and returns a FAILSAFE_RESPONSE. class ShowExceptions FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' }, - ["500 Internal Server Error\n" << - "If you are the administrator of this website, then please read this web " << - "application's log file and/or the web server's log file to find out what " << + ["500 Internal Server Error\n" \ + "If you are the administrator of this website, then please read this web " \ + "application's log file and/or the web server's log file to find out what " \ "went wrong."]] def initialize(app, exceptions_app) @@ -27,13 +27,10 @@ module ActionDispatch end def call(env) - begin - response = @app.call(env) - rescue Exception => exception - raise exception if env['action_dispatch.show_exceptions'] == false - end - - response || render_exception(env, exception) + @app.call(env) + rescue Exception => exception + raise exception if env['action_dispatch.show_exceptions'] == false + render_exception(env, exception) end private diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 9098f4e170..9e03cbf2b7 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -45,7 +45,7 @@ module ActionDispatch # http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02 def hsts_headers if @hsts - value = "max-age=#{@hsts[:expires]}" + value = "max-age=#{@hsts[:expires].to_i}" value += "; includeSubDomains" if @hsts[:subdomains] { 'Strict-Transport-Security' => value } else diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index e3b15b43b9..c6a7d9c415 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -6,7 +6,8 @@ module ActionDispatch def initialize(root, cache_control) @root = root.chomp('/') @compiled_root = /^#{Regexp.escape(root)}/ - @file_server = ::Rack::File.new(@root, cache_control) + headers = cache_control && { 'Cache-Control' => cache_control } + @file_server = ::Rack::File.new(@root, headers) end def match?(path) 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 55079848bd..ab24118f3e 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,8 +1,8 @@ <% unless @exception.blamed_files.blank? %> <% if (hide = @exception.blamed_files.length > 8) %> - <a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a> + <a href="#" onclick="toggleTrace()">Toggle blamed files</a> <% end %> - <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre> + <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre> <% end %> <% @@ -18,17 +18,17 @@ %> <h2 style="margin-top: 30px">Request</h2> -<p><b>Parameters</b>: <pre><%=h request_dump %></pre></p> +<p><b>Parameters</b>:</p> <pre><%= request_dump %></pre> <div class="details"> - <div class="summary"><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></div> - <div id="session_dump" style="display:none"><p><pre><%= debug_hash @request.session %></pre></p></div> + <div class="summary"><a href="#" onclick="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="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></div> - <div id="env_dump" style="display:none"><p><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></p></div> + <div class="summary"><a href="#" onclick="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> <h2 style="margin-top: 30px">Response</h2> -<p><b>Headers</b>: <pre><%=h defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p> +<p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb index 8771b5fd6d..9d947aea40 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb @@ -12,15 +12,15 @@ <div id="traces"> <% names.each do |name| %> <% - show = "document.getElementById('#{name.gsub(/\s/, '-')}').style.display='block';" - hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub(/\s/, '-')}').style.display='none';"} + show = "show('#{name.gsub(/\s/, '-')}');" + hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"} %> <a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %> <% end %> <% traces.each do |name, trace| %> <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == "Application Trace") ? 'block' : 'none' %>;"> - <pre><code><%=h trace.join "\n" %></code></pre> + <pre><code><%= trace.join "\n" %></code></pre> </div> <% end %> </div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb index 1c6b5010a3..57a2940802 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb @@ -1,14 +1,14 @@ <header> <h1> - <%=h @exception.class.to_s %> + <%= @exception.class.to_s %> <% if @request.parameters['controller'] %> - in <%=h @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %> + in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> <% end %> </h1> </header> <div id="container"> - <h2><%=h @exception.message %></h2> + <h2><%= @exception.message %></h2> <%= render template: "rescues/_source" %> <%= render template: "rescues/_trace" %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb index bcab5959a0..9878c2747e 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -4,7 +4,11 @@ <meta charset="utf-8" /> <title>Action Controller: Exception caught</title> <style> - body { background-color: #fff; color: #333; margin: 0px} + body { + background-color: #FAFAFA; + color: #333; + margin: 0px; + } body, p, ol, ul, td { font-family: helvetica, verdana, arial, sans-serif; @@ -18,31 +22,25 @@ } pre.box { - border: #eee solid 1px; + border: 1px solid #EEE; padding: 10px; margin: 0px; width: 958px; } header { - background: whiteSmoke; - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#980905',endColorstr='#c52f24'); - background: -webkit-gradient(linear,0% 0,0% 100%,from(#980905),to(#C52F24)); - background: -moz-linear-gradient(270deg,#980905,#C52F24); - color: #fff; - padding: 0.5em; + color: #F0F0F0; + background: #C52F24; + padding: 0.5em 1.5em; } h2 { color: #C52F24; - padding: 2px; line-height: 25px; } .details { - border: 1px solid #E5E5E5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; + border: 1px solid #D0D0D0; border-radius: 4px; margin: 1em 0px; display: block; @@ -51,7 +49,7 @@ .summary { padding: 8px 15px; - border-bottom: 1px solid #E5E5E5; + border-bottom: 1px solid #D0D0D0; display: block; } @@ -61,8 +59,9 @@ } #container { - margin: auto; - width: 98%; + box-sizing: border-box; + width: 100%; + padding: 0 1.5em; } .source * { @@ -84,7 +83,7 @@ .source .data { font-size: 80%; overflow: auto; - background-color: #fff; + background-color: #FFF; } .info { @@ -99,8 +98,12 @@ text-align: right; } + .line { + padding-left: 10px; + } + .line:hover { - background-color: #f6f6f6; + background-color: #F6F6F6; } .line.active { @@ -109,8 +112,32 @@ a { color: #980905; } a:visited { color: #666; } - a:hover { color: #C52F24;} + a:hover { color: #C52F24; } + + <%= yield :style %> </style> + + <script> + var toggle = function(id) { + var s = document.getElementById(id).style; + s.display = s.display == 'none' ? 'block' : 'none'; + } + var show = function(id) { + document.getElementById(id).style.display = 'block'; + } + var hide = function(id) { + document.getElementById(id).style.display = 'none'; + } + var toggleTrace = function() { + toggle('blame_trace'); + } + var toggleSessionDump = function() { + toggle('session_dump'); + } + var toggleEnvDump = function() { + toggle('env_dump'); + } + </script> </head> <body> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb index c5917b9acb..ca14215946 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb @@ -3,5 +3,5 @@ </header> <div id="container"> - <h2><%=h @exception.message %></h2> + <h2><%= @exception.message %></h2> </div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb index ca85e6d048..61690d3e50 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb @@ -2,27 +2,29 @@ <h1>Routing Error</h1> </header> <div id="container"> - <h2><%=h @exception.message %></h2> + <h2><%= @exception.message %></h2> <% unless @exception.failures.empty? %> <p> <h2>Failure reasons:</h2> <ol> <% @exception.failures.each do |route, reason| %> - <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li> + <li><code><%= route.inspect.gsub('\\', '') %></code> failed because <%= reason.downcase %></li> <% end %> </ol> </p> <% end %> + <%= render template: "rescues/_trace" %> - <h2> - Routes - </h2> + <% if @routes_inspector %> + <h2> + Routes + </h2> - <p> - Routes match in priority from top to bottom - </p> + <p> + Routes match in priority from top to bottom + </p> -<%= render layout: "routes/route_wrapper" do %> - <%= render partial: "routes/route", collection: @routes %> -<% end %> + <%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %> + <% end %> +</div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb index 01faf5a475..63216ef7c5 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb @@ -1,20 +1,20 @@ <% @source_extract = @exception.source_extract(0, :html) %> <header> <h1> - <%=h @exception.original_exception.class.to_s %> in - <%=h @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%=h @request.parameters["action"] %> + <%= @exception.original_exception.class.to_s %> in + <%= @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%= @request.parameters["action"] %> </h1> </header> <div id="container"> <p> - Showing <i><%=h @exception.file_name %></i> where line <b>#<%=h @exception.line_number %></b> raised: - <pre><code><%=h @exception.message %></code></pre> - </p> + Showing <i><%= @exception.file_name %></i> where line <b>#<%= @exception.line_number %></b> raised: + </p> + <pre><code><%= @exception.message %></code></pre> <div class="source"> <div class="info"> - <p>Extracted source (around line <strong>#<%=h @exception.line_number %></strong>): + <p>Extracted source (around line <strong>#<%= @exception.line_number %></strong>):</p> </div> <div class="data"> <table cellpadding="0" cellspacing="0" class="lines"> @@ -36,9 +36,8 @@ </div> </div> - <p><%=h @exception.sub_template_message %></p> + <p><%= @exception.sub_template_message %></p> <%= render template: "rescues/_trace" %> <%= render template: "rescues/_request_and_response" %> - </div> </div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb index 65fc34df90..c1fbf67eed 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb @@ -2,5 +2,5 @@ <h1>Unknown action</h1> </header> <div id="container"> - <h2><%=h @exception.message %></h2> - </div> + <h2><%= @exception.message %></h2> +</div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb index 400ae97d22..24e44f31ac 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb @@ -7,7 +7,7 @@ <td data-route-verb='<%= route[:verb] %>'> <%= route[:verb] %> </td> - <td data-route-path='<%= route[:path] %>'> + <td data-route-path='<%= route[:path] %>' data-regexp='<%= route[:regexp] %>'> <%= route[:path] %> </td> <td data-route-reqs='<%= route[:reqs] %>'> diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_route_wrapper.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_route_wrapper.html.erb deleted file mode 100644 index dc17cb77ef..0000000000 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_route_wrapper.html.erb +++ /dev/null @@ -1,56 +0,0 @@ -<style type='text/css'> - #route_table td {padding: 0 30px;} - #route_table {margin: 0 auto 0;} -</style> - -<table id='route_table' class='route_table'> - <thead> - <tr> - <th>Helper<br /> - <%= link_to "Path", "#", 'data-route-helper' => '_path', - title: "Returns a relative path (without the http or domain)" %> / - <%= link_to "Url", "#", 'data-route-helper' => '_url', - title: "Returns an absolute url (with the http and domain)" %> - </th> - <th>HTTP Verb</th> - <th>Path</th> - <th>Controller#Action</th> - </tr> - </thead> - <tbody> - <%= yield %> - </tbody> -</table> - -<script type='text/javascript'> - function each(elems, func) { - if (!elems instanceof Array) { elems = [elems]; } - for (var i = elems.length; i--; ) { - func(elems[i]); - } - } - - function setValOn(elems, val) { - each(elems, function(elem) { - elem.innerHTML = val; - }); - } - - function onClick(elems, func) { - each(elems, function(elem) { - elem.onclick = func; - }); - } - - // Enables functionality to toggle between `_path` and `_url` helper suffixes - function setupRouteToggleHelperLinks() { - var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]'); - onClick(toggleLinks, function(){ - var helperTxt = this.getAttribute("data-route-helper"); - var helperElems = document.querySelectorAll('[data-route-name] span.helper'); - setValOn(helperElems, helperTxt); - }); - } - - setupRouteToggleHelperLinks(); -</script> diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb new file mode 100644 index 0000000000..95461fa693 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -0,0 +1,144 @@ +<% content_for :style do %> + #route_table { + margin: 0 auto 0; + border-collapse: collapse; + } + + #route_table td { + padding: 0 30px; + } + + #route_table tr.bottom th { + padding-bottom: 10px; + line-height: 15px; + } + + #route_table .matched_paths { + background-color: LightGoldenRodYellow; + } + + #route_table .matched_paths { + border-bottom: solid 3px SlateGrey; + } + + #path_search { + width: 80%; + font-size: inherit; + } +<% end %> + +<table id='route_table' class='route_table'> + <thead> + <tr> + <th>Helper</th> + <th>HTTP Verb</th> + <th>Path</th> + <th>Controller#Action</th> + </tr> + <tr class='bottom'> + <th><%# Helper %> + <%= link_to "Path", "#", 'data-route-helper' => '_path', + title: "Returns a relative path (without the http or domain)" %> / + <%= link_to "Url", "#", 'data-route-helper' => '_url', + title: "Returns an absolute url (with the http and domain)" %> + </th> + <th><%# HTTP Verb %> + </th> + <th><%# Path %> + <%= search_field(:path, nil, id: 'path_search', placeholder: "Path Match") %> + </th> + <th><%# Controller#action %> + </th> + </tr> + </thead> + <tbody class='matched_paths' id='matched_paths'> + </tbody> + <tbody> + <%= yield %> + </tbody> +</table> + +<script type='text/javascript'> + function each(elems, func) { + if (!elems instanceof Array) { elems = [elems]; } + for (var i = 0, len = elems.length; i < len; i++) { + func(elems[i]); + } + } + + function setValOn(elems, val) { + each(elems, function(elem) { + elem.innerHTML = val; + }); + } + + function onClick(elems, func) { + each(elems, function(elem) { + elem.onclick = func; + }); + } + + // Enables functionality to toggle between `_path` and `_url` helper suffixes + function setupRouteToggleHelperLinks() { + var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]'); + onClick(toggleLinks, function(){ + var helperTxt = this.getAttribute("data-route-helper"), + helperElems = document.querySelectorAll('[data-route-name] span.helper'); + setValOn(helperElems, helperTxt); + }); + } + + // takes an array of elements with a data-regexp attribute and + // passes their their parent <tr> into the callback function + // if the regexp matchs a given path + function eachElemsForPath(elems, path, func) { + each(elems, function(e){ + var reg = e.getAttribute("data-regexp"); + if (path.match(RegExp(reg))) { + func(e.parentNode.cloneNode(true)); + } + }) + } + + // Ensure path always starts with a slash "/" and remove params or fragments + function sanitizePath(path) { + var path = path.charAt(0) == '/' ? path : "/" + path; + return path.replace(/\#.*|\?.*/, ''); + } + + // Enables path search functionality + function setupMatchPaths() { + var regexpElems = document.querySelectorAll('#route_table [data-regexp]'), + pathElem = document.querySelector('#path_search'), + selectedSection = document.querySelector('#matched_paths'), + noMatchText = '<tr><th colspan="4">None</th></tr>'; + + + // Remove matches if no path is present + pathElem.onblur = function(e) { + if (pathElem.value === "") selectedSection.innerHTML = ""; + } + + // On key press perform a search for matching paths + pathElem.onkeyup = function(e){ + var path = sanitizePath(pathElem.value), + defaultText = '<tr><th colspan="4">Paths Matching (' + path + '):</th></tr>'; + + // Clear out results section + selectedSection.innerHTML= defaultText; + + // Display matches if they exist + eachElemsForPath(regexpElems, path, function(e){ + selectedSection.appendChild(e); + }); + + // If no match present, tell the user + if (selectedSection.innerHTML === defaultText) { + selectedSection.innerHTML = selectedSection.innerHTML + noMatchText; + } + } + } + + setupMatchPaths(); + setupRouteToggleHelperLinks(); +</script> diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 73b2b4ac1d..bc6dd7145c 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -34,6 +34,23 @@ module ActionDispatch super.to_s end + def regexp + __getobj__.path.to_regexp + end + + def json_regexp + str = regexp.inspect. + sub('\\A' , '^'). + sub('\\Z' , '$'). + sub('\\z' , '$'). + sub(/^\// , ''). + sub(/\/[a-z]*$/ , ''). + gsub(/\(\?#.+\)/ , ''). + gsub(/\(\?-\w+:/ , '('). + gsub(/\s/ , '') + Regexp.new(str).source + end + def reqs @reqs ||= begin reqs = endpoint @@ -61,32 +78,51 @@ module ActionDispatch ## # This class is just used for displaying route information when someone - # executes `rake routes`. People should not use this class. + # executes `rake routes` or looks at the RoutingError page. + # People should not use this class. class RoutesInspector # :nodoc: - def initialize - @engines = Hash.new + def initialize(routes) + @engines = {} + @routes = routes end - def format(all_routes, filter = nil) - if filter - all_routes = all_routes.select{ |route| route.defaults[:controller] == filter } + def format(formatter, filter = nil) + routes_to_display = filter_routes(filter) + + routes = collect_routes(routes_to_display) + formatter.section routes + + @engines.each do |name, engine_routes| + formatter.section_title "Routes for #{name}" + formatter.section engine_routes end - routes = collect_routes(all_routes) + formatter.result + end + + private - formatted_routes(routes) + - formatted_routes_for_engines + def filter_routes(filter) + if filter + @routes.select { |route| route.defaults[:controller] == filter } + else + @routes + end end def collect_routes(routes) - routes = routes.collect do |route| + routes.collect do |route| RouteWrapper.new(route) end.reject do |route| route.internal? end.collect do |route| collect_engine_routes(route) - { name: route.name, verb: route.verb, path: route.path, reqs: route.reqs } + { name: route.name, + verb: route.verb, + path: route.path, + reqs: route.reqs, + regexp: route.json_regexp } end end @@ -100,21 +136,55 @@ module ActionDispatch @engines[name] = collect_routes(routes.routes) end end + end - def formatted_routes_for_engines - @engines.map do |name, routes| - ["\nRoutes for #{name}:"] + formatted_routes(routes) - end.flatten + class ConsoleFormatter + def initialize + @buffer = [] end - def formatted_routes(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 + def result + @buffer.join("\n") + end - routes.map do |r| - "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" + def section_title(title) + @buffer << "\n#{title}:" + end + + def section(routes) + @buffer << draw_section(routes) + 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 + + routes.map do |r| + "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" + end end + end + + class HtmlTableFormatter + def initialize(view) + @view = view + @buffer = [] + end + + def section_title(title) + @buffer << %(<tr><th colspan="4">#{title}</th></tr>) + end + + def section(routes) + @buffer << @view.render(partial: "routes/route", collection: routes) + end + + def result + @view.raw @view.render(layout: "routes/table") { + @view.raw @buffer.join("\n") + } end end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 0dc6403ec0..6d93f609a6 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -8,6 +8,8 @@ require 'action_dispatch/routing/redirection' module ActionDispatch module Routing class Mapper + URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port] + class Constraints #:nodoc: def self.new(app, constraints, request = Rack::Request) if constraints.any? @@ -26,15 +28,10 @@ module ActionDispatch def matches?(env) req = @request.new(env) - @constraints.each { |constraint| - if constraint.respond_to?(:matches?) && !constraint.matches?(req) - return false - elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req)) - return false - end - } - - return true + @constraints.all? do |constraint| + (constraint.respond_to?(:matches?) && constraint.matches?(req)) || + (constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req))) + end ensure req.reset_parameters end @@ -50,37 +47,68 @@ module ActionDispatch end class Mapping #:nodoc: - IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix] + 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{\*([^/\)]+)\)?$} - def initialize(set, scope, path, options) - @set, @scope = set, scope - @segment_keys = nil - @options = (@scope[:options] || {}).merge(options) - @path = normalize_path(path) - normalize_options! + attr_reader :scope, :path, :options, :requirements, :conditions, :defaults - via_all = @options.delete(:via) if @options[:via] == :all + def initialize(set, scope, path, options) + @set, @scope, @path, @options = set, scope, path, options + @requirements, @conditions, @defaults = {}, {}, {} - if !via_all && request_method_condition.empty? - msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \ - "If you want to expose your action to GET, use `get` in the router:\n\n" \ - " Instead of: match \"controller#action\"\n" \ - " Do: get \"controller#action\"" - raise msg - end + normalize_path! + normalize_options! + normalize_requirements! + normalize_conditions! + normalize_defaults! end def to_route - [ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ] + [ app, conditions, requirements, defaults, options[:as], options[:anchor] ] end private + def normalize_path! + raise ArgumentError, "path is required" if @path.blank? + @path = Mapper.normalize_path(@path) + + if required_format? + @path = "#{@path}.:format" + elsif optional_format? + @path = "#{@path}(.:format)" + end + end + + def required_format? + options[:format] == true + end + + def optional_format? + options[:format] != false && !path.include?(':format') && !path.end_with?('/') + end + def normalize_options! - path_without_format = @path.sub(/\(\.:format\)$/, '') + @options.reverse_merge!(scope[:options]) if scope[:options] + path_without_format = path.sub(/\(\.:format\)$/, '') + + # Add a constraint for wildcard route to make it non-greedy and match the + # optional format part of the route by default + if path_without_format.match(WILDCARD_PATH) && @options[:format] != false + @options[$1.to_sym] ||= /.+?/ + end + + if path_without_format.match(':controller') + raise ArgumentError, ":controller segment is not allowed within a namespace block" if scope[:module] + + # Add a default constraint for :controller path segments that matches namespaced + # controllers with default routes like :controller/:action/:id(.:format), e.g: + # GET /admin/products/show/1 + # => { controller: 'admin/products', action: 'show', id: '1' } + @options[:controller] ||= /.+?/ + end if using_match_shorthand?(path_without_format, @options) to_shorthand = @options[:to].blank? @@ -88,85 +116,101 @@ module ActionDispatch end @options.merge!(default_controller_and_action(to_shorthand)) + end - requirements.each do |name, requirement| - # segment_keys.include?(k.to_s) || k == :controller - next unless Regexp === requirement && !constraints[name] + # match "account/overview" + def using_match_shorthand?(path, options) + path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX + end + + def normalize_format! + if options[:format] == true + options[:format] = /.+/ + elsif options[:format] == false + options.delete(:format) + end + end + + def normalize_requirements! + constraints.each do |key, requirement| + next unless segment_keys.include?(key) || key == :controller if requirement.source =~ ANCHOR_CHARACTERS_REGEX raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" end + if requirement.multiline? - raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" + raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}" end + + @requirements[key] = requirement end - if @options[:constraints].is_a?(Hash) - (@options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(@options[:constraints])) + if options[:format] == true + @requirements[:format] = /.+/ + elsif Regexp === options[:format] + @requirements[:format] = options[:format] + elsif String === options[:format] + @requirements[:format] = Regexp.compile(options[:format]) end end - # match "account/overview" - def using_match_shorthand?(path, options) - path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX - end + def normalize_defaults! + @defaults.merge!(scope[:defaults]) if scope[:defaults] + @defaults.merge!(options[:defaults]) if options[:defaults] - def normalize_path(path) - raise ArgumentError, "path is required" if path.blank? - path = Mapper.normalize_path(path) + options.each do |key, default| + next if Regexp === default || IGNORE_OPTIONS.include?(key) + @defaults[key] = default + end - if path.match(':controller') - raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module] + if options[:constraints].is_a?(Hash) + options[:constraints].each do |key, default| + next unless URL_OPTIONS.include?(key) && (String === default || Fixnum === default) + @defaults[key] ||= default + end + end - # Add a default constraint for :controller path segments that matches namespaced - # controllers with default routes like :controller/:action/:id(.:format), e.g: - # GET /admin/products/show/1 - # => { controller: 'admin/products', action: 'show', id: '1' } - @options[:controller] ||= /.+?/ + if Regexp === options[:format] + @defaults[:format] = nil + elsif String === options[:format] + @defaults[:format] = options[:format] end + end - # Add a constraint for wildcard route to make it non-greedy and match the - # optional format part of the route by default - if path.match(WILDCARD_PATH) && @options[:format] != false - @options[$1.to_sym] ||= /.+?/ + def normalize_conditions! + @conditions.merge!(:path_info => path) + + constraints.each do |key, condition| + next if segment_keys.include?(key) || key == :controller + @conditions[key] = condition end - if @options[:format] == false - @options.delete(:format) - path - elsif path.include?(":format") || path.end_with?('/') - path - elsif @options[:format] == true - "#{path}.:format" - else - "#{path}(.:format)" + @conditions[:required_defaults] = [] + options.each do |key, required_default| + next if segment_keys.include?(key) || IGNORE_OPTIONS.include?(key) + next if Regexp === required_default + @conditions[:required_defaults] << key end - end - def app - Constraints.new( - to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults), - blocks, - @set.request_class - ) - end + via_all = options.delete(:via) if options[:via] == :all - def conditions - { :path_info => @path }.merge!(constraints).merge!(request_method_condition) - end + if !via_all && options[:via].blank? + msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \ + "If you want to expose your action to GET, use `get` in the router:\n\n" \ + " Instead of: match \"controller#action\"\n" \ + " Do: get \"controller#action\"" + raise msg + end - def requirements - @requirements ||= (@options[:constraints].is_a?(Hash) ? @options[:constraints] : {}).tap do |requirements| - requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints] - @options.each { |k, v| requirements[k] ||= v if v.is_a?(Regexp) } + if via = options[:via] + list = Array(via).map { |m| m.to_s.dasherize.upcase } + @conditions.merge!(:request_method => list) end end - def defaults - @defaults ||= (@options[:defaults] || {}).tap do |defaults| - defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults] - @options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) } - end + def app + Constraints.new(endpoint, blocks, @set.request_class) end def default_controller_and_action(to_shorthand=nil) @@ -193,11 +237,11 @@ module ActionDispatch controller = controller.to_s unless controller.is_a?(Regexp) action = action.to_s unless action.is_a?(Regexp) - if controller.blank? && segment_keys.exclude?("controller") + if controller.blank? && segment_keys.exclude?(:controller) raise ArgumentError, "missing :controller" end - if action.blank? && segment_keys.exclude?("action") + if action.blank? && segment_keys.exclude?(:action) raise ArgumentError, "missing :action" end @@ -209,50 +253,55 @@ module ActionDispatch end def blocks - constraints = @options[:constraints] - if constraints.present? && !constraints.is_a?(Hash) - [constraints] + if options[:constraints].present? && !options[:constraints].is_a?(Hash) + [options[:constraints]] else - @scope[:blocks] || [] + scope[:blocks] || [] end end def constraints - @constraints ||= requirements.reject { |k, v| segment_keys.include?(k.to_s) || k == :controller } - end + @constraints ||= {}.tap do |constraints| + constraints.merge!(scope[:constraints]) if scope[:constraints] - def request_method_condition - if via = @options[:via] - list = Array(via).map { |m| m.to_s.dasherize.upcase } - { :request_method => list } - else - { } + options.except(*IGNORE_OPTIONS).each do |key, option| + constraints[key] = option if Regexp === option + end + + constraints.merge!(options[:constraints]) if options[:constraints].is_a?(Hash) end end def segment_keys - return @segment_keys if @segment_keys + @segment_keys ||= path_pattern.names.map{ |s| s.to_sym } + end + + def path_pattern + Journey::Path::Pattern.new(strexp) + end - @segment_keys = Journey::Path::Pattern.new( - Journey::Router::Strexp.compile(@path, requirements, SEPARATORS) - ).names + def strexp + Journey::Router::Strexp.compile(path, requirements, SEPARATORS) + end + + def endpoint + to.respond_to?(:call) ? to : dispatcher + end + + def dispatcher + Routing::RouteSet::Dispatcher.new(:defaults => defaults) end def to - @options[:to] + options[:to] end def default_controller - @options[:controller] || @scope[:controller] + options[:controller] || scope[:controller] end def default_action - @options[:action] || @scope[:action] - end - - def defaults_from_constraints(constraints) - url_keys = [:protocol, :subdomain, :domain, :host, :port] - constraints.select { |k, v| url_keys.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) } + options[:action] || scope[:action] end end @@ -646,7 +695,11 @@ module ActionDispatch options[:constraints] ||= {} if options[:constraints].is_a?(Hash) - (options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(options[:constraints])) + defaults = options[:constraints].select do + |k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) + end + + (options[:defaults] ||= {}).reverse_merge!(defaults) else block, options[:constraints] = options[:constraints], {} end @@ -851,11 +904,6 @@ module ActionDispatch def override_keys(child) #:nodoc: child.key?(:only) || child.key?(:except) ? [:only, :except] : [] end - - def defaults_from_constraints(constraints) - url_keys = [:protocol, :subdomain, :domain, :host, :port] - constraints.select { |k, v| url_keys.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) } - end end # Resource routing allows you to quickly declare all of the common routes diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index b1959e388c..c72310cca3 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -421,7 +421,7 @@ module ActionDispatch end conditions.keep_if do |k, _| - k == :action || k == :controller || + k == :action || k == :controller || k == :required_defaults || @request_class.public_method_defined?(k) || path_values.include?(k) end end @@ -527,12 +527,10 @@ module ActionDispatch recall[:action] = options.delete(:action) if options[:action] == 'index' end - # Generates a path from routes, returns [path, params] - # if no path is returned the formatter will raise Journey::Router::RoutingError + # Generates a path from routes, returns [path, params]. + # If no route is generated the formatter will raise ActionController::UrlGenerationError def generate @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE) - rescue Journey::Router::RoutingError => e - raise ActionController::UrlGenerationError, "No route matches #{options.inspect} #{e.message}" end def different_controller? diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 79dff7d121..9210bffd1d 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -1,5 +1,6 @@ require 'uri' require 'active_support/core_ext/hash/indifferent_access' +require 'active_support/core_ext/string/access' require 'action_controller/metal/exceptions' module ActionDispatch @@ -208,11 +209,9 @@ module ActionDispatch end def fail_on(exception_class) - begin - yield - rescue exception_class => e - raise MiniTest::Assertion, e.message - end + yield + rescue exception_class => e + raise MiniTest::Assertion, e.message end end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 1fc5933e98..ed4e88aab6 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -273,7 +273,7 @@ module ActionDispatch if path =~ %r{://} location = URI.parse(path) https! URI::HTTPS === location if location.scheme - host! location.host if location.host + host! "#{location.host}:#{location.port}" if location.host path = location.query ? "#{location.path}?#{location.query}" : location.path end diff --git a/actionpack/lib/action_dispatch/testing/performance_test.rb b/actionpack/lib/action_dispatch/testing/performance_test.rb deleted file mode 100644 index 13fe693c32..0000000000 --- a/actionpack/lib/action_dispatch/testing/performance_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'active_support/testing/performance' - -module ActionDispatch - # An integration test that runs a code profiler on your test methods. - # Profiling output for combinations of each test method, measurement, and - # output format are written to your tmp/performance directory. - class PerformanceTest < ActionDispatch::IntegrationTest - include ActiveSupport::Testing::Performance - end -end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 668515df59..08253de3f4 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -6,7 +6,7 @@ require 'action_view/log_subscriber' module ActionView #:nodoc: # = Action View Base # - # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERb + # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERB # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> extension then Jim Weirich's Builder::XmlMarkup library is used. # # == ERB diff --git a/actionpack/lib/action_view/digestor.rb b/actionpack/lib/action_view/digestor.rb index f3f6b425a8..4507861dcc 100644 --- a/actionpack/lib/action_view/digestor.rb +++ b/actionpack/lib/action_view/digestor.rb @@ -24,16 +24,17 @@ module ActionView @@cache = ThreadSafe::Cache.new def self.digest(name, format, finder, options = {}) - @@cache["#{name}.#{format}"] ||= begin + cache_key = [name, format] + Array.wrap(options[:dependencies]) + @@cache[cache_key.join('.')] ||= begin klass = options[:partial] || name.include?("/_") ? PartialDigestor : Digestor - klass.new(name, format, finder).digest + klass.new(name, format, finder, options).digest end end - attr_reader :name, :format, :finder + attr_reader :name, :format, :finder, :options - def initialize(name, format, finder) - @name, @format, @finder = name, format, finder + def initialize(name, format, finder, options={}) + @name, @format, @finder, @options = name, format, finder, options end def digest @@ -81,9 +82,11 @@ module ActionView end def dependency_digest - dependencies.collect do |template_name| + template_digests = dependencies.collect do |template_name| Digestor.digest(template_name, format, finder, partial: true) - end.join("-") + end + + (template_digests + injected_dependencies).join("-") end def render_dependencies @@ -105,6 +108,10 @@ module ActionView def explicit_dependencies source.scan(EXPLICIT_DEPENDENCY).flatten.uniq end + + def injected_dependencies + Array.wrap(options[:dependencies]) + end end class PartialDigestor < Digestor # :nodoc: diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index 269e78a021..8a78685ae1 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -1,3 +1,5 @@ +require 'active_support/benchmarkable' + module ActionView #:nodoc: module Helpers #:nodoc: extend ActiveSupport::Autoload @@ -6,7 +8,6 @@ module ActionView #:nodoc: autoload :AssetTagHelper autoload :AssetUrlHelper autoload :AtomFeedHelper - autoload :BenchmarkHelper autoload :CacheHelper autoload :CaptureHelper autoload :ControllerHelper @@ -29,11 +30,11 @@ module ActionView #:nodoc: extend ActiveSupport::Concern + include ActiveSupport::Benchmarkable include ActiveModelHelper include AssetTagHelper include AssetUrlHelper include AtomFeedHelper - include BenchmarkHelper include CacheHelper include CaptureHelper include ControllerHelper diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb deleted file mode 100644 index 87fbf8f1a8..0000000000 --- a/actionpack/lib/action_view/helpers/benchmark_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'active_support/benchmarkable' - -module ActionView - module Helpers - module BenchmarkHelper #:nodoc: - include ActiveSupport::Benchmarkable - - def benchmark(*) - capture { super } - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 995aa10afb..8fc78ea7fb 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -167,7 +167,7 @@ module ActionView if @virtual_path [ *Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name), - Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context) + Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context, dependencies: view_cache_dependencies) ] else name diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 1fbf61a5a9..10748aacf4 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -193,6 +193,7 @@ module ActionView # * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty # dates. # * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil. + # * <tt>:selected</tt> - Set a date that overrides the actual value. # * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled. # * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings # for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>. @@ -234,6 +235,10 @@ module ActionView # # which is initially set to the date 3 days from the current date # date_select("article", "written_on", default: 3.days.from_now) # + # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute + # # which is set in the form with todays date, regardless of the value in the Active Record object. + # date_select("article", "written_on", selected: Date.today) + # # # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute # # that will have a default day of 20. # date_select("credit_card", "bill_due", default: { day: 20 }) @@ -875,6 +880,7 @@ module ActionView def translated_date_order date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => []) + date_order = date_order.map { |element| element.to_sym } forbidden_elements = date_order - [:year, :month, :day] if forbidden_elements.any? diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index d361a69a92..34fc23ac1a 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -27,14 +27,12 @@ module ActionView # new_record: true # </pre> def debug(object) - begin - Marshal::dump(object) - object = ERB::Util.html_escape(object.to_yaml).gsub(" ", " ").html_safe - 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") - end + Marshal::dump(object) + object = ERB::Util.html_escape(object.to_yaml).gsub(" ", " ").html_safe + 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") end end end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 516492ca30..4a31de715d 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -84,7 +84,7 @@ module ActionView # # <form action="/people/256" class="edit_person" id="edit_person_256" method="post"> # <div style="margin:0;padding:0;display:inline"> - # <input name="_method" type="hidden" value="put" /> + # <input name="_method" type="hidden" value="patch" /> # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> # </div> # <label for="person_first_name">First name</label>: @@ -101,9 +101,9 @@ module ActionView # and generate HTML accordingly. # # The controller would receive the form data again in <tt>params[:person]</tt>, ready to be - # passed to <tt>Person#update_attributes</tt>: + # passed to <tt>Person#update</tt>: # - # if @person.update_attributes(params[:person]) + # if @person.update(params[:person]) # # success # else # # error handling @@ -242,7 +242,7 @@ module ActionView # # is then equivalent to something like: # - # <%= form_for @post, as: :post, url: post_path(@post), method: :put, html: { class: "edit_post", id: "edit_post_45" } do |f| %> + # <%= form_for @post, as: :post, url: post_path(@post), method: :patch, html: { class: "edit_post", id: "edit_post_45" } do |f| %> # ... # <% end %> # @@ -318,7 +318,7 @@ module ActionView # # <form action='http://www.example.com' method='post' data-remote='true'> # <div style='margin:0;padding:0;display:inline'> - # <input name='_method' type='hidden' value='put' /> + # <input name='_method' type='hidden' value='patch' /> # </div> # ... # </form> @@ -336,7 +336,7 @@ module ActionView # # <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'> # <div style='margin:0;padding:0;display:inline'> - # <input name='_method' type='hidden' value='put' /> + # <input name='_method' type='hidden' value='patch' /> # </div> # ... # </form> @@ -388,9 +388,9 @@ module ActionView # In many cases you will want to wrap the above in another helper, so you # could do something like the following: # - # def labelled_form_for(record_or_name_or_array, *args, &proc) + # def labelled_form_for(record_or_name_or_array, *args, &block) # options = args.extract_options! - # form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &proc) + # form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &block) # end # # If you don't need to attach a form to a model instance, then check out @@ -412,10 +412,9 @@ module ActionView # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| # ... # <% end %> - def form_for(record, options = {}, &proc) + def form_for(record, options = {}, &block) raise ArgumentError, "Missing block" unless block_given? - - options[:html] ||= {} + html_options = options[:html] ||= {} case record when String, Symbol @@ -428,17 +427,16 @@ module ActionView apply_form_for_options!(record, object, options) end - options[:html][:data] = options.delete(:data) if options.has_key?(:data) - options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote) - options[:html][:method] = options.delete(:method) if options.has_key?(:method) - options[:html][:authenticity_token] = options.delete(:authenticity_token) + html_options[:data] = options.delete(:data) if options.has_key?(:data) + html_options[:remote] = options.delete(:remote) if options.has_key?(:remote) + html_options[:method] = options.delete(:method) if options.has_key?(:method) + html_options[:authenticity_token] = options.delete(:authenticity_token) - builder = options[:parent_builder] = instantiate_builder(object_name, object, options) - fields_for = fields_for(object_name, object, options, &proc) - default_options = builder.multipart? ? { multipart: true } : {} - default_options.merge!(options.delete(:html)) + builder = instantiate_builder(object_name, object, options) + output = capture(builder, &block) + html_options[:multipart] = builder.multipart? - form_tag(options.delete(:url) || {}, default_options) { fields_for } + form_tag(options[:url] || {}, html_options) { output } end def apply_form_for_options!(record, object, options) #:nodoc: @@ -707,9 +705,7 @@ module ActionView # to prevent fields_for from rendering it automatically. def fields_for(record_name, record_object = nil, options = {}, &block) builder = instantiate_builder(record_name, record_object, options) - output = capture(builder, &block) - output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id? - output + capture(builder, &block) end # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object @@ -897,7 +893,7 @@ module ActionView # invoice the user unchecks its check box, no +paid+ parameter is sent. So, # any mass-assignment idiom like # - # @invoice.update_attributes(params[:invoice]) + # @invoice.update(params[:invoice]) # # wouldn't update the flag. # @@ -1174,12 +1170,15 @@ module ActionView attr_accessor :object_name, :object, :options - attr_reader :multipart, :parent_builder, :index + attr_reader :multipart, :index alias :multipart? :multipart def multipart=(multipart) @multipart = multipart - parent_builder.multipart = multipart if parent_builder + + if parent_builder = @options[:parent_builder] + parent_builder.multipart = multipart + end end def self._to_partial_path @@ -1201,7 +1200,6 @@ module ActionView @nested_child_index = {} @object_name, @object, @template, @options = object_name, object, template, options - @parent_builder = options[:parent_builder] @default_options = @options ? @options.slice(:index, :namespace) : {} if @object_name.to_s.match(/\[\]$/) if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param) @@ -1478,8 +1476,8 @@ module ActionView def fields_for(record_name, record_object = nil, fields_options = {}, &block) fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options? fields_options[:builder] ||= options[:builder] - fields_options[:parent_builder] = self fields_options[:namespace] = options[:namespace] + fields_options[:parent_builder] = self case record_name when String, Symbol @@ -1569,7 +1567,7 @@ module ActionView # invoice the user unchecks its check box, no +paid+ parameter is sent. So, # any mass-assignment idiom like # - # @invoice.update_attributes(params[:invoice]) + # @invoice.update(params[:invoice]) # # wouldn't update the flag. # @@ -1802,7 +1800,7 @@ module ActionView association = convert_to_model(association) if association.respond_to?(:persisted?) - association = [association] if @object.send(association_name).is_a?(Array) + association = [association] if @object.send(association_name).respond_to?(:to_ary) elsif !association.respond_to?(:to_ary) association = @object.send(association_name) end @@ -1820,13 +1818,17 @@ module ActionView end end - def fields_for_nested_model(name, object, options, block) + def fields_for_nested_model(name, object, fields_options, block) object = convert_to_model(object) + emit_hidden_id = object.persisted? && fields_options.fetch(:include_id) { + options.fetch(:include_id, true) + } - parent_include_id = self.options.fetch(:include_id, true) - include_id = options.fetch(:include_id, parent_include_id) - options[:hidden_field_id] = object.persisted? && include_id - @template.fields_for(name, object, options, &block) + @template.fields_for(name, object, fields_options) do |f| + output = @template.capture(f, &block) + output.concat f.hidden_field(:id) if output && emit_hidden_id && !f.emitted_hidden_id? + output + end end def nested_child_index(name) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 1b5b788a35..ae7bfd1ec6 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -130,7 +130,7 @@ module ActionView # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So, # any mass-assignment idiom like # - # @user.update_attributes(params[:user]) + # @user.update(params[:user]) # # wouldn't update roles. # @@ -772,12 +772,12 @@ module ActionView @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options)) end - def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}) - @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) + def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block) + @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) end - def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}) - @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) + def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block) + @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block) end end end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index ff83ef3ca1..86d9f94067 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -26,7 +26,7 @@ module ActionView # ==== Options # * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data". # * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post". - # If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt> + # If "patch", "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt> # is added to simulate the verb over post. # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to # pass custom authenticity token string, or to not add authenticity_token field at all @@ -184,7 +184,7 @@ module ActionView # # => <label for="name">Name</label> # # label_tag 'name', 'Your name' - # # => <label for="name">Your Name</label> + # # => <label for="name">Your name</label> # # label_tag 'name', nil, class: 'small_label' # # => <label for="name" class="small_label">Name</label> @@ -526,19 +526,19 @@ module ActionView # # ==== Examples # image_submit_tag("login.png") - # # => <input src="/images/login.png" type="image" /> + # # => <input alt="Login" src="/images/login.png" type="image" /> # # image_submit_tag("purchase.png", disabled: true) - # # => <input disabled="disabled" src="/images/purchase.png" type="image" /> + # # => <input alt="Purchase" disabled="disabled" src="/images/purchase.png" type="image" /> # - # image_submit_tag("search.png", class: 'search_button') - # # => <input class="search_button" src="/images/search.png" type="image" /> + # image_submit_tag("search.png", class: 'search_button', alt: 'Find') + # # => <input alt="Find" class="search_button" src="/images/search.png" type="image" /> # # image_submit_tag("agree.png", disabled: true, class: "agree_disagree_button") - # # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" /> + # # => <input alt="Agree" class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" /> # # image_submit_tag("save.png", data: { confirm: "Are you sure?" }) - # # => <input src="/images/save.png" data-confirm="Are you sure?" type="image" /> + # # => <input alt="Save" src="/images/save.png" data-confirm="Are you sure?" type="image" /> def image_submit_tag(source, options = {}) options = options.stringify_keys @@ -550,7 +550,7 @@ module ActionView options["data-confirm"] = confirm end - tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options) + tag :input, { "alt" => image_alt(source), "type" => "image", "src" => path_to_image(source) }.update(options) end # Creates a field set for grouping HTML form elements. diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 33194250b7..271a194913 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -65,8 +65,8 @@ module ActionView # # produces: # - # <tr id="person_123" class="person">...</tr> - # <tr id="person_124" class="person">...</tr> + # <tr id="person_123" class="person">...</tr> + # <tr id="person_124" class="person">...</tr> # # content_tag_for also accepts a hash of options, which will be converted to # additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined @@ -95,7 +95,11 @@ module ActionView options[:class] = "#{dom_class(record, prefix)} #{options[:class]}".rstrip options[:id] = dom_id(record, prefix) - content_tag(tag_name, capture(record, &block), options) + if block_given? + content_tag(tag_name, capture(record, &block), options) + else + content_tag(tag_name, "", options) + end end end end diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb index 6c400f85cb..734591394b 100644 --- a/actionpack/lib/action_view/helpers/tags/date_select.rb +++ b/actionpack/lib/action_view/helpers/tags/date_select.rb @@ -27,7 +27,7 @@ module ActionView end def datetime_selector(options, html_options) - datetime = value(object) || default_datetime(options) + datetime = options.fetch(:selected) { value(object) || default_datetime(options) } @auto_index ||= nil options = options.dup diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index aeee662071..bade121d44 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -170,7 +170,7 @@ module ActionView # You can also use custom data attributes using the <tt>:data</tt> option: # # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" } - # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?"">Visit Other Site</a> + # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a> def link_to(name = nil, options = nil, html_options = nil, &block) html_options, options = options, name if block_given? options ||= {} diff --git a/actionpack/lib/action_view/record_identifier.rb b/actionpack/lib/action_view/record_identifier.rb index 2953654972..63f645431a 100644 --- a/actionpack/lib/action_view/record_identifier.rb +++ b/actionpack/lib/action_view/record_identifier.rb @@ -17,7 +17,7 @@ module ActionView # # controller # def update # post = Post.find(params[:id]) - # post.update_attributes(params[:post]) + # post.update(params[:post]) # # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post) # end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 37f93a13fc..43a88b0623 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -452,7 +452,7 @@ module ActionView def retrieve_template_keys keys = @locals.keys - keys << @variable + keys << @variable if @object || @collection keys << @variable_counter if @collection keys end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index aefc42be48..b927c69260 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/object/try' require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/deprecation' require 'thread' module ActionView diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 8b23029bbc..1a1083bf00 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -118,7 +118,7 @@ module ActionView private - delegate :caching?, :to => "self.class" + delegate :caching?, to: :class # This is what child classes implement. No defaults are needed # because Resolver guarantees that the arguments are present and diff --git a/actionpack/lib/action_view/template/types.rb b/actionpack/lib/action_view/template/types.rb index 7611c9e708..db77cb5d19 100644 --- a/actionpack/lib/action_view/template/types.rb +++ b/actionpack/lib/action_view/template/types.rb @@ -1,6 +1,5 @@ require 'set' require 'active_support/core_ext/class/attribute_accessors' -require 'active_support/core_ext/object/blank' module ActionView class Template diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb index 62f82a4c7a..eb9143c8f6 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionpack/test/abstract/abstract_controller_test.rb @@ -157,13 +157,11 @@ module AbstractController private def self.layout(formats) + find_template(name.underscore, {:formats => formats}, :_prefixes => ["layouts"]) + rescue ActionView::MissingTemplate begin - find_template(name.underscore, {:formats => formats}, :_prefixes => ["layouts"]) + find_template("application", {:formats => formats}, :_prefixes => ["layouts"]) rescue ActionView::MissingTemplate - begin - find_template("application", {:formats => formats}, :_prefixes => ["layouts"]) - rescue ActionView::MissingTemplate - end end end diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb index e79008fa9d..7960e5b55b 100644 --- a/actionpack/test/abstract/helper_test.rb +++ b/actionpack/test/abstract/helper_test.rb @@ -69,12 +69,10 @@ module AbstractController end def test_declare_missing_helper - begin - AbstractHelpers.helper :missing - flunk "should have raised an exception" - rescue LoadError => e - assert_equal "helpers/missing_helper.rb", e.path - end + AbstractHelpers.helper :missing + flunk "should have raised an exception" + rescue LoadError => e + assert_equal "helpers/missing_helper.rb", e.path end def test_helpers_with_module_through_block diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb index 99064a8b87..4fdc480b43 100644 --- a/actionpack/test/abstract/translation_test.rb +++ b/actionpack/test/abstract/translation_test.rb @@ -1,39 +1,50 @@ require 'abstract_unit' -# class TranslatingController < ActionController::Base -# end - -class TranslationControllerTest < ActiveSupport::TestCase - def setup - @controller = ActionController::Base.new - end - - def test_action_controller_base_responds_to_translate - assert_respond_to @controller, :translate - end - - def test_action_controller_base_responds_to_t - assert_respond_to @controller, :t - end - - def test_action_controller_base_responds_to_localize - assert_respond_to @controller, :localize - end - - def test_action_controller_base_responds_to_l - assert_respond_to @controller, :l - end - - def test_lazy_lookup - expected = 'bar' - @controller.stubs(:action_name => :index) - I18n.stubs(:translate).with('action_controller.base.index.foo').returns(expected) - assert_equal expected, @controller.t('.foo') - end - - def test_default_translation - key, expected = 'one.two' 'bar' - I18n.stubs(:translate).with(key).returns(expected) - assert_equal expected, @controller.t(key) +module AbstractController + module Testing + class TranslationController < AbstractController::Base + include AbstractController::Translation + end + + class TranslationControllerTest < ActiveSupport::TestCase + def setup + @controller = TranslationController.new + end + + def test_action_controller_base_responds_to_translate + assert_respond_to @controller, :translate + end + + def test_action_controller_base_responds_to_t + assert_respond_to @controller, :t + end + + def test_action_controller_base_responds_to_localize + assert_respond_to @controller, :localize + end + + def test_action_controller_base_responds_to_l + assert_respond_to @controller, :l + end + + def test_lazy_lookup + expected = 'bar' + @controller.stubs(action_name: :index) + I18n.stubs(:translate).with('abstract_controller.testing.translation.index.foo').returns(expected) + assert_equal expected, @controller.t('.foo') + end + + def test_default_translation + key, expected = 'one.two', 'bar' + I18n.stubs(:translate).with(key).returns(expected) + assert_equal expected, @controller.t(key) + end + + def test_localize + time, expected = Time.gm(2000), 'Sat, 01 Jan 2000 00:00:00 +0000' + I18n.stubs(:localize).with(time).returns(expected) + assert_equal expected, @controller.l(time) + end + end end end diff --git a/actionpack/test/activerecord/form_helper_activerecord_test.rb b/actionpack/test/activerecord/form_helper_activerecord_test.rb new file mode 100644 index 0000000000..2e302c65a7 --- /dev/null +++ b/actionpack/test/activerecord/form_helper_activerecord_test.rb @@ -0,0 +1,91 @@ +require 'active_record_unit' +require 'fixtures/project' +require 'fixtures/developer' + +class FormHelperActiveRecordTest < ActionView::TestCase + tests ActionView::Helpers::FormHelper + + def form_for(*) + @output_buffer = super + end + + def setup + @developer = Developer.new + @developer.id = 123 + @developer.name = "developer #123" + + @project = Project.new + @project.id = 321 + @project.name = "project #321" + @project.save + + @developer.projects << @project + @developer.save + end + + def teardown + Project.delete(321) + Developer.delete(123) + end + + Routes = ActionDispatch::Routing::RouteSet.new + Routes.draw do + resources :developers do + resources :projects + end + end + + def _routes + Routes + end + + include Routes.url_helpers + + def test_nested_fields_for_with_child_index_option_override_on_a_nested_attributes_collection_association + form_for(@developer) do |f| + concat f.fields_for(:projects, @developer.projects.first, :child_index => 'abc') { |cf| + concat cf.text_field(:name) + } + end + + expected = whole_form('/developers/123', 'edit_developer_123', 'edit_developer', :method => 'patch') do + '<input id="developer_projects_attributes_abc_name" name="developer[projects_attributes][abc][name]" type="text" value="project #321" />' + + '<input id="developer_projects_attributes_abc_id" name="developer[projects_attributes][abc][id]" type="hidden" value="321" />' + end + + assert_dom_equal expected, output_buffer + end + + protected + + def hidden_fields(method = nil) + txt = %{<div style="margin:0;padding:0;display:inline">} + txt << %{<input name="utf8" type="hidden" value="✓" />} + if method && !%w(get post).include?(method.to_s) + txt << %{<input name="_method" type="hidden" value="#{method}" />} + end + txt << %{</div>} + end + + def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) + txt = %{<form accept-charset="UTF-8" action="#{action}"} + txt << %{ enctype="multipart/form-data"} if multipart + txt << %{ data-remote="true"} if remote + txt << %{ class="#{html_class}"} if html_class + txt << %{ id="#{id}"} if id + method = method.to_s == "get" ? "get" : "post" + txt << %{ method="#{method}">} + end + + def whole_form(action = "/", id = nil, html_class = nil, options = nil) + contents = block_given? ? yield : "" + + if options.is_a?(Hash) + method, remote, multipart = options.values_at(:method, :remote, :multipart) + else + method = options + end + + form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>" + end +end
\ No newline at end of file diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index b94f45bfe7..5d727b3811 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -259,7 +259,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_flash_exist process :flash_me assert flash.any? - assert_present flash['hello'] + assert flash['hello'].present? end def test_flash_does_not_exist diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 2428cd7433..ca86837a2c 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -296,6 +296,24 @@ class CacheHelperOutputBufferTest < ActionController::TestCase end end +class ViewCacheDependencyTest < ActionController::TestCase + class NoDependenciesController < ActionController::Base + end + + class HasDependenciesController < ActionController::Base + view_cache_dependency { "trombone" } + view_cache_dependency { "flute" } + end + + def test_view_cache_dependencies_are_empty_by_default + assert NoDependenciesController.new.view_cache_dependencies.empty? + end + + def test_view_cache_dependencies_are_listed_in_declaration_order + assert_equal %w(trombone flute), HasDependenciesController.new.view_cache_dependencies + end +end + class DeprecatedPageCacheExtensionTest < ActiveSupport::TestCase def test_page_cache_extension_binds_default_static_extension deprecation_behavior = ActiveSupport::Deprecation.behavior diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 1c59dd5953..3b79161ad3 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -450,11 +450,9 @@ class FilterTest < ActionController::TestCase class RescuingAroundFilterWithBlock def around(controller) - begin - yield - rescue ErrorToRescue => ex - controller.__send__ :render, :text => "I rescued this: #{ex.inspect}" - end + yield + rescue ErrorToRescue => ex + controller.__send__ :render, :text => "I rescued this: #{ex.inspect}" end end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index cf561d913a..e2239c05c7 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -466,6 +466,58 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest assert_equal 'http://www.example.com/foo', url_for(:controller => "foo") end + def test_port_via_host! + with_test_route_set do + host! 'www.example.com:8080' + get '/get' + assert_equal 8080, request.port + end + end + + def test_port_via_process + with_test_route_set do + get 'http://www.example.com:8080/get' + assert_equal 8080, request.port + end + end + + def test_https_and_port_via_host_and_https! + with_test_route_set do + host! 'www.example.com' + https! true + + get '/get' + assert_equal 443, request.port + assert_equal true, request.ssl? + + host! 'www.example.com:443' + https! true + + get '/get' + assert_equal 443, request.port + assert_equal true, request.ssl? + + host! 'www.example.com:8443' + https! true + + get '/get' + assert_equal 8443, request.port + assert_equal true, request.ssl? + end + end + + def test_https_and_port_via_process + with_test_route_set do + get 'https://www.example.com/get' + assert_equal 443, request.port + assert_equal true, request.ssl? + + get 'https://www.example.com:8443/get' + assert_equal 8443, request.port + assert_equal true, request.ssl? + end + end + private def with_test_route_set with_routing do |set| diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 94a8d2f180..365fa04570 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -80,7 +80,7 @@ end class StreamingLayoutController < LayoutTest def render(*args) - options = args.extract_options! || {} + options = args.extract_options! super(*args, options.merge(:stream => true)) end end diff --git a/actionpack/test/controller/output_escaping_test.rb b/actionpack/test/controller/output_escaping_test.rb index f6913a2138..43a8c05cda 100644 --- a/actionpack/test/controller/output_escaping_test.rb +++ b/actionpack/test/controller/output_escaping_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' class OutputEscapingTest < ActiveSupport::TestCase test "escape_html shouldn't die when passed nil" do - assert_blank ERB::Util.h(nil) + assert ERB::Util.h(nil).blank? end test "escapeHTML should escape strings" do diff --git a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb new file mode 100644 index 0000000000..22e603b881 --- /dev/null +++ b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb @@ -0,0 +1,50 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' + +class LogOnUnpermittedParamsTest < ActiveSupport::TestCase + def setup + ActionController::Parameters.action_on_unpermitted_parameters = :log + end + + def teardown + ActionController::Parameters.action_on_unpermitted_parameters = false + end + + test "logs on unexpected params" do + params = ActionController::Parameters.new({ + book: { pages: 65 }, + fishing: "Turnips" + }) + + assert_logged("Unpermitted parameters: fishing") do + params.permit(book: [:pages]) + end + end + + test "logs on unexpected nested params" do + params = ActionController::Parameters.new({ + book: { pages: 65, title: "Green Cats and where to find then." } + }) + + assert_logged("Unpermitted parameters: title") do + params.permit(book: [:pages]) + end + end + + private + + def assert_logged(message) + old_logger = ActionController::Base.logger + log = StringIO.new + ActionController::Base.logger = Logger.new(log) + + begin + yield + + log.rewind + assert_match message, log.read + ensure + ActionController::Base.logger = old_logger + end + end +end diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_test.rb index 6df849c4e2..8aec159499 100644 --- a/actionpack/test/controller/parameters/nested_parameters_test.rb +++ b/actionpack/test/controller/parameters/nested_parameters_test.rb @@ -2,6 +2,10 @@ require 'abstract_unit' require 'action_controller/metal/strong_parameters' class NestedParametersTest < ActiveSupport::TestCase + def assert_filtered_out(params, key) + assert !params.has_key?(key), "key #{key.inspect} has not been filtered out" + end + test "permitted nested parameters" do params = ActionController::Parameters.new({ book: { @@ -11,6 +15,8 @@ class NestedParametersTest < ActiveSupport::TestCase born: "1564-04-26" }, { name: "Christopher Marlowe" + }, { + :name => %w(malicious injected names) }], details: { pages: 200, @@ -30,10 +36,12 @@ class NestedParametersTest < ActiveSupport::TestCase assert_equal "William Shakespeare", permitted[:book][:authors][0][:name] assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name] assert_equal 200, permitted[:book][:details][:pages] - assert_nil permitted[:book][:id] - assert_nil permitted[:book][:details][:genre] - assert_nil permitted[:book][:authors][0][:born] - assert_nil permitted[:magazine] + + assert_filtered_out permitted, :magazine + assert_filtered_out permitted[:book], :id + assert_filtered_out permitted[:book][:details], :genre + assert_filtered_out permitted[:book][:authors][0], :born + assert_filtered_out permitted[:book][:authors][2], :name end test "permitted nested parameters with a string or a symbol as a key" do @@ -68,7 +76,7 @@ class NestedParametersTest < ActiveSupport::TestCase } }) - permitted = params.permit :book => :genres + permitted = params.permit :book => {:genres => []} assert_equal ["Tragedy"], permitted[:book][:genres] end @@ -124,19 +132,41 @@ class NestedParametersTest < ActiveSupport::TestCase test "fields_for-style nested params" do params = ActionController::Parameters.new({ - book: { - authors_attributes: { - :'0' => { name: 'William Shakespeare', age_of_death: '52' }, - :'-1' => { name: 'Unattributed Assistant' } + :book => { + :authors_attributes => { + :'0' => { :name => 'William Shakespeare', :age_of_death => '52' }, + :'1' => { :name => 'Unattributed Assistant' }, + :'2' => { :name => %w(injected names)} } } }) - permitted = params.permit book: { authors_attributes: [ :name ] } + permitted = params.permit :book => { :authors_attributes => [ :name ] } assert_not_nil permitted[:book][:authors_attributes]['0'] - assert_not_nil permitted[:book][:authors_attributes]['-1'] - assert_nil permitted[:book][:authors_attributes]['0'][:age_of_death] + assert_not_nil permitted[:book][:authors_attributes]['1'] + assert_empty permitted[:book][:authors_attributes]['2'] assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['0'][:name] - assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-1'][:name] + assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['1'][:name] + + assert_filtered_out permitted[:book][:authors_attributes]['0'], :age_of_death + end + + test "fields_for-style nested params with negative numbers" do + params = ActionController::Parameters.new({ + :book => { + :authors_attributes => { + :'-1' => { :name => 'William Shakespeare', :age_of_death => '52' }, + :'-2' => { :name => 'Unattributed Assistant' } + } + } + }) + permitted = params.permit :book => { :authors_attributes => [:name] } + + assert_not_nil permitted[:book][:authors_attributes]['-1'] + assert_not_nil permitted[:book][:authors_attributes]['-2'] + assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['-1'][:name] + assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-2'][:name] + + assert_filtered_out permitted[:book][:authors_attributes]['-1'], :age_of_death end end diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 7cc71fe6dc..303c13eb25 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -2,10 +2,131 @@ require 'abstract_unit' require 'action_controller/metal/strong_parameters' class ParametersPermitTest < ActiveSupport::TestCase + def assert_filtered_out(params, key) + assert !params.has_key?(key), "key #{key.inspect} has not been filtered out" + end + setup do @params = ActionController::Parameters.new({ person: { age: "32", name: { first: "David", last: "Heinemeier Hansson" } }}) + + @struct_fields = [] + %w(0 1 12).each do |number| + ['', 'i', 'f'].each do |suffix| + @struct_fields << "sf(#{number}#{suffix})" + end + end + end + + test 'if nothing is permitted, the hash becomes empty' do + params = ActionController::Parameters.new(:id => '1234') + permitted = params.permit + assert permitted.permitted? + assert permitted.empty? + end + + test 'key: permitted scalar values' do + values = ['a', :a, nil] + values += [0, 1.0, 2**128, BigDecimal.new(1)] + values += [true, false] + values += [Date.today, Time.now, DateTime.now] + values += [StringIO.new] + + values.each do |value| + params = ActionController::Parameters.new(:id => value) + permitted = params.permit(:id) + assert_equal value, permitted[:id] + + @struct_fields.each do |sf| + params = ActionController::Parameters.new(sf => value) + permitted = params.permit(:sf) + assert_equal value, permitted[sf] + end + end + end + + test 'key: unknown keys are filtered out' do + params = ActionController::Parameters.new(:id => '1234', :injected => 'injected') + permitted = params.permit(:id) + assert_equal '1234', permitted[:id] + assert_filtered_out permitted, :injected + end + + test 'key: arrays are filtered out' do + [[], [1], ['1']].each do |array| + params = ActionController::Parameters.new(:id => array) + permitted = params.permit(:id) + assert_filtered_out permitted, :id + + @struct_fields.each do |sf| + params = ActionController::Parameters.new(sf => array) + permitted = params.permit(:sf) + assert_filtered_out permitted, sf + end + end + end + + test 'key: hashes are filtered out' do + [{}, {:foo => 1}, {:foo => 'bar'}].each do |hash| + params = ActionController::Parameters.new(:id => hash) + permitted = params.permit(:id) + assert_filtered_out permitted, :id + + @struct_fields.each do |sf| + params = ActionController::Parameters.new(sf => hash) + permitted = params.permit(:sf) + assert_filtered_out permitted, sf + end + end + end + + test 'key: non-permitted scalar values are filtered out' do + params = ActionController::Parameters.new(:id => Object.new) + permitted = params.permit(:id) + assert_filtered_out permitted, :id + + @struct_fields.each do |sf| + params = ActionController::Parameters.new(sf => Object.new) + permitted = params.permit(:sf) + assert_filtered_out permitted, sf + end + end + + test 'key: it is not assigned if not present in params' do + params = ActionController::Parameters.new(:name => 'Joe') + permitted = params.permit(:id) + assert !permitted.has_key?(:id) + end + + test 'key to empty array: empty arrays pass' do + params = ActionController::Parameters.new(:id => []) + permitted = params.permit(:id => []) + assert_equal [], permitted[:id] + end + + test 'key to empty array: arrays of permitted scalars pass' do + [['foo'], [1], ['foo', 'bar'], [1, 2, 3]].each do |array| + params = ActionController::Parameters.new(:id => array) + permitted = params.permit(:id => []) + assert_equal array, permitted[:id] + end + end + + test 'key to empty array: permitted scalar values do not pass' do + ['foo', 1].each do |permitted_scalar| + params = ActionController::Parameters.new(:id => permitted_scalar) + permitted = params.permit(:id => []) + assert_filtered_out permitted, :id + end + end + + test 'key to empty array: arrays of non-permitted scalar do not pass' do + [[Object.new], [[]], [[1]], [{}], [{:id => '1'}]].each do |non_permitted_scalar| + params = ActionController::Parameters.new(:id => non_permitted_scalar) + permitted = params.permit(:id => []) + assert_filtered_out permitted, :id + end end test "fetch raises ParameterMissing exception" do @@ -73,10 +194,6 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal "Jonas", @params[:person][:family][:brother] end - test "permitting parameters that are not there should not include the keys" do - assert !@params.permit(:person, :funky).has_key?(:funky) - end - test "permit state is kept on a dup" do @params.permit! assert_equal @params.permitted?, @params.dup.permitted? diff --git a/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb new file mode 100644 index 0000000000..f9cc9f96f1 --- /dev/null +++ b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb @@ -0,0 +1,33 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' + +class RaiseOnUnpermittedParamsTest < ActiveSupport::TestCase + def setup + ActionController::Parameters.action_on_unpermitted_parameters = :raise + end + + def teardown + ActionController::Parameters.action_on_unpermitted_parameters = false + end + + test "raises on unexpected params" do + params = ActionController::Parameters.new({ + book: { pages: 65 }, + fishing: "Turnips" + }) + + assert_raises(ActionController::UnpermittedParameters) do + params.permit(book: [:pages]) + end + end + + test "raises on unexpected nested params" do + params = ActionController::Parameters.new({ + book: { pages: 65, title: "Green Cats and where to find then." } + }) + + assert_raises(ActionController::UnpermittedParameters) do + params.permit(book: [:pages]) + end + end +end diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb new file mode 100644 index 0000000000..ff5d7fd3bd --- /dev/null +++ b/actionpack/test/controller/record_identifier_test.rb @@ -0,0 +1,34 @@ +require 'abstract_unit' +require 'controller/fake_models' + +class ControllerRecordIdentifierTest < ActiveSupport::TestCase + include ActionController::RecordIdentifier + + def setup + @record = Comment.new + end + + def test_dom_id_deprecation + assert_deprecated(/dom_id method will no longer be included by default in controllers/) do + dom_id(@record) + end + end + + def test_dom_class_deprecation + assert_deprecated(/dom_class method will no longer be included by default in controllers/) do + dom_class(@record) + end + end + + def test_dom_id_from_module_deprecation + assert_deprecated(/Calling ActionController::RecordIdentifier.dom_id is deprecated/) do + ActionController::RecordIdentifier.dom_id(@record) + end + end + + def test_dom_class_from_module_deprecation + assert_deprecated(/Calling ActionController::RecordIdentifier.dom_class is deprecated/) do + ActionController::RecordIdentifier.dom_class(@record) + end + end +end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index c7a66f4298..0e5bad7482 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -793,15 +793,13 @@ class RenderTest < ActionController::TestCase end def test_line_offset - begin - get :render_line_offset - flunk "the action should have raised an exception" - rescue StandardError => exc - line = exc.backtrace.first - assert(line =~ %r{:(\d+):}) - assert_equal "1", $1, - "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}" - end + get :render_line_offset + flunk "the action should have raised an exception" + rescue StandardError => exc + line = exc.backtrace.first + assert(line =~ %r{:(\d+):}) + assert_equal "1", $1, + "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}" end # :ported: compatibility @@ -1221,27 +1219,27 @@ class RenderTest < ActionController::TestCase def test_head_created post :head_created - assert_blank @response.body + assert @response.body.blank? assert_response :created end def test_head_created_with_application_json_content_type post :head_created_with_application_json_content_type - assert_blank @response.body + assert @response.body.blank? assert_equal "application/json", @response.header["Content-Type"] assert_response :created end def test_head_ok_with_image_png_content_type post :head_ok_with_image_png_content_type - assert_blank @response.body + assert @response.body.blank? assert_equal "image/png", @response.header["Content-Type"] assert_response :ok end def test_head_with_location_header get :head_with_location_header - assert_blank @response.body + assert @response.body.blank? assert_equal "/foo", @response.headers["Location"] assert_response :ok end @@ -1254,7 +1252,7 @@ class RenderTest < ActionController::TestCase end get :head_with_location_object - assert_blank @response.body + assert @response.body.blank? assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"] assert_response :ok end @@ -1262,14 +1260,14 @@ class RenderTest < ActionController::TestCase def test_head_with_custom_header get :head_with_custom_header - assert_blank @response.body + assert @response.body.blank? assert_equal "something", @response.headers["X-Custom-Header"] assert_response :ok end def test_head_with_www_authenticate_header get :head_with_www_authenticate_header - assert_blank @response.body + assert @response.body.blank? assert_equal "something", @response.headers["WWW-Authenticate"] assert_response :ok end @@ -1603,7 +1601,7 @@ class LastModifiedRenderTest < ActionController::TestCase @request.if_modified_since = @last_modified get :conditional_hello assert_equal 304, @response.status.to_i - assert_blank @response.body + assert @response.body.blank? assert_equal @last_modified, @response.headers['Last-Modified'] end @@ -1618,7 +1616,7 @@ class LastModifiedRenderTest < ActionController::TestCase @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT' get :conditional_hello assert_equal 200, @response.status.to_i - assert_present @response.body + assert @response.body.present? assert_equal @last_modified, @response.headers['Last-Modified'] end @@ -1632,7 +1630,7 @@ class LastModifiedRenderTest < ActionController::TestCase @request.if_modified_since = @last_modified get :conditional_hello_with_record assert_equal 304, @response.status.to_i - assert_blank @response.body + assert @response.body.blank? assert_equal @last_modified, @response.headers['Last-Modified'] end @@ -1647,7 +1645,7 @@ class LastModifiedRenderTest < ActionController::TestCase @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT' get :conditional_hello_with_record assert_equal 200, @response.status.to_i - assert_present @response.body + assert @response.body.present? assert_equal @last_modified, @response.headers['Last-Modified'] end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 1f637eb791..523a8d0572 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -320,7 +320,7 @@ class FreeCookieControllerTest < ActionController::TestCase test 'should not emit a csrf-token meta tag' do get :meta - assert_blank @response.body + assert @response.body.blank? end end diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb index 661bcb3945..343d57c300 100644 --- a/actionpack/test/controller/required_params_test.rb +++ b/actionpack/test/controller/required_params_test.rb @@ -11,20 +11,17 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase tests BooksController test "missing required parameters will raise exception" do - post :create, { magazine: { name: "Mjallo!" } } - assert_response :bad_request + assert_raise ActionController::ParameterMissing do + post :create, { magazine: { name: "Mjallo!" } } + end - post :create, { book: { title: "Mjallo!" } } - assert_response :bad_request + assert_raise ActionController::ParameterMissing do + post :create, { book: { title: "Mjallo!" } } + end end test "required parameters that are present will not raise" do post :create, { book: { name: "Mjallo!" } } assert_response :ok end - - test "missing parameters will be mentioned in the return" do - post :create, { magazine: { name: "Mjallo!" } } - assert_equal "Required parameter missing: book", response.body - end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index f0430e516f..5e821046db 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -57,13 +57,13 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase end class MockController - def self.build(helpers) + def self.build(helpers, additional_options = {}) Class.new do - def url_options - options = super + define_method :url_options do + options = super() options[:protocol] ||= "http" options[:host] ||= "test.host" - options + options.merge(additional_options) end include helpers @@ -428,8 +428,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase routes.send(:pages_url) end - def setup_for_named_route - MockController.build(rs.url_helpers).new + def setup_for_named_route(options = {}) + MockController.build(rs.url_helpers, options).new end def test_named_route_without_hash @@ -456,6 +456,16 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal("/", routes.send(:root_path)) end + def test_named_route_root_with_trailing_slash + rs.draw do + root "hello#index" + end + + routes = setup_for_named_route(trailing_slash: true) + assert_equal("http://test.host/", routes.send(:root_url)) + assert_equal("http://test.host/?foo=bar", routes.send(:root_url, foo: :bar)) + end + def test_named_route_with_regexps rs.draw do get 'page/:year/:month/:day/:title' => 'page#show', :as => 'article', diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index bdca1d4d77..df31338f09 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -692,7 +692,7 @@ XML assert_equal "bar", @request.params[:foo] @request.recycle! post :no_op - assert_blank @request.params[:foo] + assert @request.params[:foo].blank? end def test_symbolized_path_params_reset_after_request @@ -931,3 +931,34 @@ class AnonymousControllerTest < ActionController::TestCase assert_equal 'anonymous', @response.body end end + +class RoutingDefaultsTest < ActionController::TestCase + def setup + @controller = Class.new(ActionController::Base) do + def post + render :text => request.fullpath + end + + def project + render :text => request.fullpath + end + end.new + + @routes = ActionDispatch::Routing::RouteSet.new.tap do |r| + r.draw do + get '/posts/:id', :to => 'anonymous#post', :bucket_type => 'post' + get '/projects/:id', :to => 'anonymous#project', :defaults => { :bucket_type => 'project' } + end + end + end + + def test_route_option_can_be_passed_via_process + get :post, :id => 1, :bucket_type => 'post' + assert_equal '/posts/1', @response.body + end + + def test_route_default_is_not_required_for_building_request_uri + get :project, :id => 2 + assert_equal '/projects/2', @response.body + end +end diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index d3fc7128e9..ba24e7fac5 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -2,7 +2,6 @@ require 'abstract_unit' module AbstractController module Testing - class UrlForTest < ActionController::TestCase class W include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { get ':controller(/:action(/:id(.:format)))' } }.url_helpers @@ -350,10 +349,10 @@ module AbstractController def test_with_hash_with_indifferent_access W.default_url_options[:controller] = 'd' W.default_url_options[:only_path] = false - assert_equal("/c", W.new.url_for(HashWithIndifferentAccess.new('controller' => 'c', 'only_path' => true))) + assert_equal("/c", W.new.url_for(ActiveSupport::HashWithIndifferentAccess.new('controller' => 'c', 'only_path' => true))) W.default_url_options[:action] = 'b' - assert_equal("/c/a", W.new.url_for(HashWithIndifferentAccess.new('controller' => 'c', 'action' => 'a', 'only_path' => true))) + assert_equal("/c/a", W.new.url_for(ActiveSupport::HashWithIndifferentAccess.new('controller' => 'c', 'action' => 'a', 'only_path' => true))) end def test_url_params_with_nil_to_param_are_not_in_url diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index c0b9833603..0480295056 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -116,22 +116,22 @@ class WebServiceTest < ActionDispatch::IntegrationTest end end - def test_register_and_use_yaml + def test_post_xml_using_a_disallowed_type_attribute + $stderr = StringIO.new 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'} + post '/', '<foo type="symbol">value</foo>', 'CONTENT_TYPE' => 'application/xml' + assert_response 500 - assert_equal 'entry', @controller.response.body - assert @controller.params.has_key?(:entry) - assert_equal 'loaded from yaml', @controller.params["entry"] - end + post '/', '<foo type="yaml">value</foo>', 'CONTENT_TYPE' => 'application/xml' + assert_response 500 end + ensure + $stderr = STDERR end - def test_register_and_use_yaml_as_symbol + def test_register_and_use_yaml with_test_route_set do - with_params_parsers Mime::YAML => :yaml 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'} @@ -211,36 +211,6 @@ class WebServiceTest < ActionDispatch::IntegrationTest end end - def test_typecast_as_yaml - with_test_route_set do - with_params_parsers Mime::YAML => :yaml do - yaml = (<<-YAML).strip - --- - data: - a: 15 - b: false - c: true - d: 2005-03-17 - e: 2005-03-17T21:41:07Z - f: unparsed - g: - - 1 - - hello - - 1974-07-25 - YAML - post "/", yaml, {'CONTENT_TYPE' => 'application/x-yaml'} - 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 - end - private def with_params_parsers(parsers = {}) old_session = @integration_session diff --git a/actionpack/test/dispatch/best_standards_support_test.rb b/actionpack/test/dispatch/best_standards_support_test.rb index 0737c40a39..551bb9621a 100644 --- a/actionpack/test/dispatch/best_standards_support_test.rb +++ b/actionpack/test/dispatch/best_standards_support_test.rb @@ -16,9 +16,10 @@ class BestStandardsSupportTest < ActiveSupport::TestCase assert_equal nil, headers["X-UA-Compatible"] end - def test_appends_to_app_headers + 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"] diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 39e791b4f4..6035f0361e 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -39,14 +39,25 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest raise ActionController::BadRequest when "/missing_keys" raise ActionController::UrlGenerationError, "No route matches" + when "/parameter_missing" + raise ActionController::ParameterMissing, :missing_param_key else raise "puke!" end end end - ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false)) - DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true)) + def setup + app = ActiveSupport::OrderedOptions.new + app.config = ActiveSupport::OrderedOptions.new + app.config.assets = ActiveSupport::OrderedOptions.new + app.config.assets.prefix = '/sprockets' + Rails.stubs(:application).returns(app) + end + + RoutesApp = Struct.new(:routes).new(SharedTestRoutes) + ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false), RoutesApp) + DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp) test 'skip diagnosis if not showing detailed exceptions' do @app = ProductionApp @@ -78,6 +89,15 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert boomer.closed, "Expected to close the response body" end + test 'displays routes in a table when a RoutingError occurs' do + @app = DevelopmentApp + get "/pass", {}, {'action_dispatch.show_exceptions' => true} + routing_table = body[/route_table.*<.table>/m] + assert_match '/:controller(/:action)(.:format)', routing_table + assert_match ':controller#:action', routing_table + assert_no_match '<|>', routing_table, "there should not be escaped html in the output" + end + test "rescue with diagnostics message" do @app = DevelopmentApp @@ -96,6 +116,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest get "/bad_request", {}, {'action_dispatch.show_exceptions' => true} assert_response 400 assert_match(/ActionController::BadRequest/, body) + + get "/parameter_missing", {}, {'action_dispatch.show_exceptions' => true} + assert_response 400 + assert_match(/ActionController::ParameterMissing/, body) end test "does not show filtered parameters" do diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index c0c3147e37..7d3fc84089 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -30,6 +30,21 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest ) end + test "nils are stripped from collections" do + assert_parses( + {"person" => nil}, + "{\"person\":[null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + assert_parses( + {"person" => ['foo']}, + "{\"person\":[\"foo\",null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + assert_parses( + {"person" => nil}, + "{\"person\":[null, null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + end + test "logs error if parsing unsuccessful" do with_test_routing do output = StringIO.new @@ -107,6 +122,13 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest ) end + test "parses json with non-object JSON content" do + assert_parses( + {"user" => {"_json" => "string content" }, "_json" => "string content" }, + "\"string content\"", { 'CONTENT_TYPE' => 'application/json' } + ) + end + private def assert_parses(expected, actual, headers = {}) with_test_routing(UsersController) do diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb index 3cb430d83d..f072a9f717 100644 --- a/actionpack/test/dispatch/request/query_string_parsing_test.rb +++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb @@ -84,8 +84,8 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest assert_parses({"action" => nil}, "action") assert_parses({"action" => {"foo" => nil}}, "action[foo]") assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar]") - assert_parses({"action" => {"foo" => { "bar" => [] }}}, "action[foo][bar][]") - assert_parses({"action" => {"foo" => []}}, "action[foo][]") + assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar][]") + assert_parses({"action" => {"foo" => nil }}, "action[foo][]") assert_parses({"action"=>{"foo"=>[{"bar"=>nil}]}}, "action[foo][][bar]") end diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb index cb68667002..f13b64a3c7 100644 --- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb @@ -30,6 +30,23 @@ class XmlParamsParsingTest < ActionDispatch::IntegrationTest 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>" diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 4e59e214c6..8a01b29340 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -34,7 +34,7 @@ class RequestTest < ActiveSupport::TestCase assert_equal '1.2.3.4', request.remote_ip request = stub_request 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6' - assert_equal '1.2.3.4', request.remote_ip + assert_equal '3.4.5.6', request.remote_ip request = stub_request 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' @@ -47,30 +47,32 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,unknown' assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.16.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,172.16.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,192.168.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,10.0.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 10.0.0.1, 10.0.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '127.0.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,127.0.0.1' + assert_equal '3.4.5.6', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1' assert_equal nil, request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 172.31.4.4' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 172.31.4.4, 10.0.0.1' assert_equal '3.4.5.6', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address' assert_equal nil, request.remote_ip + end + test "remote ip spoof detection" do request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', 'HTTP_CLIENT_IP' => '2.2.2.2' e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { @@ -79,26 +81,20 @@ class RequestTest < ActiveSupport::TestCase assert_match(/IP spoofing attack/, e.message) assert_match(/HTTP_X_FORWARDED_FOR="1.1.1.1"/, e.message) assert_match(/HTTP_CLIENT_IP="2.2.2.2"/, e.message) + end - # turn IP Spoofing detection off. - # This is useful for sites that are aimed at non-IP clients. The typical - # example is WAP. Since the cellular network is not IP based, it's a - # leap of faith to assume that their proxies are ever going to set the - # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly. + test "remote ip with spoof detection disabled" do request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', 'HTTP_CLIENT_IP' => '2.2.2.2', :ip_spoofing_check => false - assert_equal '2.2.2.2', request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 8.8.8.8' - assert_equal '9.9.9.9', request.remote_ip + assert_equal '1.1.1.1', request.remote_ip end test "remote ip v6" do request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334' assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip - request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334' assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', @@ -109,30 +105,26 @@ class RequestTest < ActiveSupport::TestCase 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,unknown' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1, ::1, fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, ::1' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,::1' assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::' - assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address' assert_equal nil, request.remote_ip + end + test "remote ip v6 spoof detection" do request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334' e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { @@ -141,26 +133,15 @@ class RequestTest < ActiveSupport::TestCase assert_match(/IP spoofing attack/, e.message) assert_match(/HTTP_X_FORWARDED_FOR="fe80:0000:0000:0000:0202:b3ff:fe1e:8329"/, e.message) assert_match(/HTTP_CLIENT_IP="2001:0db8:85a3:0000:0000:8a2e:0370:7334"/, e.message) + end - # Turn IP Spoofing detection off. - # This is useful for sites that are aimed at non-IP clients. The typical - # example is WAP. Since the cellular network is not IP based, it's a - # leap of faith to assume that their proxies are ever going to set the - # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly. + test "remote ip v6 spoof detection disabled" do request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', :ip_spoofing_check => false - assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334' assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip end - test "remote ip when the remote ip middleware returns nil" do - request = stub_request 'REMOTE_ADDR' => '127.0.0.1' - assert_equal '127.0.0.1', request.remote_ip - end - test "remote ip with user specified trusted proxies String" do @trusted_proxies = "67.205.106.73" @@ -170,16 +151,16 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73', 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' - assert_equal '172.16.0.1', request.remote_ip + assert_equal '67.205.106.73', request.remote_ip request = stub_request 'REMOTE_ADDR' => '67.205.106.73,3.4.5.6', 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73,unknown' assert_equal nil, request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 67.205.106.73' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73' assert_equal '3.4.5.6', request.remote_ip end @@ -196,13 +177,13 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1', 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + assert_equal '::1', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334' - assert_equal nil, request.remote_ip + assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", request.remote_ip end test "remote ip with user specified trusted proxies Regexp" do @@ -212,8 +193,8 @@ class RequestTest < ActiveSupport::TestCase 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73, 10.0.0.1, 9.9.9.9, 3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 9.9.9.9, 3.4.5.6, 67.205.106.73' + assert_equal '3.4.5.6', request.remote_ip end test "remote ip v6 with user specified trusted proxies Regexp" do @@ -223,8 +204,13 @@ class RequestTest < ActiveSupport::TestCase 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + end + + test "remote ip middleware not present still returns an IP" do + request = ActionDispatch::Request.new({'REMOTE_ADDR' => '127.0.0.1'}) + assert_equal '127.0.0.1', request.remote_ip end test "domains" do @@ -476,6 +462,27 @@ class RequestTest < ActiveSupport::TestCase assert request.put? end + test "post uneffected by local inflections" do + existing_acrnoyms = ActiveSupport::Inflector.inflections.acronyms.dup + existing_acrnoym_regex = ActiveSupport::Inflector.inflections.acronym_regex.dup + begin + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym "POS" + end + assert_equal "pos_t", "POST".underscore + request = stub_request "REQUEST_METHOD" => "POST" + assert_equal :post, ActionDispatch::Request::HTTP_METHOD_LOOKUP["POST"] + assert_equal :post, request.method_symbol + assert request.post? + ensure + # Reset original acronym set + ActiveSupport::Inflector.inflections do |inflect| + inflect.send(:instance_variable_set,"@acronyms",existing_acrnoyms) + inflect.send(:instance_variable_set,"@acronym_regex",existing_acrnoym_regex) + end + end + end + test "xml format" do request = stub_request request.expects(:parameters).at_least_once.returns({ :format => 'xml' }) @@ -580,6 +587,10 @@ class RequestTest < ActiveSupport::TestCase request.expects(:parameters).at_least_once.returns({}) assert_equal [Mime::HTML], request.formats + request = stub_request 'HTTP_ACCEPT' => '' + request.expects(:parameters).at_least_once.returns({}) + assert_equal [Mime::HTML], request.formats + request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8', 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" request.expects(:parameters).at_least_once.returns({}) @@ -602,7 +613,7 @@ class RequestTest < ActiveSupport::TestCase assert_equal request.format.xml?, false assert_equal request.format.json?, false end - + test "formats with xhr request" do request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" request.expects(:parameters).at_least_once.returns({}) diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index c058bd4909..55221f87c4 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -1,5 +1,4 @@ -require 'active_support/testing/autorun' -require 'action_controller' +require 'abstract_unit' require 'rails/engine' require 'action_dispatch/routing/inspector' @@ -8,7 +7,6 @@ module ActionDispatch class RoutesInspectorTest < ActiveSupport::TestCase def setup @set = ActionDispatch::Routing::RouteSet.new - @inspector = ActionDispatch::Routing::RoutesInspector.new app = ActiveSupport::OrderedOptions.new app.config = ActiveSupport::OrderedOptions.new app.config.assets = ActiveSupport::OrderedOptions.new @@ -17,9 +15,18 @@ module ActionDispatch Rails.stubs(:env).returns("development") end - def draw(&block) + def draw(options = {}, &block) @set.draw(&block) - @inspector.format(@set.routes) + inspector = ActionDispatch::Routing::RoutesInspector.new(@set.routes) + inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n") + end + + def test_json_regexp_converter + @set.draw do + get '/cart', :to => 'cart#show' + end + route = ActionDispatch::Routing::RouteWrapper.new(@set.routes.first) + assert_equal "^\\/cart(?:\\.([^\\/.?]+))?$", route.json_regexp end def test_displaying_routes_for_engines @@ -40,7 +47,8 @@ module ActionDispatch expected = [ "custom_assets GET /custom/assets(.:format) custom_assets#show", " blog /blog Blog::Engine", - "\nRoutes for Blog::Engine:", + "", + "Routes for Blog::Engine:", "cart GET /cart(.:format) cart#show" ] assert_equal expected, output @@ -165,6 +173,22 @@ module ActionDispatch assert_equal " bar GET /bar(.:format) redirect(307, path: /foo/bar)", output[1] assert_equal "foobar GET /foobar(.:format) redirect(301)", output[2] end + + def test_routes_can_be_filtered + output = draw(filter: 'posts') do + resources :articles + resources :posts + end + + assert_equal [" 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", + " post GET /posts/:id(.:format) posts#show", + " PATCH /posts/:id(.:format) posts#update", + " PUT /posts/:id(.:format) posts#update", + " DELETE /posts/:id(.:format) posts#destroy"], output + end end end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index cb5299e8d3..da7474e73c 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -3252,3 +3252,79 @@ class TestOptionalRootSegments < ActionDispatch::IntegrationTest assert_equal '/page/1', root_path(:page => '1') end end + +class TestPortConstraints < ActionDispatch::IntegrationTest + Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| + app.draw do + ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + + get '/integer', to: ok, constraints: { :port => 8080 } + get '/string', to: ok, constraints: { :port => '8080' } + get '/array', to: ok, constraints: { :port => [8080] } + get '/regexp', to: ok, constraints: { :port => /8080/ } + end + end + + include Routes.url_helpers + def app; Routes end + + def test_integer_port_constraints + get 'http://www.example.com/integer' + assert_response :not_found + + get 'http://www.example.com:8080/integer' + assert_response :success + end + + def test_string_port_constraints + get 'http://www.example.com/string' + assert_response :not_found + + get 'http://www.example.com:8080/string' + assert_response :success + end + + def test_array_port_constraints + get 'http://www.example.com/array' + assert_response :not_found + + get 'http://www.example.com:8080/array' + assert_response :success + end + + def test_regexp_port_constraints + get 'http://www.example.com/regexp' + assert_response :not_found + + get 'http://www.example.com:8080/regexp' + assert_response :success + end +end + +class TestRouteDefaults < ActionDispatch::IntegrationTest + stub_controllers do |routes| + Routes = routes + Routes.draw do + resources :posts, bucket_type: 'post' + resources :projects, defaults: { bucket_type: 'project' } + end + end + + def app + Routes + end + + include Routes.url_helpers + + def test_route_options_are_required_for_url_for + assert_raises(ActionController::UrlGenerationError) do + assert_equal '/posts/1', url_for(controller: 'posts', action: 'show', id: 1, only_path: true) + end + + assert_equal '/posts/1', url_for(controller: 'posts', action: 'show', id: 1, bucket_type: 'post', only_path: true) + end + + def test_route_defaults_are_not_required_for_url_for + assert_equal '/projects/1', url_for(controller: 'projects', action: 'show', id: 1, only_path: true) + end +end diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index b4a39219bf..a9bea7ea73 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -57,6 +57,13 @@ class SSLTest < ActionDispatch::IntegrationTest response.headers['Strict-Transport-Security'] end + def test_hsts_expires_with_duration + self.app = ActionDispatch::SSL.new(default_app, :hsts => { :expires => 1.year }) + get "https://example.org/" + assert_equal "max-age=31557600", + response.headers['Strict-Transport-Security'] + end + def test_hsts_include_subdomains self.app = ActionDispatch::SSL.new(default_app, :hsts => { :subdomains => true }) get "https://example.org/" diff --git a/actionpack/test/fixtures/developer.rb b/actionpack/test/fixtures/developer.rb index dd14548fac..4941463015 100644 --- a/actionpack/test/fixtures/developer.rb +++ b/actionpack/test/fixtures/developer.rb @@ -2,6 +2,7 @@ class Developer < ActiveRecord::Base has_and_belongs_to_many :projects has_many :replies has_many :topics, :through => :replies + accepts_nested_attributes_for :projects end class DeVeLoPeR < ActiveRecord::Base diff --git a/actionpack/test/fixtures/test/_partial_name_local_variable.erb b/actionpack/test/fixtures/test/_partial_name_local_variable.erb new file mode 100644 index 0000000000..cc3a91c89f --- /dev/null +++ b/actionpack/test/fixtures/test/_partial_name_local_variable.erb @@ -0,0 +1 @@ +<%= partial_name_local_variable %> diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb index 78608a5c6b..cbe6284714 100644 --- a/actionpack/test/journey/route_test.rb +++ b/actionpack/test/journey/route_test.rb @@ -6,18 +6,18 @@ module ActionDispatch def test_initialize app = Object.new path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))' - defaults = Object.new + defaults = {} route = Route.new("name", app, path, {}, defaults) assert_equal app, route.app assert_equal path, route.path - assert_equal defaults, route.defaults + assert_same defaults, route.defaults end def test_route_adds_itself_as_memo app = Object.new path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))' - defaults = Object.new + defaults = {} route = Route.new("name", app, path, {}, defaults) route.ast.grep(Nodes::Terminal).each do |node| @@ -82,11 +82,14 @@ module ActionDispatch end def test_score + constraints = {:required_defaults => [:controller, :action]} + defaults = {:controller=>"pages", :action=>"show"} + path = Path::Pattern.new "/page/:id(/:action)(.:format)" - specific = Route.new "name", nil, path, {}, {:controller=>"pages", :action=>"show"} + specific = Route.new "name", nil, path, constraints, defaults path = Path::Pattern.new "/:controller(/:action(/:id))(.:format)" - generic = Route.new "name", nil, path, {} + generic = Route.new "name", nil, path, constraints knowledge = {:id=>20, :controller=>"pages", :action=>"show"} diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index 27bdb0108a..3d52b2e9ee 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -155,7 +155,7 @@ module ActionDispatch Router::Strexp.new("/foo/:id", { :id => /\d/ }, ['/', '.', '?'], false) ] - assert_raises(Router::RoutingError) do + assert_raises(ActionController::UrlGenerationError) do @formatter.generate(:path_info, nil, { :id => '10' }, { }) end end @@ -168,7 +168,7 @@ module ActionDispatch path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { }) assert_equal '/foo/10', path - assert_raises(Router::RoutingError) do + assert_raises(ActionController::UrlGenerationError) do @formatter.generate(:path_info, nil, { :id => 'aa' }, { }) end end @@ -194,11 +194,11 @@ module ActionDispatch path = Path::Pattern.new pattern @router.routes.add_route nil, path, {}, {}, route_name - error = assert_raises(Router::RoutingError) do + error = assert_raises(ActionController::UrlGenerationError) do @formatter.generate(:path_info, route_name, { }, { }) end - assert_match(/required keys: \[:id\]/, error.message) + assert_match(/missing required keys: \[:id\]/, error.message) end def test_X_Cascade diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index 89aae4ac56..63b5ac0fab 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -238,7 +238,7 @@ class AtomFeedTest < ActionController::TestCase get :index, :id=>"provide_builder" # because we pass in the non-default builder, the content generated by the # helper should go 'nowhere'. Leaving the response body blank. - assert_blank @response.body + assert @response.body.blank? end end diff --git a/actionpack/test/template/benchmark_helper_test.rb b/actionpack/test/template/benchmark_helper_test.rb deleted file mode 100644 index 8c198d2562..0000000000 --- a/actionpack/test/template/benchmark_helper_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'abstract_unit' -require 'stringio' - -class BenchmarkHelperTest < ActionView::TestCase - include RenderERBUtils - tests ActionView::Helpers::BenchmarkHelper - - def test_output_in_erb - output = render_erb("Hello <%= benchmark do %>world<% end %>") - expected = 'Hello world' - assert_equal expected, output - end - - def test_returns_value_from_block - assert_equal 'test', benchmark { 'test' } - end - - def test_default_message - log = StringIO.new - self.stubs(:logger).returns(Logger.new(log)) - benchmark {} - assert_match(/Benchmarking \(\d+.\d+ms\)/, log.rewind && log.read) - end -end diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb index 495a9d3f9d..21fca35185 100644 --- a/actionpack/test/template/date_helper_i18n_test.rb +++ b/actionpack/test/template/date_helper_i18n_test.rb @@ -117,7 +117,7 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase I18n.expects(:translate).with(('datetime.prompts.' + key.to_s).to_sym, :locale => 'en').returns prompt end - I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day] + I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns %w(year month day) datetime_select('post', 'updated_at', :locale => 'en', :include_seconds => true, :prompt => true) end @@ -129,15 +129,20 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase end def test_date_or_time_select_given_no_order_options_translates_order - I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day] + I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns %w(year month day) datetime_select('post', 'updated_at', :locale => 'en') end def test_date_or_time_select_given_invalid_order - I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:invalid, :month, :day] + I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns %w(invalid month day) assert_raise StandardError do datetime_select('post', 'updated_at', :locale => 'en') end end + + def test_date_or_time_select_given_symbol_keys + I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day] + datetime_select('post', 'updated_at', :locale => 'en') + end end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index f9ce63fcb0..f11adefad8 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1510,6 +1510,44 @@ 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 def test_date_select_without_day @post = Post.new @@ -1968,6 +2006,44 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, time_select("post", "written_on") end + def test_time_select_with_selected + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n} + expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n} + expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n} + + expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 12}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + expected << " : " + expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 20}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, time_select("post", "written_on", selected: Time.local(2004, 6, 15, 12, 20, 30)) + end + + def test_time_select_with_selected_nil + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="1" />\n} + expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="1" />\n} + expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="1" />\n} + + expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}">#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + expected << " : " + expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}">#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, time_select("post", "written_on", discard_year: true, discard_month: true, discard_day: true, selected: nil) + end + def test_time_select_without_date_hidden_fields @post = Post.new @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) @@ -2165,6 +2241,62 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, datetime_select("post", "updated_at") end + def test_datetime_select_with_selected + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 16, 35) + + expected = %{<select id="post_updated_at_1i" name="post[updated_at(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 value="2004" selected="selected">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_updated_at_2i" name="post[updated_at(2i)]">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3" selected="selected">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_updated_at_3i" name="post[updated_at(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" + + expected << " — " + + expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} + expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12" selected="selected">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} + expected << "</select>\n" + expected << " : " + expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} + expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30" selected="selected">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} + expected << "</select>\n" + + assert_dom_equal expected, datetime_select("post", "updated_at", :selected => Time.local(2004, 3, 10, 12, 30)) + end + + def test_datetime_select_with_selected_nil + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 16, 35) + + expected = '<input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="1"/>' + "\n" + + expected << %{<select id="post_updated_at_2i" name="post[updated_at(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">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_updated_at_3i" name="post[updated_at(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">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" + + expected << " — " + + expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} + expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n} + expected << "</select>\n" + expected << " : " + expected << %{<select id="post_updated_at_5i" name="post[updated_at(5i)]">\n} + expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n} + expected << "</select>\n" + + 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) { diff --git a/actionpack/test/template/digestor_test.rb b/actionpack/test/template/digestor_test.rb index 02b1fd87a8..849e2981a6 100644 --- a/actionpack/test/template/digestor_test.rb +++ b/actionpack/test/template/digestor_test.rb @@ -138,6 +138,20 @@ class TemplateDigestorTest < ActionView::TestCase end end + def test_dependencies_via_options_results_in_different_digest + digest_plain = digest("comments/_comment") + digest_fridge = digest("comments/_comment", dependencies: ["fridge"]) + digest_phone = digest("comments/_comment", dependencies: ["phone"]) + digest_fridge_phone = digest("comments/_comment", dependencies: ["fridge", "phone"]) + + assert_not_equal digest_plain, digest_fridge + assert_not_equal digest_plain, digest_phone + assert_not_equal digest_plain, digest_fridge_phone + assert_not_equal digest_fridge, digest_phone + assert_not_equal digest_fridge, digest_fridge_phone + assert_not_equal digest_phone, digest_fridge_phone + end + private def assert_logged(message) old_logger = ActionView::Base.logger @@ -164,8 +178,8 @@ class TemplateDigestorTest < ActionView::TestCase ActionView::Digestor.cache.clear end - def digest(template_name) - ActionView::Digestor.digest(template_name, :html, FixtureFinder.new) + def digest(template_name, options={}) + ActionView::Digestor.digest(template_name, :html, FixtureFinder.new, options) end def change_template(template_name) diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index c730e3ab74..f9890a2eef 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -1120,6 +1120,28 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_with_collection_radio_buttons_with_custom_builder_block + post = Post.new + def post.active; false; end + form_for(post) do |f| + rendered_radio_buttons = f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) do |b| + b.label { b.radio_button + b.text } + end + concat rendered_radio_buttons + end + + expected = whole_form("/posts", "new_post" , "new_post") do + "<label for='post_active_true'>"+ + "<input id='post_active_true' name='post[active]' type='radio' value='true' />" + + "true</label>" + + "<label for='post_active_false'>"+ + "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" + + "false</label>" + end + + assert_dom_equal expected, output_buffer + end + def test_form_for_with_collection_check_boxes post = Post.new def post.tag_ids; [1, 3]; end @@ -1141,6 +1163,33 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_with_collection_check_boxes_with_custom_builder_block + post = Post.new + def post.tag_ids; [1, 3]; end + collection = (1..3).map{|i| [i, "Tag #{i}"] } + form_for(post) do |f| + rendered_check_boxes = f.collection_check_boxes(:tag_ids, collection, :first, :last) do |b| + b.label { b.check_box + b.text } + end + concat rendered_check_boxes + end + + expected = whole_form("/posts", "new_post" , "new_post") do + "<label for='post_tag_ids_1'>" + + "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + + "Tag 1</label>" + + "<label for='post_tag_ids_2'>" + + "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" + + "Tag 2</label>" + + "<label for='post_tag_ids_3'>" + + "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" + + "Tag 3</label>" + + "<input name='post[tag_ids][]' type='hidden' value='' />" + end + + assert_dom_equal expected, output_buffer + end + def test_form_for_with_file_field_generate_multipart Post.send :attr_accessor, :file @@ -1171,7 +1220,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_form_for_with_format form_for(@post, :format => :json, :html => { :id => "edit_post_123", :class => "edit_post" }) do |f| concat f.label(:title) @@ -2164,6 +2212,29 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + class FakeAssociationProxy + def to_ary + [1, 2, 3] + end + end + + def test_nested_fields_for_with_child_index_option_override_on_a_nested_attributes_collection_association_with_proxy + @post.comments = FakeAssociationProxy.new + + form_for(@post) do |f| + concat f.fields_for(:comments, Comment.new(321), :child_index => 'abc') { |cf| + concat cf.text_field(:name) + } + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' + end + + assert_dom_equal expected, output_buffer + end + def test_nested_fields_for_index_method_with_existing_records_on_a_nested_attributes_collection_association @post.comments = Array.new(2) { |id| Comment.new(id + 1) } @@ -2693,6 +2764,19 @@ class FormHelperTest < ActionView::TestCase end end + def test_form_for_only_instantiates_builder_once + initialization_count = 0 + builder_class = Class.new(ActionView::Helpers::FormBuilder) do + define_method :initialize do |*args| + super(*args) + initialization_count += 1 + end + end + + form_for(@post, builder: builder_class) { } + assert_equal 1, initialization_count, 'form builder instantiated more than once' + end + protected def hidden_fields(method = nil) diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 0a94fa079b..6c6a142397 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -488,7 +488,7 @@ class FormTagHelperTest < ActionView::TestCase def test_image_submit_tag_with_confirmation assert_dom_equal( - %(<input type="image" src="/images/save.gif" data-confirm="Are you sure?" />), + %(<input alt="Save" type="image" src="/images/save.gif" data-confirm="Are you sure?" />), image_submit_tag("save.gif", :data => { :confirm => "Are you sure?" }) ) end @@ -496,7 +496,7 @@ class FormTagHelperTest < ActionView::TestCase def test_image_submit_tag_with_deprecated_confirmation assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do assert_dom_equal( - %(<input type="image" src="/images/save.gif" data-confirm="Are you sure?" />), + %(<input alt="Save" type="image" src="/images/save.gif" data-confirm="Are you sure?" />), image_submit_tag("save.gif", :confirm => "Are you sure?") ) end diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index a84034c02e..9c49438f6a 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -25,33 +25,33 @@ class RecordTagHelperTest < ActionView::TestCase def test_content_tag_for expected = %(<li class="record_tag_post" id="record_tag_post_45"></li>) - actual = content_tag_for(:li, @post) { } + actual = content_tag_for(:li, @post) assert_dom_equal expected, actual end def test_content_tag_for_prefix expected = %(<ul class="archived_record_tag_post" id="archived_record_tag_post_45"></ul>) - actual = content_tag_for(:ul, @post, :archived) { } + actual = content_tag_for(:ul, @post, :archived) assert_dom_equal expected, actual end def test_content_tag_for_with_extra_html_options expected = %(<tr class="record_tag_post special" id="record_tag_post_45" style='background-color: #f0f0f0'></tr>) - actual = content_tag_for(:tr, @post, :class => "special", :style => "background-color: #f0f0f0") { } + actual = content_tag_for(:tr, @post, class: "special", style: "background-color: #f0f0f0") 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") { } + actual = content_tag_for(:tr, @post, :archived, class: "special", style: "background-color: #f0f0f0") assert_dom_equal expected, actual end def test_block_not_in_erb_multiple_calls expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>) - actual = div_for(@post, :class => "special") { @post.body } + actual = div_for(@post, class: "special") { @post.body } assert_dom_equal expected, actual - actual = div_for(@post, :class => "special") { @post.body } + actual = div_for(@post, class: "special") { @post.body } assert_dom_equal expected, actual end @@ -63,7 +63,7 @@ class RecordTagHelperTest < ActionView::TestCase def test_div_for_in_erb expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>) - actual = render_erb("<%= div_for(@post, :class => 'special') do %><%= @post.body %><% end %>") + actual = render_erb("<%= div_for(@post, class: 'special') do %><%= @post.body %><% end %>") assert_dom_equal expected, actual end @@ -75,6 +75,14 @@ class RecordTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_content_tag_for_collection_without_given_block + post_1 = RecordTagPost.new.tap { |post| post.id = 101; post.body = "Hello!" } + post_2 = RecordTagPost.new.tap { |post| post.id = 102; post.body = "World!" } + expected = %(<li class="record_tag_post" id="record_tag_post_101"></li>\n<li class="record_tag_post" id="record_tag_post_102"></li>) + actual = content_tag_for(:li, [post_1, post_2]) + assert_dom_equal expected, actual + end + def test_div_for_collection post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" } post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" } @@ -84,7 +92,7 @@ class RecordTagHelperTest < ActionView::TestCase end def test_content_tag_for_single_record_is_html_safe - result = div_for(@post, :class => "special") { @post.body } + result = div_for(@post, class: "special") { @post.body } assert result.html_safe? end @@ -96,8 +104,8 @@ class RecordTagHelperTest < ActionView::TestCase end def test_content_tag_for_does_not_change_options_hash - options = { :class => "important" } - content_tag_for(:li, @post, options) { } - assert_equal({ :class => "important" }, options) + options = { class: "important" } + content_tag_for(:li, @post, options) + assert_equal({ class: "important" }, options) end end diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 9fb26e32b1..8111e58527 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -318,6 +318,13 @@ module RenderTestCases @controller_view.render(customers, :greeting => "Hello") end + def test_render_partial_without_object_or_collection_does_not_generate_partial_name_local_variable + exception = assert_raises ActionView::Template::Error do + @controller_view.render("partial_name_local_variable") + end + assert_match "undefined local variable or method `partial_name_local_variable'", exception.message + end + # TODO: The reason for this test is unclear, improve documentation def test_render_partial_and_fallback_to_layout assert_equal "Before (Josh)\n\nAfter", @view.render(:partial => "test/layout_for_partial", :locals => { :name => "Josh" }) diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc index 3262bf61ac..1b1fe2fa2b 100644 --- a/activemodel/README.rdoc +++ b/activemodel/README.rdoc @@ -1,7 +1,7 @@ = Active Model -- model interfaces for Rails Active Model provides a known set of interfaces for usage in model classes. -They allow for Action Pack helpers to interact with non-ActiveRecord models, +They allow for Action Pack helpers to interact with non-Active Record models, for example. Active Model also helps building custom ORMs for use outside of the Rails framework. @@ -11,7 +11,7 @@ code from Rails, or monkey patch entire helpers to make them handle objects that did not exactly conform to the Active Record interface. This would result in code duplication and fragile applications that broke on upgrades. Active Model solves this by defining an explicit API. You can read more about the -API in ActiveModel::Lint::Tests. +API in <tt>ActiveModel::Lint::Tests</tt>. Active Model provides a default module that implements the basic API required to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>. @@ -133,24 +133,6 @@ behavior out of the box: {Learn more}[link:classes/ActiveModel/Naming.html] -* Observer support - - ActiveModel::Observers allows your object to implement the Observer - pattern in a Rails App and take advantage of all the standard observer - functions. - - class PersonObserver < ActiveModel::Observer - def after_create(person) - person.logger.info("New person added!") - end - - def after_destroy(person) - person.logger.warn("Person with an id of #{person.id} was destroyed!") - end - end - - {Learn more}[link:classes/ActiveModel/Observer.html] - * Making objects serializable ActiveModel::Serialization provides a standard interface for your object diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index db5759ada9..6d11c0fbdc 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -436,7 +436,7 @@ module ActiveModel # attribute_missing is like method_missing, but for attributes. When method_missing is # called we check to see if there is a matching attribute method. If so, we call # attribute_missing to dispatch the attribute. This method can be overloaded to - # customise the behaviour. + # customize the behavior. def attribute_missing(match, *args, &block) __send__(match.target, match.attr_name, *args, &block) end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index ecb7a9e9b1..6e67cd2285 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -119,7 +119,7 @@ module ActiveModel # person.name = 'bob' # person.changes # => { "name" => ["bill", "bob"] } def changes - HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }] + ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }] end # Returns a hash of attributes that were changed before the model was saved. diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb index 3d7067fbcb..49df98d6c1 100644 --- a/activemodel/lib/active_model/validations/clusivity.rb +++ b/activemodel/lib/active_model/validations/clusivity.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/range' module ActiveModel module Validations module Clusivity #:nodoc: - ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " << + ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \ "and must be supplied as the :in (or :within) option of the configuration hash" def check_validity! diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 744c196d30..085532c35b 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -17,9 +17,9 @@ module ActiveModel end def validate_each(record, attr_name, value) - before_type_cast = "#{attr_name}_before_type_cast" + before_type_cast = :"#{attr_name}_before_type_cast" - raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast.to_sym) + raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast) raw_value ||= value return if options[:allow_nil] && raw_value.nil? diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index d51f4d1936..f989b21140 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -103,7 +103,7 @@ module ActiveModel end # Accepts options that will be made available through the +options+ reader. - def initialize(options) + def initialize(options = {}) @options = options.freeze end diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb index d679ad41aa..a037666cbc 100644 --- a/activemodel/test/cases/conversion_test.rb +++ b/activemodel/test/cases/conversion_test.rb @@ -29,4 +29,8 @@ class ConversionTest < ActiveModel::TestCase assert_equal "helicopters/helicopter", Helicopter.new.to_partial_path, "ActiveModel::Conversion#to_partial_path caching should be class-specific" end + + test "to_partial_path handles namespaced models" do + assert_equal "helicopter/comanches/comanche", Helicopter::Comanche.new.to_partial_path + end end diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index 0b9f9537e2..ba45089cca 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -46,7 +46,7 @@ class DirtyTest < ActiveModel::TestCase assert @model.name_changed? end - test "list of changed attributes" do + test "list of changed attribute keys" do assert_equal [], @model.changed @model.name = "Paul" assert_equal ['name'], @model.changed @@ -106,6 +106,17 @@ class DirtyTest < ActiveModel::TestCase assert_equal [nil, "Jericho Cane"], @model.previous_changes['name'] end + test "previous value is preserved when changed after save" do + assert_equal({}, @model.changed_attributes) + @model.name = "Paul" + assert_equal({ "name" => nil }, @model.changed_attributes) + + @model.save + + @model.name = "John" + assert_equal({ "name" => "Paul" }, @model.changed_attributes) + end + test "changing the same attribute multiple times retains the correct original value" do @model.name = "Otto" @model.save diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index 1ffce1ae47..cc0c3f16d2 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -54,6 +54,59 @@ class ErrorsTest < ActiveModel::TestCase assert errors.has_key?(:foo), 'errors should have key :foo' end + test "should be able to clear the errors" do + person = Person.new + person.validate! + + assert_equal 1, person.errors.count + person.errors.clear + assert person.errors.empty? + end + + test "get returns the error by the provided key" do + errors = ActiveModel::Errors.new(self) + errors[:foo] = "omg" + + assert_equal ["omg"], errors.get(:foo) + end + + test "sets the error with the provided key" do + errors = ActiveModel::Errors.new(self) + errors.set(:foo, "omg") + + assert_equal({ foo: "omg" }, errors.messages) + end + + test "values returns an array of messages" do + errors = ActiveModel::Errors.new(self) + errors.set(:foo, "omg") + errors.set(:baz, "zomg") + + assert_equal ["omg", "zomg"], errors.values + end + + test "keys returns the error keys" do + errors = ActiveModel::Errors.new(self) + errors.set(:foo, "omg") + errors.set(:baz, "zomg") + + assert_equal [:foo, :baz], errors.keys + end + + test "as_json returns a json formatted representation of the errors hash" do + person = Person.new + person.validate! + + assert_equal({ name: ["can not be nil"] }, person.errors.as_json) + end + + test "as_json with :full_messages option" do + person = Person.new + person.validate! + + assert_equal({ name: ["name can not be nil"] }, person.errors.as_json(full_messages: true)) + end + test "should return true if no errors" do person = Person.new person.errors[:foo] diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb index d93fd96b88..588d8e661e 100644 --- a/activemodel/test/cases/model_test.rb +++ b/activemodel/test/cases/model_test.rb @@ -20,7 +20,13 @@ class ModelTest < ActiveModel::TestCase def test_initialize_with_nil_or_empty_hash_params_does_not_explode assert_nothing_raised do BasicModel.new() + BasicModel.new nil BasicModel.new({}) end end + + def test_persisted_is_always_false + object = BasicModel.new(:attr => "value") + assert object.persisted? == false + end end diff --git a/activemodel/test/models/helicopter.rb b/activemodel/test/models/helicopter.rb index a52b6fb4dd..933f3c463a 100644 --- a/activemodel/test/models/helicopter.rb +++ b/activemodel/test/models/helicopter.rb @@ -1,3 +1,7 @@ class Helicopter include ActiveModel::Conversion end + +class Helicopter::Comanche + include ActiveModel::Conversion +end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 756bad5507..f1cca0ad76 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,7 +1,44 @@ ## Rails 4.0.0 (unreleased) ## +* Added a state instance variable to each transaction. Will allow other objects + to know whether a transaction has been committed or rolled back. + + *John Wang* + +* Collection associations `#empty?` always respects builded records. + Fix #8879. + + Example: + + widget = Widget.new + widget.things.build + widget.things.empty? # => false + + *Yves Senn* + +* Remove support for parsing YAML parameters from request. + + *Aaron Patterson* + +* Support for PostgreSQL's `ltree` data type. + + *Rob Worley* + +* Fix undefined method `to_i` when calling `new` on a scope that uses an + Array; Fix FloatDomainError when setting integer column to NaN. + Fixes #8718, #8734, #8757. + + *Jason Stirk + Tristan Harward* + +* Rename `update_attributes` to `update`, keep `update_attributes` as an alias for `update` method. + This is a soft-deprecation for `update_attributes`, although it will still work without any + deprecation message in 4.0 is recommended to start using `update` since `update_attributes` will be + deprecated and removed in future versions of Rails. + + *Amparo Luna + Guillermo Iguaran* + * `after_commit` and `after_rollback` now validate the `:on` option and raise an `ArgumentError` - if it is not one of `:create`, `:destroy` or ``:update` + if it is not one of `:create`, `:destroy` or `:update` *Pascal Friederich* @@ -18,7 +55,8 @@ If migrating down, the given migration / block is run normally. See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations) - Attempting to revert the methods `execute`, `remove_columns` and `change_column` will now raise an IrreversibleMigration instead of actually executing them without any output. + Attempting to revert the methods `execute`, `remove_columns` and `change_column` will now + raise an `IrreversibleMigration` instead of actually executing them without any output. *Marc-André Lafortune* @@ -952,7 +990,6 @@ * `:conditions` becomes `:where`. * `:include` becomes `:includes`. - * `:extend` becomes `:extending`. The code to implement the deprecated features has been moved out to the `activerecord-deprecated_finders` gem. This gem is a dependency diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 53ddff420e..0523314128 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -39,6 +39,11 @@ namespace :test do end end +namespace :db do + task :create => ['mysql:build_databases', 'postgresql:build_databases'] + task :drop => ['mysql:drop_databases', 'postgresql:drop_databases'] +end + %w( mysql mysql2 postgresql sqlite3 sqlite3_mem firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| Rake::TestTask.new("test_#{adapter}") { |t| adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 31ddb01123..bfc2e54aba 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -25,5 +25,5 @@ Gem::Specification.new do |s| s.add_dependency 'activemodel', version s.add_dependency 'arel', '~> 3.0.2' - s.add_dependency 'activerecord-deprecated_finders', '0.0.1' + s.add_dependency 'activerecord-deprecated_finders', '0.0.2' end diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb index cd9825b50c..ad12f8597f 100644 --- a/activerecord/examples/performance.rb +++ b/activerecord/examples/performance.rb @@ -143,7 +143,7 @@ Benchmark.ips(TIME) do |x| end x.report 'Resource#update' do - Exhibit.first.update_attributes(:name => 'bob') + Exhibit.first.update(name: 'bob') end x.report 'Resource#destroy' do diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 8101f7a45e..9d1c12ec62 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -113,7 +113,7 @@ module ActiveRecord # other than the writer method. # # The immutable requirement is enforced by Active Record by freezing any object assigned as a value - # object. Attempting to change it afterwards will result in a ActiveSupport::FrozenObjectError. + # object. Attempting to change it afterwards will result in a RuntimeError. # # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable @@ -165,7 +165,7 @@ module ActiveRecord # by specifying an instance of the value object in the conditions hash. The following example # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD": # - # Customer.where(balance: Money.new(20, "USD")).all + # Customer.where(balance: Money.new(20, "USD")) # module ClassMethods # Adds reader and writer methods for manipulating a value object: diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index d8b6d7a86b..16a46a59d1 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1,8 +1,6 @@ require 'active_support/core_ext/enumerable' require 'active_support/core_ext/string/conversions' require 'active_support/core_ext/module/remove_method' -require 'active_support/dependencies/autoload' -require 'active_support/concern' require 'active_record/errors' module ActiveRecord @@ -194,7 +192,7 @@ module ActiveRecord # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt> # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt> # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(mileston), Project#milestones.find(milestone_id),</tt> - # <tt>Project#milestones.all(options), Project#milestones.build, Project#milestones.create</tt> + # <tt>Project#milestones.build, Project#milestones.create</tt> # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt> # <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt> # @@ -459,7 +457,7 @@ module ActiveRecord # has_many :people do # def find_or_create_by_name(name) # first_name, last_name = name.split(" ", 2) - # find_or_create_by_first_name_and_last_name(first_name, last_name) + # find_or_create_by(first_name: first_name, last_name: last_name) # end # end # end @@ -474,7 +472,7 @@ module ActiveRecord # module FindOrCreateByNameExtension # def find_or_create_by_name(name) # first_name, last_name = name.split(" ", 2) - # find_or_create_by_first_name_and_last_name(first_name, last_name) + # find_or_create_by(first_name: first_name, last_name: last_name) # end # end # @@ -743,7 +741,7 @@ module ActiveRecord # other than the main one. If this is the case Active Record falls back to the previously # used LEFT OUTER JOIN based strategy. For example # - # Post.includes([:author, :comments]).where(['comments.approved = ?', true]).all + # Post.includes([:author, :comments]).where(['comments.approved = ?', true]) # # This will result in a single SQL query with joins along the lines of: # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and @@ -951,7 +949,7 @@ module ActiveRecord # # The <tt>:dependent</tt> option can have different values which specify how the deletion # is done. For more information, see the documentation for this option on the different - # specific association types. When no option is given, the behaviour is to do nothing + # specific association types. When no option is given, the behavior is to do nothing # with the associated records when destroying a record. # # Note that <tt>:dependent</tt> is implemented using Rails' callback @@ -1081,7 +1079,7 @@ module ActiveRecord # === Example # # Example: A Firm class declares <tt>has_many :clients</tt>, which will add: - # * <tt>Firm#clients</tt> (similar to <tt>Clients.all conditions: ["firm_id = ?", id]</tt>) + # * <tt>Firm#clients</tt> (similar to <tt>Client.where(firm_id: id)</tt>) # * <tt>Firm#clients<<</tt> # * <tt>Firm#clients.delete</tt> # * <tt>Firm#clients.destroy</tt> @@ -1091,7 +1089,7 @@ module ActiveRecord # * <tt>Firm#clients.clear</tt> # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>) # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>) - # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, conditions: "firm_id = #{id}")</tt>) + # * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>) # * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>) # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>) # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) @@ -1213,7 +1211,7 @@ module ActiveRecord # === Example # # An Account class declares <tt>has_one :beneficiary</tt>, which will add: - # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.first(conditions: "account_id = #{id}")</tt>) + # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>) # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>) # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 1303822868..300f67959d 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -16,6 +16,7 @@ module ActiveRecord def scope scope = klass.unscoped scope.merge! eval_scope(klass, reflection.scope) if reflection.scope + scope.extending! Array(options[:extend]) add_constraints(scope) end diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 75f72c1a46..532af3e83e 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -50,8 +50,11 @@ module ActiveRecord # Checks whether record is different to the current target, without loading it def different_target?(record) - record.nil? && owner[reflection.foreign_key] || - record && record.id != owner[reflection.foreign_key] + if record.nil? + owner[reflection.foreign_key] + else + record.id != owner[reflection.foreign_key] + end end def replace_keys(record) diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index fcdfc1e150..fdead16761 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -6,7 +6,8 @@ module ActiveRecord::Associations::Builder CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove] def valid_options - super + [:table_name, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove] + super + [:table_name, :finder_sql, :counter_sql, :before_add, + :after_add, :before_remove, :after_remove, :extend] end attr_reader :block_extension, :extension_module diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 832b963052..5feb149946 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -273,7 +273,7 @@ module ActiveRecord if loaded? || options[:counter_sql] size.zero? else - !scope.exists? + @target.blank? && !scope.exists? end end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 7c43e37cf2..e93e700c93 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -33,6 +33,7 @@ module ActiveRecord def initialize(klass, association) #:nodoc: @association = association super klass, klass.arel_table + self.default_scoped = true merge! association.scope(nullify: false) end @@ -758,7 +759,7 @@ module ActiveRecord # person.pets.count # => 0 # person.pets.any? # => true # - # You can also pass a block to define criteria. The behaviour + # You can also pass a block to define criteria. The behavior # is the same, it returns true if the collection based on the # criteria is not empty. # @@ -793,7 +794,7 @@ module ActiveRecord # person.pets.many? #=> true # # You can also pass a block to define criteria. The - # behaviour is the same, it returns true if the collection + # behavior is the same, it returns true if the collection # based on the criteria has more than one record. # # person.pets diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index c3266f2bb4..d1458f30ba 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -114,11 +114,7 @@ module ActiveRecord end def target_reflection_has_associated_record? - if through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank? - false - else - true - end + !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?) end def update_through_counter?(method) diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb index fdf8ae1453..08e0ec691f 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -23,7 +23,7 @@ module ActiveRecord attributes = construct_join_attributes(record) if through_record - through_record.update_attributes(attributes) + through_record.update(attributes) elsif owner.new_record? through_proxy.build(attributes) else diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 0848e7afb3..82bf426b22 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -100,7 +100,9 @@ module ActiveRecord case association when Hash preload_hash(association) - when String, Symbol + when Symbol + preload_one(association) + when String preload_one(association.to_sym) else raise ArgumentError, "#{association.inspect} was not recognised for preload" diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index cbf5e734ea..82588905c6 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -76,7 +76,7 @@ module ActiveRecord else # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000) # Make several smaller queries if necessary or make one query if the adapter supports it - sliced = owner_keys.each_slice(model.connection.in_clause_length || owner_keys.size) + sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) records = sliced.map { |slice| records_for(slice).to_a }.flatten end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 0333605eac..616ae1631f 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/deprecation' module ActiveRecord module AttributeMethods @@ -71,11 +70,11 @@ module ActiveRecord super(attr, value) end - def update(*) + def update_record(*) partial_writes? ? super(keys_for_partial_write) : super end - def create(*) + def create_record(*) partial_writes? ? super(keys_for_partial_write) : super end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 0857b02c8e..310f1b6e75 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -76,7 +76,7 @@ module ActiveRecord end def get_primary_key(base_name) #:nodoc: - return 'id' unless base_name && !base_name.blank? + return 'id' if base_name.blank? case primary_key_prefix_type when :table_name diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index aab832c2f7..bf5793d454 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -162,12 +162,9 @@ module ActiveRecord #:nodoc: # # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects # by simple queries without turning to SQL. They work by appending the name of an attribute - # to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt> and thus produces finders - # like <tt>Person.find_by_user_name</tt>, <tt>Person.find_all_by_last_name</tt>, and - # <tt>Payment.find_by_transaction_id</tt>. Instead of writing - # <tt>Person.where(user_name: user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>. - # And instead of writing <tt>Person.where(last_name: last_name).all</tt>, you just do - # <tt>Person.find_all_by_last_name(last_name)</tt>. + # to <tt>find_by_</tt> # like <tt>Person.find_by_user_name</tt>. + # Instead of writing # <tt>Person.where(user_name: user_name).first</tt>, you just do + # <tt>Person.find_by_user_name(user_name)</tt>. # # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an # <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records, @@ -180,46 +177,7 @@ module ActiveRecord #:nodoc: # # It's even possible to call these dynamic finder methods on relations and named scopes. # - # Payment.order("created_on").find_all_by_amount(50) - # Payment.pending.find_last_by_amount(100) - # - # The same dynamic finder style can be used to create the object if it doesn't already exist. - # This dynamic finder is called with <tt>find_or_create_by_</tt> and will return the object if - # it already exists and otherwise creates it, then returns it. Protected attributes won't be set - # unless they are given in a block. - # - # # No 'Summer' tag exists - # Tag.find_or_create_by_name("Summer") # equal to Tag.create(name: "Summer") - # - # # Now the 'Summer' tag does exist - # Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer") - # - # # Now 'Bob' exist and is an 'admin' - # User.find_or_create_by_name('Bob', age: 40) { |u| u.admin = true } - # - # Adding an exclamation point (!) on to the end of <tt>find_or_create_by_</tt> will - # raise an <tt>ActiveRecord::RecordInvalid</tt> error if the new record is invalid. - # - # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without - # saving it first. Protected attributes won't be set unless they are given in a block. - # - # # No 'Winter' tag exists - # winter = Tag.find_or_initialize_by_name("Winter") - # winter.persisted? # false - # - # To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of - # a list of parameters. - # - # Tag.find_or_create_by_name(name: "rails", creator: current_user) - # - # That will either find an existing tag named "rails", or create a new one while setting the - # user that created it. - # - # Just like <tt>find_by_*</tt>, you can also use <tt>scoped_by_*</tt> to retrieve data. The good thing about - # using this feature is that the very first time result is returned using <tt>method_missing</tt> technique - # but after that the method is declared on the class. Henceforth <tt>method_missing</tt> will not be hit. - # - # User.scoped_by_user_name('David') + # Payment.order("created_on").find_by_amount(50) # # == Saving arrays, hashes, and other non-mappable objects in text columns # diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 1c9c627090..22226b2f4f 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -299,11 +299,11 @@ module ActiveRecord run_callbacks(:save) { super } end - def create #:nodoc: + def create_record #:nodoc: run_callbacks(:create) { super } end - def update(*) #:nodoc: + def update_record(*) #:nodoc: run_callbacks(:update) { super } end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 82d0cf7e2e..3675184193 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -2,7 +2,6 @@ require 'thread' require 'thread_safe' require 'monitor' require 'set' -require 'active_support/deprecation' module ActiveRecord # Raised when a connection could not be obtained within the connection @@ -518,6 +517,7 @@ module ActiveRecord def establish_connection(owner, spec) @class_to_pool.clear + raise RuntimeError, "Anonymous class is not allowed." unless owner.name owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 4cca94e40b..2b8026dbf9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -5,7 +5,17 @@ module ActiveRecord def initialize(connection) @connection = connection + @state = nil end + + def committed? + @state == :commit + end + + def rolledback? + @state == :rollback + end + end class ClosedTransaction < Transaction #:nodoc: @@ -91,6 +101,7 @@ module ActiveRecord end def rollback_records + @state = :rollback records.uniq.each do |record| begin record.rolledback!(parent.closed?) @@ -101,6 +112,7 @@ module ActiveRecord end def commit_records + @state = :commit records.uniq.each do |record| begin record.committed! diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 8517ce5fc5..cbb6869e66 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -5,7 +5,6 @@ require 'active_support/core_ext/benchmark' require 'active_record/connection_adapters/schema_cache' require 'active_record/connection_adapters/abstract/schema_dumper' require 'monitor' -require 'active_support/deprecation' module ActiveRecord module ConnectionAdapters # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index fd36a5b075..fb28ecb6cf 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -206,7 +206,7 @@ module ActiveRecord when TrueClass, FalseClass value ? 1 : 0 else - value.to_i + value.to_i rescue nil end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index a6013f754a..20a5ca2baa 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -7,13 +7,15 @@ module ActiveRecord module ConnectionHandling # Establishes a connection to the database that's used by all Active Record objects. def mysql2_connection(config) + config = config.symbolize_keys + config[:username] = 'root' if config[:username].nil? if Mysql2::Client.const_defined? :FOUND_ROWS config[:flags] = Mysql2::Client::FOUND_ROWS end - client = Mysql2::Client.new(config.symbolize_keys) + client = Mysql2::Client.new(config) options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0] ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 34d7a246b2..9b5170f657 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -1,5 +1,3 @@ -require 'active_support/deprecation' - module ActiveRecord module ConnectionAdapters class PostgreSQLAdapter < AbstractAdapter diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 18ea83ce42..02c295983f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -82,7 +82,7 @@ module ActiveRecord def type_cast(value) return if value.nil? - value.to_i rescue value ? 1 : 0 + ConnectionAdapters::Column.value_to_integer value end end @@ -276,6 +276,7 @@ module ActiveRecord register_type 'circle', OID::Identity.new register_type 'hstore', OID::Hstore.new register_type 'json', OID::Json.new + register_type 'ltree', OID::Identity.new register_type 'int4range', OID::IntRange.new alias_type 'int8range', 'int4range' 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 e10b562fa4..8c68576bdc 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -18,9 +18,9 @@ module ActiveRecord # create_database config[:database], config # create_database 'foo_development', encoding: 'unicode' def create_database(name, options = {}) - options = options.reverse_merge(:encoding => "utf8") + options = { encoding: 'utf8' }.merge!(options.symbolize_keys) - option_string = options.symbolize_keys.sum do |key, value| + option_string = options.sum do |key, value| case key when :owner " OWNER = \"#{value}\"" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d62a375470..b1b0467379 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -17,22 +17,25 @@ require 'ipaddr' module ActiveRecord module ConnectionHandling + VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout, + :client_encoding, :options, :application_name, :fallback_application_name, + :keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count, + :tty, :sslmode, :requiressl, :sslcert, :sslkey, :sslrootcert, :sslcrl, + :requirepeer, :krbsrvname, :gsslib, :service] + # Establishes a connection to the database that's used by all Active Record objects def postgresql_connection(config) # :nodoc: conn_params = config.symbolize_keys - # Forward any unused config params to PGconn.connect. - [:statement_limit, :encoding, :min_messages, :schema_search_path, - :schema_order, :adapter, :pool, :checkout_timeout, :template, - :reaping_frequency, :insert_returning, :variables].each do |key| - conn_params.delete key - end - conn_params.delete_if { |k,v| v.nil? } + conn_params.delete_if { |_, v| v.nil? } # Map ActiveRecords param names to PGs. conn_params[:user] = conn_params.delete(:username) if conn_params[:username] conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database] + # Forward only valid config params to PGconn.connect. + conn_params.keep_if { |k, _| VALID_CONN_PARAMS.include?(k) } + # The postgres drivers don't allow the creation of an unconnected PGconn object, # so just pass a nil connection object for the time being. ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config) @@ -173,6 +176,8 @@ module ActiveRecord :decimal when 'hstore' :hstore + when 'ltree' + :ltree # Network address types when 'inet' :inet @@ -275,6 +280,10 @@ module ActiveRecord column(name, 'hstore', options) end + def ltree(name, options = {}) + column(name, 'ltree', options) + end + def inet(name, options = {}) column(name, 'inet', options) end @@ -340,7 +349,8 @@ module ActiveRecord macaddr: { name: "macaddr" }, uuid: { name: "uuid" }, json: { name: "json" }, - intrange: { name: "int4range" } + intrange: { name: "int4range" }, + ltree: { name: "ltree" } } include Quoting @@ -451,8 +461,6 @@ module ActiveRecord @visitor = BindSubstitution.new self end - connection_parameters.delete :prepared_statements - @connection_parameters, @config = connection_parameters, config # @local_tz is initialized as nil to avoid warnings when connect tries to use it @@ -640,32 +648,30 @@ module ActiveRecord end def exec_cache(sql, binds) + stmt_key = prepare_statement sql + + # Clear the queue + @connection.get_last_result + @connection.send_query_prepared(stmt_key, binds.map { |col, val| + type_cast(val, col) + }) + @connection.block + @connection.get_last_result + rescue PGError => e + # Get the PG code for the failure. Annoyingly, the code for + # prepared statements whose return value may have changed is + # FEATURE_NOT_SUPPORTED. Check here for more details: + # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 begin - stmt_key = prepare_statement sql - - # Clear the queue - @connection.get_last_result - @connection.send_query_prepared(stmt_key, binds.map { |col, val| - type_cast(val, col) - }) - @connection.block - @connection.get_last_result - rescue PGError => e - # Get the PG code for the failure. Annoyingly, the code for - # prepared statements whose return value may have changed is - # FEATURE_NOT_SUPPORTED. Check here for more details: - # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 - begin - code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE) - rescue - raise e - end - if FEATURE_NOT_SUPPORTED == code - @statements.delete sql_key(sql) - retry - else - raise e - end + code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE) + rescue + raise e + end + if FEATURE_NOT_SUPPORTED == code + @statements.delete sql_key(sql) + retry + else + raise e end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 94c6684700..812f1ce5c5 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -365,17 +365,18 @@ module ActiveRecord pk = self.class.primary_key @attributes[pk] = nil unless @attributes.key?(pk) - @aggregation_cache = {} - @association_cache = {} - @attributes_cache = {} - @previously_changed = {} - @changed_attributes = {} - @readonly = false - @destroyed = false - @marked_for_destruction = false - @new_record = true - @txn = nil - @_start_transaction_state = {} + @aggregation_cache = {} + @association_cache = {} + @attributes_cache = {} + @previously_changed = {} + @changed_attributes = {} + @readonly = false + @destroyed = false + @marked_for_destruction = false + @new_record = true + @txn = nil + @_start_transaction_state = {} + @transaction = nil end end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index ea3bb8f33f..5a4ef5991d 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -5,8 +5,6 @@ require 'active_support/dependencies' require 'active_record/fixture_set/file' require 'active_record/errors' -require 'active_support/deprecation' # temporary - module ActiveRecord class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc: end diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 6ab67fdece..e630897a4b 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/indifferent_access' + module ActiveRecord module Inheritance extend ActiveSupport::Concern diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 035c77c424..701949e57b 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -66,7 +66,7 @@ module ActiveRecord send(lock_col + '=', previous_lock_value + 1) end - def update(attribute_names = @attributes.keys) #:nodoc: + def update_record(attribute_names = @attributes.keys) #:nodoc: return super unless locking_enabled? return 0 if attribute_names.empty? diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index b4bb95a392..8e4ddcac82 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -26,7 +26,7 @@ module ActiveRecord # # Account.transaction do # # select * from accounts where ... - # accounts = Account.where(...).all + # accounts = Account.where(...) # account1 = accounts.detect { |account| ... } # account2 = accounts.detect { |account| ... } # # select * from accounts where id=? for update diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index 2366a91bb5..c1ba524c84 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -21,13 +21,15 @@ module ActiveRecord end def render_bind(column, value) - if column.type == :binary - rendered_value = "<#{value.bytesize} bytes of binary data>" + if column + if column.binary? + value = "<#{value.bytesize} bytes of binary data>" + end + + [column.name, value] else - rendered_value = value + [nil, value] end - - [column.name, rendered_value] end def sql(event) diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 4c9bd76d7c..c5bd11edbf 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -58,7 +58,7 @@ module ActiveRecord # It also allows you to update the avatar through the member: # # params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } } - # member.update_attributes params[:member] + # member.update params[:member] # member.avatar.icon # => 'sad' # # By default you will only be able to set and update attributes on the diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 4d1a9c94b7..1b2aa9349e 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -212,7 +212,7 @@ module ActiveRecord # Updates the attributes of the model from the passed-in hash and saves the # record, all wrapped in a transaction. If the object is invalid, the saving # will fail and false will be returned. - def update_attributes(attributes) + def update(attributes) # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do @@ -220,10 +220,12 @@ module ActiveRecord save end end + + alias update_attributes update - # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead + # Updates its receiver just like +update+ but calls <tt>save!</tt> instead # of +save+, so an exception is raised if the record is invalid. - def update_attributes!(attributes) + def update!(attributes) # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do @@ -231,27 +233,29 @@ module ActiveRecord save! end end + + alias update_attributes! update! - # Updates a single attribute of an object, without having to explicitly call save on that object. - # - # * Validation is skipped. - # * Callbacks are skipped. - # * updated_at/updated_on column is not updated if that column is available. - # - # Raises an +ActiveRecordError+ when called on new objects, or when the +name+ - # attribute is marked as readonly. + # Equivalent to <code>update_columns(name => value)</code>. def update_column(name, value) update_columns(name => value) end - # Updates the attributes from the passed-in hash, without having to explicitly call save on that object. + # Updates the attributes directly in the database issuing an UPDATE SQL + # statement and sets them in the receiver: # - # * Validation is skipped. + # user.update_columns(last_request_at: Time.current) + # + # This is the fastest way to update attributes because it goes straight to + # the database, but take into account that in consequence the regular update + # procedures are totally bypassed. In particular: + # + # * Validations are skipped. # * Callbacks are skipped. - # * updated_at/updated_on column is not updated if that column is available. + # * +updated_at+/+updated_on+ are not updated. # - # Raises an +ActiveRecordError+ when called on new objects, or when at least - # one of the attributes is marked as readonly. + # This method raises an +ActiveRecord::ActiveRecordError+ when called on new + # objects, or when at least one of the attributes is marked as readonly. def update_columns(attributes) raise ActiveRecordError, "can not update on a new record object" unless persisted? @@ -406,13 +410,13 @@ module ActiveRecord def create_or_update raise ReadOnlyRecord if readonly? - result = new_record? ? create : update + result = new_record? ? create_record : update_record result != false end # Updates the associated record with values matching those of the instance attributes. # Returns the number of affected rows. - def update(attribute_names = @attributes.keys) + def update_record(attribute_names = @attributes.keys) attributes_with_values = arel_attributes_with_values_for_update(attribute_names) if attributes_with_values.empty? @@ -426,7 +430,7 @@ module ActiveRecord # Creates a record with values matching those of the instance attributes # and returns its id. - def create(attribute_names = @attributes.keys) + def create_record(attribute_names = @attributes.keys) attributes_values = arel_attributes_with_values_for_create(attribute_names) new_id = self.class.unscoped.insert attributes_values diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 0df895eb67..0053530f73 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -276,7 +276,7 @@ module ActiveRecord stmt.table(table) stmt.key = table[primary_key] - if joins_values.any? + if with_default_scope.joins_values.any? @klass.connection.join_to_update(stmt, arel) else stmt.take(arel.limit) @@ -308,7 +308,7 @@ module ActiveRecord id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) } else object = find(id) - object.update_attributes(attributes) + object.update(attributes) object end end @@ -401,7 +401,7 @@ module ActiveRecord stmt = Arel::DeleteManager.new(arel.engine) stmt.from(table) - if joins_values.any? + if with_default_scope.joins_values.any? @klass.connection.join_to_delete(stmt, arel, table[primary_key]) else stmt.wheres = arel.constraints @@ -474,16 +474,16 @@ module ActiveRecord # Returns sql statement for the relation. # - # Users.where(name: 'Oscar').to_sql + # User.where(name: 'Oscar').to_sql # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar' def to_sql @to_sql ||= klass.connection.to_sql(arel, bind_values.dup) end - # Returns a hash of where conditions + # Returns a hash of where conditions. # - # Users.where(name: 'Oscar').where_values_hash - # # => {name: "oscar"} + # User.where(name: 'Oscar').where_values_hash + # # => {name: "Oscar"} def where_values_hash equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node| node.left.relation.name == table_name diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 0b27ea730b..3f154bd1cc 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -1,5 +1,3 @@ -require 'active_support/deprecation' - module ActiveRecord module Calculations # Count the records. diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 431d083f21..615309964c 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -1,4 +1,3 @@ -require 'active_support/concern' require 'thread' require 'thread_safe' diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 83074e72c1..883d25d80b 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -7,12 +7,12 @@ module ActiveRecord table = default_table if value.is_a?(Hash) - table = Arel::Table.new(column, default_table.engine) - association = klass.reflect_on_association(column.to_sym) - if value.empty? - queries.concat ['1 = 2'] + queries << '1 = 2' else + table = Arel::Table.new(column, default_table.engine) + association = klass.reflect_on_association(column.to_sym) + value.each do |k, v| queries.concat expand(association && association.klass, table, k, v) end diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 8b7eda6eee..01fbb96b8e 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -134,16 +134,14 @@ module ActiveRecord # end # # def self.titles - # map(&:title) + # pluck(:title) # end - # # end # # We are able to call the methods like this: # # Article.published.featured.latest_article # Article.featured.titles - def scope(name, body, &block) extension = Module.new(&block) if block diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index df7f58c81f..cf4cf9e602 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -107,7 +107,7 @@ module ActiveRecord private def initialize_store_attribute(store_attribute) attribute = send(store_attribute) - unless attribute.is_a?(HashWithIndifferentAccess) + unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess) attribute = IndifferentCoder.as_indifferent_hash(attribute) send :"#{store_attribute}=", attribute end @@ -134,12 +134,12 @@ module ActiveRecord def self.as_indifferent_hash(obj) case obj - when HashWithIndifferentAccess + when ActiveSupport::HashWithIndifferentAccess obj when Hash obj.with_indifferent_access else - HashWithIndifferentAccess.new + ActiveSupport::HashWithIndifferentAccess.new end end end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index fda51b3d76..67c7e714e6 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -1,5 +1,7 @@ module ActiveRecord module Tasks # :nodoc: + class DatabaseAlreadyExists < StandardError; end # :nodoc: + module DatabaseTasks # :nodoc: extend self @@ -32,6 +34,8 @@ module ActiveRecord def create(*arguments) configuration = arguments.first class_for_adapter(configuration['adapter']).new(*arguments).create + rescue DatabaseAlreadyExists + $stderr.puts "#{configuration['database']} already exists" rescue Exception => error $stderr.puts error, *(error.backtrace) $stderr.puts "Couldn't create database for #{configuration.inspect}" diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 3d27c97254..17378969a5 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -1,7 +1,6 @@ module ActiveRecord module Tasks # :nodoc: class MySQLDatabaseTasks # :nodoc: - DEFAULT_CHARSET = ENV['CHARSET'] || 'utf8' DEFAULT_COLLATION = ENV['COLLATION'] || 'utf8_unicode_ci' ACCESS_DENIED_ERROR = 1045 @@ -16,18 +15,23 @@ module ActiveRecord establish_connection configuration_without_database connection.create_database configuration['database'], creation_options establish_connection configuration + rescue ActiveRecord::StatementInvalid => error + if /database exists/ === error.message + raise DatabaseAlreadyExists + else + raise + end rescue error_class => error - raise error unless error.errno == ACCESS_DENIED_ERROR - - $stdout.print error.error - establish_connection root_configuration_without_database - connection.create_database configuration['database'], creation_options - connection.execute grant_statement.gsub(/\s+/, ' ').strip - establish_connection configuration - rescue error_class => error - $stderr.puts error.error - $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" - $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['encoding'] + if error.respond_to?(:errno) && error.errno == ACCESS_DENIED_ERROR + $stdout.print error.error + establish_connection root_configuration_without_database + connection.create_database configuration['database'], creation_options + connection.execute grant_statement.gsub(/\s+/, ' ').strip + establish_connection configuration + else + $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" + $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['encoding'] + end end def drop @@ -87,14 +91,15 @@ module ActiveRecord end def error_class - case configuration['adapter'] - when /jdbc/ + if configuration['adapter'] =~ /jdbc/ require 'active_record/railties/jdbcmysql_error' ArJdbcMySQL::Error - when /mysql2/ + elsif defined?(Mysql2) Mysql2::Error - else + elsif defined?(Mysql) Mysql::Error + else + StandardError end end @@ -128,7 +133,6 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION; end args end - end end end diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index ea5cb888fb..0b1b030516 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -3,7 +3,6 @@ require 'shellwords' module ActiveRecord module Tasks # :nodoc: class PostgreSQLDatabaseTasks # :nodoc: - DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8' delegate :connection, :establish_connection, :clear_active_connections!, @@ -18,6 +17,12 @@ module ActiveRecord connection.create_database configuration['database'], configuration.merge('encoding' => encoding) establish_connection configuration + rescue ActiveRecord::StatementInvalid => error + if /database .* already exists/ === error.message + raise DatabaseAlreadyExists + else + raise + end end def drop diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb index da01058a82..de8b16627e 100644 --- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb @@ -1,7 +1,6 @@ module ActiveRecord module Tasks # :nodoc: class SQLiteDatabaseTasks # :nodoc: - delegate :connection, :establish_connection, to: ActiveRecord::Base def initialize(configuration, root = Rails.root) @@ -9,10 +8,7 @@ module ActiveRecord end def create - if File.exist?(configuration['database']) - $stderr.puts "#{configuration['database']} already exists" - return - end + raise DatabaseAlreadyExists if File.exist?(configuration['database']) establish_connection configuration connection diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index cf17b1d8a4..8ded6d4a86 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -43,7 +43,7 @@ module ActiveRecord private - def create + def create_record if self.record_timestamps current_time = current_time_from_proper_timezone @@ -57,7 +57,7 @@ module ActiveRecord super end - def update(*args) + def update_record(*args) if should_record_timestamps? current_time = current_time_from_proper_timezone diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 404b492288..f9149c1819 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -164,14 +164,16 @@ module ActiveRecord class AdapterTestWithoutTransaction < ActiveRecord::TestCase self.use_transactional_fixtures = false + class Klass < ActiveRecord::Base + end + def setup - @klass = Class.new(ActiveRecord::Base) - @klass.establish_connection 'arunit' - @connection = @klass.connection + Klass.establish_connection 'arunit' + @connection = Klass.connection end def teardown - @klass.remove_connection + Klass.remove_connection end test "transaction state is reset after a reconnect" do diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 94fc3564df..8812cf1b7d 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -42,7 +42,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase assert_equal "DROP TABLE `people`", drop_table(:people) end - if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_create_mysql_database_with_encoding assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index ffd6904aec..b67d70ede7 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -1,6 +1,9 @@ require "cases/helper" class MysqlConnectionTest < ActiveRecord::TestCase + class Klass < ActiveRecord::Base + end + def setup super @connection = ActiveRecord::Base.connection @@ -17,9 +20,8 @@ class MysqlConnectionTest < ActiveRecord::TestCase run_without_connection do |orig| ar_config = ARTest.connection_config['arunit'] url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}" - klass = Class.new(ActiveRecord::Base) - klass.establish_connection(url) - assert_equal ar_config['database'], klass.connection.current_database + Klass.establish_connection(url) + assert_equal ar_config['database'], Klass.connection.current_database end end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index ddfe42b375..0eb1cc511e 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -49,13 +49,11 @@ module ActiveRecord end def test_tables_quoting - begin - @conn.tables(nil, "foo-bar", nil) - flunk - rescue => e - # assertion for *quoted* database properly - assert_match(/database 'foo-bar'/, e.inspect) - end + @conn.tables(nil, "foo-bar", nil) + flunk + rescue => e + # assertion for *quoted* database properly + assert_match(/database 'foo-bar'/, e.inspect) end def test_pk_and_sequence_for diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index 2c0ed73c92..94429e772f 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -37,13 +37,11 @@ module ActiveRecord end def test_tables_quoting - begin - @connection.tables(nil, "foo-bar", nil) - flunk - rescue => e - # assertion for *quoted* database properly - assert_match(/database 'foo-bar'/, e.inspect) - end + @connection.tables(nil, "foo-bar", nil) + flunk + rescue => e + # assertion for *quoted* database properly + assert_match(/database 'foo-bar'/, e.inspect) end end diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index 1b4f4a5fc9..01c3e6b49b 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -16,6 +16,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase def test_create_database_with_encoding assert_equal %(CREATE DATABASE "matt" ENCODING = 'utf8'), create_database(:matt) assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1) + assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, 'encoding' => :latin1) end def test_create_database_with_collation_and_ctype diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index c7ce43d71e..2254be8612 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -30,6 +30,9 @@ end class PostgresqlUUID < ActiveRecord::Base end +class PostgresqlLtree < ActiveRecord::Base +end + class PostgresqlDataTypeTest < ActiveRecord::TestCase self.use_transactional_fixtures = false @@ -37,38 +40,43 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase @connection = ActiveRecord::Base.connection @connection.execute("set lc_monetary = 'C'") - @connection.execute("INSERT INTO postgresql_arrays (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )") + @connection.execute("INSERT INTO postgresql_arrays (id, commission_by_quarter, nicknames) VALUES (1, '{35000,21000,18000,17000}', '{foo,bar,baz}')") @first_array = PostgresqlArray.find(1) - @connection.execute("INSERT INTO postgresql_tsvectors (text_vector) VALUES (' ''text'' ''vector'' ')") + @connection.execute("INSERT INTO postgresql_tsvectors (id, text_vector) VALUES (1, ' ''text'' ''vector'' ')") @first_tsvector = PostgresqlTsvector.find(1) - @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('567.89'::money)") - @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('-567.89'::money)") + @connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (1, '567.89'::money)") + @connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (2, '-567.89'::money)") @first_money = PostgresqlMoney.find(1) @second_money = PostgresqlMoney.find(2) - @connection.execute("INSERT INTO postgresql_numbers (single, double) VALUES (123.456, 123456.789)") + @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (1, 123.456, 123456.789)") @first_number = PostgresqlNumber.find(1) - @connection.execute("INSERT INTO postgresql_times (time_interval, scaled_time_interval) VALUES ('1 year 2 days ago', '3 weeks ago')") + @connection.execute("INSERT INTO postgresql_times (id, time_interval, scaled_time_interval) VALUES (1, '1 year 2 days ago', '3 weeks ago')") @first_time = PostgresqlTime.find(1) - @connection.execute("INSERT INTO postgresql_network_addresses (cidr_address, inet_address, mac_address) VALUES('192.168.0/24', '172.16.1.254/32', '01:23:45:67:89:0a')") + @connection.execute("INSERT INTO postgresql_network_addresses (id, cidr_address, inet_address, mac_address) VALUES(1, '192.168.0/24', '172.16.1.254/32', '01:23:45:67:89:0a')") @first_network_address = PostgresqlNetworkAddress.find(1) - @connection.execute("INSERT INTO postgresql_bit_strings (bit_string, bit_string_varying) VALUES (B'00010101', X'15')") + @connection.execute("INSERT INTO postgresql_bit_strings (id, bit_string, bit_string_varying) VALUES (1, B'00010101', X'15')") @first_bit_string = PostgresqlBitString.find(1) - @connection.execute("INSERT INTO postgresql_oids (obj_id) VALUES (1234)") + @connection.execute("INSERT INTO postgresql_oids (id, obj_id) VALUES (1, 1234)") @first_oid = PostgresqlOid.find(1) - @connection.execute("INSERT INTO postgresql_timestamp_with_zones (time) VALUES ('2010-01-01 10:00:00-1')") + @connection.execute("INSERT INTO postgresql_timestamp_with_zones (id, time) VALUES (1, '2010-01-01 10:00:00-1')") - @connection.execute("INSERT INTO postgresql_uuids (guid, compact_guid) VALUES('d96c3da0-96c1-012f-1316-64ce8f32c6d8', 'f06c715096c1012f131764ce8f32c6d8')") + @connection.execute("INSERT INTO postgresql_uuids (id, guid, compact_guid) VALUES(1, 'd96c3da0-96c1-012f-1316-64ce8f32c6d8', 'f06c715096c1012f131764ce8f32c6d8')") @first_uuid = PostgresqlUUID.find(1) end + def teardown + [PostgresqlArray, PostgresqlTsvector, PostgresqlMoney, PostgresqlNumber, PostgresqlTime, PostgresqlNetworkAddress, + PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone, PostgresqlUUID].each(&:delete_all) + end + def test_data_type_of_array_types assert_equal :integer, @first_array.column_for_attribute(:commission_by_quarter).type assert_equal :text, @first_array.column_for_attribute(:nicknames).type diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb new file mode 100644 index 0000000000..5d12ca75ca --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb @@ -0,0 +1,41 @@ +# encoding: utf-8 +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlLtreeTest < ActiveRecord::TestCase + class Ltree < ActiveRecord::Base + self.table_name = 'ltrees' + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.transaction do + @connection.create_table('ltrees') do |t| + t.ltree 'path' + end + end + rescue ActiveRecord::StatementInvalid + skip "do not test on PG without ltree" + end + + def teardown + @connection.execute 'drop table if exists ltrees' + end + + def test_column + column = Ltree.columns_hash['path'] + assert_equal :ltree, column.type + end + + def test_write + ltree = Ltree.new(path: '1.2.3.4') + assert ltree.save! + end + + def test_select + @connection.execute "insert into ltrees (path) VALUES ('1.2.3')" + ltree = Ltree.first + assert_equal '1.2.3', ltree.path + end +end diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb index 48b06a767f..10195e3ae4 100644 --- a/activerecord/test/cases/aggregations_test.rb +++ b/activerecord/test/cases/aggregations_test.rb @@ -1,6 +1,5 @@ require "cases/helper" require 'models/customer' -require 'active_support/core_ext/exception' class AggregationsTest < ActiveRecord::TestCase fixtures :customers @@ -26,7 +25,7 @@ class AggregationsTest < ActiveRecord::TestCase def test_immutable_value_objects customers(:david).balance = Money.new(100) - assert_raise(ActiveSupport::FrozenObjectError) { customers(:david).balance.instance_eval { @amount = 20 } } + assert_raise(RuntimeError) { customers(:david).balance.instance_eval { @amount = 20 } } end def test_inferred_mapping diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index b2eac0349b..aa5b27623e 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -7,6 +7,7 @@ if ActiveRecord::Base.connection.supports_migrations? def setup @connection = ActiveRecord::Base.connection + ActiveRecord::SchemaMigration.drop_table end def teardown @@ -45,5 +46,4 @@ if ActiveRecord::Base.connection.supports_migrations? assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } end end - end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 04d0f755b6..3a6da0e59f 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -33,7 +33,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_belongs_to_with_primary_key_joins_on_correct_column sql = Client.joins(:firm_with_primary_key).to_sql - if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql) assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql) elsif current_adapter?(:OracleAdapter) @@ -63,6 +63,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal apple.id, citibank.firm_id end + def test_id_assignment + apple = Firm.create("name" => "Apple") + citibank = Account.create("credit_limit" => 10) + citibank.firm_id = apple + assert_nil citibank.firm_id + end + def test_natural_assignment_with_primary_key apple = Firm.create("name" => "Apple") citibank = Client.create("name" => "Primary key client") @@ -317,12 +324,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 1, Topic.find(topic.id)[:replies_count] end - def test_belongs_to_counter_after_update_attributes - topic = Topic.create!(:title => "37s") - topic.replies.create!(:title => "re: 37s", :content => "rails") + def test_belongs_to_counter_after_update + topic = Topic.create!(title: "37s") + topic.replies.create!(title: "re: 37s", content: "rails") assert_equal 1, Topic.find(topic.id)[:replies_count] - topic.update_attributes(:title => "37signals") + topic.update(title: "37signals") assert_equal 1, Topic.find(topic.id)[:replies_count] end @@ -567,6 +574,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal new_firm.name, "Apple" end + def test_attributes_are_set_without_error_when_initialized_from_belongs_to_association_with_array_in_where_clause + new_account = Account.where(:credit_limit => [ 50, 60 ]).new + assert_nil new_account.credit_limit + end + def test_reassigning_the_parent_id_updates_the_object client = companies(:second_client) diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 94437380a4..be2d30641e 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -93,31 +93,31 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_preloading_has_many_in_multiple_queries_with_more_ids_than_database_can_handle - Post.connection.expects(:in_clause_length).at_least_once.returns(5) + Comment.connection.expects(:in_clause_length).at_least_once.returns(5) posts = Post.all.merge!(:includes=>:comments).to_a assert_equal 11, posts.size end def test_preloading_has_many_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle - Post.connection.expects(:in_clause_length).at_least_once.returns(nil) + Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) posts = Post.all.merge!(:includes=>:comments).to_a assert_equal 11, posts.size end def test_preloading_habtm_in_multiple_queries_with_more_ids_than_database_can_handle - Post.connection.expects(:in_clause_length).at_least_once.returns(5) + Comment.connection.expects(:in_clause_length).at_least_once.returns(5) posts = Post.all.merge!(:includes=>:categories).to_a assert_equal 11, posts.size end def test_preloading_habtm_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle - Post.connection.expects(:in_clause_length).at_least_once.returns(nil) + Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) posts = Post.all.merge!(:includes=>:categories).to_a assert_equal 11, posts.size end def test_load_associated_records_in_one_query_when_adapter_has_no_limit - Post.connection.expects(:in_clause_length).at_least_once.returns(nil) + Comment.connection.expects(:in_clause_length).at_least_once.returns(nil) post = posts(:welcome) assert_queries(2) do @@ -126,7 +126,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_load_associated_records_in_several_queries_when_many_ids_passed - Post.connection.expects(:in_clause_length).at_least_once.returns(1) + Comment.connection.expects(:in_clause_length).at_least_once.returns(1) post1, post2 = posts(:welcome), posts(:thinking) assert_queries(3) do @@ -135,7 +135,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_load_associated_records_in_one_query_when_a_few_ids_passed - Post.connection.expects(:in_clause_length).at_least_once.returns(3) + Comment.connection.expects(:in_clause_length).at_least_once.returns(3) post = posts(:welcome) assert_queries(2) do @@ -212,8 +212,9 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once post = posts(:welcome) - post.update_attributes!(:author => nil) - post = assert_queries(1) { Post.all.merge!(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author which is null so no query for the author or address + post.update!(author: nil) + post = assert_queries(1) { Post.all.merge!(includes: {author_with_address: :author_address}).find(post.id) } + # find the post, then find the author which is null so no query for the author or address assert_no_queries do assert_equal nil, post.author_with_address end @@ -221,7 +222,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_finding_with_includes_on_null_belongs_to_polymorphic_association sponsor = sponsors(:moustache_club_sponsor_for_groucho) - sponsor.update_attributes!(:sponsorable => nil) + sponsor.update!(sponsorable: nil) sponsor = assert_queries(1) { Sponsor.all.merge!(:includes => :sponsorable).find(sponsor.id) } assert_no_queries do assert_equal nil, sponsor.sponsorable diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 7e6c7d5862..d42630e1b7 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -625,6 +625,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 3, company.clients_of_firm.size end + def test_collection_not_empty_after_building + company = companies(:first_firm) + assert_predicate company.contracts, :empty? + company.contracts.build + assert_not_predicate company.contracts, :empty? + end + def test_collection_size_twice_for_regressions post = posts(:thinking) assert_equal 0, post.readers.size @@ -1705,4 +1712,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, post.comments.count end end + + test "collection proxy respects default scope" do + author = authors(:mary) + assert !author.first_posts.exists? + end + + test "association with extend option" do + post = posts(:welcome) + assert_equal "lifo", post.comments_with_extend.author + assert_equal "hello", post.comments_with_extend.greeting + end + + test "association with extend option with multiple extensions" do + post = posts(:welcome) + assert_equal "lifo", post.comments_with_extend_2.author + assert_equal "hello", post.comments_with_extend_2.greeting + end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 2b96b42032..af91fb2920 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -706,7 +706,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_can_update_through_association assert_nothing_raised do - people(:michael).posts.first.update_attributes!(:title => "Can write") + people(:michael).posts.first.update!(title: "Can write") end end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 9b00c21b52..10ec33be75 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -443,8 +443,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_has_many_through_uses_conditions_specified_on_the_has_many_association author = Author.first - assert_present author.comments - assert_blank author.nonexistant_comments + assert author.comments.present? + assert author.nonexistant_comments.blank? end def test_has_many_through_uses_correct_attributes diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 16ce150396..e5cb4f8f7a 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -161,16 +161,16 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas end def test_callbacks_firing_order_on_update - eye = Eye.create(:iris_attributes => {:color => 'honey'}) - eye.update_attributes(:iris_attributes => {:color => 'green'}) + eye = Eye.create(iris_attributes: {color: 'honey'}) + eye.update(iris_attributes: {color: 'green'}) assert_equal [true, false], eye.after_update_callbacks_stack end def test_callbacks_firing_order_on_save - eye = Eye.create(:iris_attributes => {:color => 'honey'}) + eye = Eye.create(iris_attributes: {color: 'honey'}) assert_equal [false, false], eye.after_save_callbacks_stack - eye.update_attributes(:iris_attributes => {:color => 'blue'}) + eye.update(iris_attributes: {color: 'blue'}) assert_equal [false, false, false, false], eye.after_save_callbacks_stack end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 518ce9763c..4f46459ab3 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -26,7 +26,6 @@ require 'models/bird' require 'models/car' require 'models/bulb' require 'rexml/document' -require 'active_support/core_ext/exception' class FirstAbstractClass < ActiveRecord::Base self.abstract_class = true @@ -125,7 +124,7 @@ class BasicsTest < ActiveRecord::TestCase assert_nil Edge.primary_key end - unless current_adapter?(:PostgreSQLAdapter,:OracleAdapter,:SQLServerAdapter) + unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter) def test_limit_with_comma assert Topic.limit("1,2").to_a end @@ -154,7 +153,7 @@ class BasicsTest < ActiveRecord::TestCase end end - unless current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) + unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_limit_should_allow_sql_literal assert_equal 1, Topic.limit(Arel.sql('2-1')).to_a.length end @@ -223,7 +222,7 @@ class BasicsTest < ActiveRecord::TestCase ) # For adapters which support microsecond resolution. - if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLite3Adapter) + if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) assert_equal 11, Topic.find(1).written_on.sec assert_equal 223300, Topic.find(1).written_on.usec assert_equal 9900, Topic.find(2).written_on.usec @@ -301,13 +300,11 @@ class BasicsTest < ActiveRecord::TestCase end def test_initialize_with_invalid_attribute - begin - Topic.new({ "title" => "test", - "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"}) - rescue ActiveRecord::MultiparameterAssignmentErrors => ex - assert_equal(1, ex.errors.size) - assert_equal("last_read", ex.errors[0].attribute) - end + Topic.new({ "title" => "test", + "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"}) + rescue ActiveRecord::MultiparameterAssignmentErrors => ex + assert_equal(1, ex.errors.size) + assert_equal("last_read", ex.errors[0].attribute) end def test_create_after_initialize_without_block @@ -472,7 +469,7 @@ class BasicsTest < ActiveRecord::TestCase Post.reset_table_name end - if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_update_all_with_order_and_limit assert_equal 1, Topic.limit(1).order('id DESC').update_all(:content => 'bulk updated!') end @@ -594,7 +591,7 @@ class BasicsTest < ActiveRecord::TestCase post.reload assert_equal "cannot change this", post.title - post.update_attributes(:title => "try to change", :body => "changed") + post.update(title: "try to change", body: "changed") post.reload assert_equal "cannot change this", post.title assert_equal "changed", post.body @@ -1002,7 +999,7 @@ class BasicsTest < ActiveRecord::TestCase def test_reload_with_exclusive_scope dev = DeveloperCalledDavid.first - dev.update_attributes!( :name => "NotDavid" ) + dev.update!(name: "NotDavid" ) assert_equal dev, dev.reload end diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb index 2124c256fa..adbe51f430 100644 --- a/activerecord/test/cases/column_test.rb +++ b/activerecord/test/cases/column_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'models/company' module ActiveRecord module ConnectionAdapters @@ -40,13 +41,26 @@ module ActiveRecord def test_type_cast_non_integer_to_integer column = Column.new("field", nil, "integer") - assert_raises(NoMethodError) do - column.type_cast([]) - end + assert_nil column.type_cast([1,2]) + assert_nil column.type_cast({1 => 2}) + assert_nil column.type_cast((1..2)) + end - assert_raises(NoMethodError) do - column.type_cast(Object.new) - end + def test_type_cast_activerecord_to_integer + column = Column.new("field", nil, "integer") + firm = Firm.create(:name => 'Apple') + assert_nil column.type_cast(firm) + end + + def test_type_cast_object_without_to_i_to_integer + column = Column.new("field", nil, "integer") + assert_nil column.type_cast(Object.new) + end + + def test_type_cast_nan_and_infinity_to_integer + column = Column.new("field", nil, "integer") + assert_nil column.type_cast(Float::NAN) + assert_nil column.type_cast(1.0/0.0) end def test_type_cast_time diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 0718d0886f..ea344e992b 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -327,6 +327,20 @@ module ActiveRecord def test_pool_sets_connection_visitor assert @pool.connection.visitor.is_a?(Arel::Visitors::ToSql) end + + + #make sure exceptions are thrown when establish_connection + #is called with a anonymous class + def test_anonymous_class_exception + anonymous = Class.new(ActiveRecord::Base) + handler = ActiveRecord::Base.connection_handler + + assert_raises(RuntimeError){ + handler.establish_connection anonymous, nil + } + end + + end end end diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index ee9818678d..52de0efe7f 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -9,7 +9,7 @@ module ActiveRecord end def test_url_host_no_db - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) spec = resolve 'mysql://foo?encoding=utf8' assert_equal({ :adapter => "mysql", @@ -18,7 +18,7 @@ module ActiveRecord end def test_url_host_db - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) spec = resolve 'mysql://foo/bar?encoding=utf8' assert_equal({ :adapter => "mysql", @@ -28,7 +28,7 @@ module ActiveRecord end def test_url_port - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) spec = resolve 'mysql://foo:123?encoding=utf8' assert_equal({ :adapter => "mysql", @@ -38,7 +38,7 @@ module ActiveRecord end def test_encoded_password - skip "only if mysql is available" unless current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + 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" diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index ed7eedaa27..e0cf4adf13 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -39,7 +39,7 @@ class DefaultTest < ActiveRecord::TestCase end end -if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) +if current_adapter?(:MysqlAdapter, :Mysql2Adapter) class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase # ActiveRecord::Base#create! (and #save and other related methods) will # open a new transaction. When in transactional fixtures mode, this will diff --git a/activerecord/test/cases/deprecated_dynamic_methods_test.rb b/activerecord/test/cases/deprecated_dynamic_methods_test.rb index dde36e7f72..8e842d8758 100644 --- a/activerecord/test/cases/deprecated_dynamic_methods_test.rb +++ b/activerecord/test/cases/deprecated_dynamic_methods_test.rb @@ -1,7 +1,7 @@ # This file should be deleted when activerecord-deprecated_finders is removed as # a dependency. # -# It is kept for now as there is some fairly nuanced behaviour in the dynamic +# It is kept for now as there is some fairly nuanced behavior in the dynamic # finders so it is useful to keep this around to guard against regressions if # we need to change the code. @@ -568,9 +568,9 @@ class DynamicScopeTest < ActiveRecord::TestCase end def test_dynamic_scope_should_create_methods_after_hitting_method_missing - assert_blank @test_klass.methods.grep(/scoped_by_type/) + assert @test_klass.methods.grep(/scoped_by_type/).blank? @test_klass.scoped_by_type(nil) - assert_present @test_klass.methods.grep(/scoped_by_type/) + assert @test_klass.methods.grep(/scoped_by_type/).present? end def test_dynamic_scope_with_less_number_of_arguments diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 55ee066cda..b9961a4420 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -517,7 +517,7 @@ class DirtyTest < ActiveRecord::TestCase assert !pirate.previous_changes.key?('created_on') pirate = Pirate.find_by_catchphrase("Thar She Blows!") - pirate.update_attributes(:catchphrase => "Ahoy!") + pirate.update(catchphrase: "Ahoy!") assert_equal 2, pirate.previous_changes.size assert_equal ["Thar She Blows!", "Ahoy!"], pirate.previous_changes['catchphrase'] diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index a7e5fdf709..aa2a6d7509 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -122,7 +122,7 @@ if ActiveRecord::Base.connection.supports_explain? base.expects(:collecting_sqls_for_explain).never base.logger.expects(:warn).never base.silence_auto_explain do - with_threshold(0) { Car.all } + with_threshold(0) { Car.all.to_a } end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 7db7953313..a9fa107749 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -15,6 +15,18 @@ require 'models/toy' class FinderTest < ActiveRecord::TestCase fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations + def test_find_by_id_with_hash + assert_raises(ActiveRecord::StatementInvalid) do + Post.find_by_id(:limit => 1) + end + end + + def test_find_by_title_and_id_with_hash + assert_raises(ActiveRecord::StatementInvalid) do + Post.find_by_title_and_id('foo', :limit => 1) + end + end + def test_find assert_equal(topics(:first).title, Topic.find(1).title) end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 2392516395..a0a3e6cb0d 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -207,7 +207,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase s.reload assert_equal "unchangeable name", s.name - s.update_attributes(:name => "changed name") + s.update(name: "changed name") s.reload assert_equal "unchangeable name", s.name end diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 345e83a102..57eac0c175 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -8,6 +8,19 @@ class LogSubscriberTest < ActiveRecord::TestCase include ActiveSupport::LogSubscriber::TestHelper include ActiveSupport::Logger::Severity + class TestDebugLogSubscriber < ActiveRecord::LogSubscriber + attr_reader :debugs + + def initialize + @debugs = [] + super + end + + def debug message + @debugs << message + end + end + fixtures :posts def setup @@ -30,30 +43,27 @@ class LogSubscriberTest < ActiveRecord::TestCase def test_schema_statements_are_ignored event = Struct.new(:duration, :payload) - logger = Class.new(ActiveRecord::LogSubscriber) { - attr_accessor :debugs - - def initialize - @debugs = [] - super - end - - def debug message - @debugs << message - end - }.new + logger = TestDebugLogSubscriber.new assert_equal 0, logger.debugs.length - logger.sql(event.new(0, { :sql => 'hi mom!' })) + logger.sql(event.new(0, sql: 'hi mom!')) assert_equal 1, logger.debugs.length - logger.sql(event.new(0, { :sql => 'hi mom!', :name => 'foo' })) + logger.sql(event.new(0, sql: 'hi mom!', name: 'foo')) assert_equal 2, logger.debugs.length - logger.sql(event.new(0, { :sql => 'hi mom!', :name => 'SCHEMA' })) + logger.sql(event.new(0, sql: 'hi mom!', name: 'SCHEMA')) assert_equal 2, logger.debugs.length end + def test_ignore_binds_payload_with_nil_column + event = Struct.new(:duration, :payload) + + logger = TestDebugLogSubscriber.new + logger.sql(event.new(0, sql: 'hi mom!', binds: [[nil, 1]])) + assert_equal 1, logger.debugs.length + end + def test_basic_query_logging Developer.all.load wait @@ -105,7 +115,7 @@ class LogSubscriberTest < ActiveRecord::TestCase def test_binary_data_is_not_logged skip if current_adapter?(:Mysql2Adapter) - Binary.create(:data => 'some binary data') + Binary.create(data: 'some binary data') wait assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join) end diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index 4a3c48f750..cad759bba9 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -35,7 +35,7 @@ module ActiveRecord t.column :foo, :string end - assert_equal %w(foo id), connection.columns(:testings).map(&:name).sort + assert_equal %w(id foo), connection.columns(:testings).map(&:name) end def test_create_table_with_not_null_column @@ -50,7 +50,7 @@ module ActiveRecord def test_create_table_with_defaults # MySQL doesn't allow defaults on TEXT or BLOB columns. - mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) + mysql = current_adapter?(:MysqlAdapter, :Mysql2Adapter) connection.create_table :testings do |t| t.column :one, :string, :default => "hello" @@ -99,7 +99,7 @@ module ActiveRecord assert_equal 'smallint', one.sql_type assert_equal 'integer', four.sql_type assert_equal 'bigint', eight.sql_type - elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter) assert_match 'int(11)', default.sql_type assert_match 'tinyint', one.sql_type assert_match 'int', four.sql_type @@ -119,7 +119,7 @@ module ActiveRecord t.column :foo, :string end - assert_equal %w(foo testing_id), connection.columns(:testings).map(&:name).sort + assert_equal %w(testing_id foo), connection.columns(:testings).map(&:name) end def test_create_table_with_primary_key_prefix_as_table_name @@ -129,7 +129,7 @@ module ActiveRecord t.column :foo, :string end - assert_equal %w(foo testingid), connection.columns(:testings).map(&:name).sort + assert_equal %w(testingid foo), connection.columns(:testings).map(&:name) end def test_create_table_raises_when_redefining_primary_key_column diff --git a/activerecord/test/cases/migration/rename_column_test.rb b/activerecord/test/cases/migration/rename_column_test.rb index 866b2e334c..8f6918d06a 100644 --- a/activerecord/test/cases/migration/rename_column_test.rb +++ b/activerecord/test/cases/migration/rename_column_test.rb @@ -107,8 +107,9 @@ module ActiveRecord assert_equal 1, connection.indexes('test_models').size remove_column("test_models", "hat_size") - # FIXME: should all adapters behave the same? - if current_adapter?(:PostgreSQLAdapter) + # 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) else assert_equal ['index_test_models_on_hat_style_and_hat_size'], connection.indexes('test_models').map(&:name) diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 9cb64a6a71..187c6e8447 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -24,7 +24,7 @@ class MigrationTest < ActiveRecord::TestCase def setup super - %w(reminders people_reminders prefix_reminders_suffix).each do |table| + %w(reminders people_reminders prefix_reminders_suffix p_things_s).each do |table| Reminder.connection.drop_table(table) rescue nil end Reminder.reset_column_information diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb index e905006570..b5a69c4a92 100644 --- a/activerecord/test/cases/migrator_test.rb +++ b/activerecord/test/cases/migrator_test.rb @@ -29,6 +29,7 @@ module ActiveRecord def teardown super ActiveRecord::SchemaMigration.delete_all rescue nil + ActiveRecord::Migration.verbose = true end def test_migrator_with_duplicate_names diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 3f08f9ea4d..94837341fc 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -79,10 +79,10 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase def test_should_disable_allow_destroy_by_default Pirate.accepts_nested_attributes_for :ship - pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") - ship = pirate.create_ship(:name => 'Nights Dirty Lightning') + pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + ship = pirate.create_ship(name: 'Nights Dirty Lightning') - pirate.update_attributes(:ship_attributes => { '_destroy' => true, :id => ship.id }) + pirate.update(ship_attributes: { '_destroy' => true, :id => ship.id }) assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.ship.reload } end @@ -125,33 +125,33 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase def test_reject_if_with_a_proc_which_returns_true_always_for_has_one Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| true } - pirate = Pirate.new(:catchphrase => "Stop wastin' me time") - ship = pirate.create_ship(:name => 's1') - pirate.update_attributes({:ship_attributes => { :name => 's2', :id => ship.id } }) + pirate = Pirate.new(catchphrase: "Stop wastin' me time") + ship = pirate.create_ship(name: 's1') + pirate.update({ship_attributes: { name: 's2', id: ship.id } }) assert_equal 's1', ship.reload.name end def test_reject_if_with_a_proc_which_returns_true_always_for_has_many Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true } - man = Man.create(:name => "John") - interest = man.interests.create(:topic => 'photography') - man.update_attributes({:interests_attributes => { :topic => 'gardening', :id => interest.id } }) + man = Man.create(name: "John") + interest = man.interests.create(topic: 'photography') + man.update({interests_attributes: { topic: 'gardening', id: interest.id } }) assert_equal 'photography', interest.reload.topic end def test_destroy_works_independent_of_reject_if Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }, :allow_destroy => true - man = Man.create(:name => "Jon") - interest = man.interests.create(:topic => 'the ladies') - man.update_attributes({:interests_attributes => { :_destroy => "1", :id => interest.id } }) + man = Man.create(name: "Jon") + interest = man.interests.create(topic: 'the ladies') + man.update({interests_attributes: { _destroy: "1", id: interest.id } }) assert man.reload.interests.empty? end def test_has_many_association_updating_a_single_record Man.accepts_nested_attributes_for(:interests) - man = Man.create(:name => 'John') - interest = man.interests.create(:topic => 'photography') - man.update_attributes({:interests_attributes => {:topic => 'gardening', :id => interest.id}}) + man = Man.create(name: 'John') + interest = man.interests.create(topic: 'photography') + man.update({interests_attributes: {topic: 'gardening', id: interest.id}}) assert_equal 'gardening', interest.reload.topic end @@ -284,8 +284,8 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase @pirate.ship.destroy [1, '1', true, 'true'].each do |truth| - ship = @pirate.reload.create_ship(:name => 'Mister Pablo') - @pirate.update_attributes(:ship_attributes => { :id => ship.id, :_destroy => truth }) + ship = @pirate.reload.create_ship(name: 'Mister Pablo') + @pirate.update(ship_attributes: { id: ship.id, _destroy: truth }) assert_nil @pirate.reload.ship assert_raise(ActiveRecord::RecordNotFound) { Ship.find(ship.id) } @@ -294,7 +294,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy [nil, '0', 0, 'false', false].each do |not_truth| - @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => not_truth }) + @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: not_truth }) assert_equal @ship, @pirate.reload.ship end @@ -303,7 +303,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? } - @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => '1' }) + @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: '1' }) assert_equal @ship, @pirate.reload.ship @@ -311,14 +311,14 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_also_work_with_a_HashWithIndifferentAccess - @pirate.ship_attributes = HashWithIndifferentAccess.new(:id => @ship.id, :name => 'Davy Jones Gold Dagger') + @pirate.ship_attributes = ActiveSupport::HashWithIndifferentAccess.new(:id => @ship.id, :name => 'Davy Jones Gold Dagger') assert @pirate.ship.persisted? assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name end - def test_should_work_with_update_attributes_as_well - @pirate.update_attributes({ :catchphrase => 'Arr', :ship_attributes => { :id => @ship.id, :name => 'Mister Pablo' } }) + def test_should_work_with_update_as_well + @pirate.update({ catchphrase: 'Arr', ship_attributes: { id: @ship.id, name: 'Mister Pablo' } }) @pirate.reload assert_equal 'Arr', @pirate.catchphrase @@ -342,22 +342,22 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_accept_update_only_option - @pirate.update_attributes(:update_only_ship_attributes => { :id => @pirate.ship.id, :name => 'Mayflower' }) + @pirate.update(update_only_ship_attributes: { id: @pirate.ship.id, name: 'Mayflower' }) end def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true @ship.delete - @pirate.reload.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower' }) + @pirate.reload.update(update_only_ship_attributes: { name: 'Mayflower' }) assert_not_nil @pirate.ship end def test_should_update_existing_when_update_only_is_true_and_no_id_is_given @ship.delete - @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning') + @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning') - @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower' }) + @pirate.update(update_only_ship_attributes: { name: 'Mayflower' }) assert_equal 'Mayflower', @ship.reload.name assert_equal @ship, @pirate.reload.ship @@ -365,9 +365,9 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_update_existing_when_update_only_is_true_and_id_is_given @ship.delete - @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning') + @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning') - @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id }) + @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id }) assert_equal 'Mayflower', @ship.reload.name assert_equal @ship, @pirate.reload.ship @@ -376,9 +376,9 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => true @ship.delete - @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning') + @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning') - @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id, :_destroy => true }) + @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id, _destroy: true }) assert_nil @pirate.reload.ship assert_raise(ActiveRecord::RecordNotFound) { Ship.find(@ship.id) } @@ -468,15 +468,15 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy @ship.pirate.destroy [1, '1', true, 'true'].each do |truth| - pirate = @ship.reload.create_pirate(:catchphrase => 'Arr') - @ship.update_attributes(:pirate_attributes => { :id => pirate.id, :_destroy => truth }) + pirate = @ship.reload.create_pirate(catchphrase: 'Arr') + @ship.update(pirate_attributes: { id: pirate.id, _destroy: truth }) assert_raise(ActiveRecord::RecordNotFound) { pirate.reload } end end def test_should_unset_association_when_an_existing_record_is_destroyed original_pirate_id = @ship.pirate.id - @ship.update_attributes! pirate_attributes: { id: @ship.pirate.id, _destroy: true } + @ship.update! pirate_attributes: { id: @ship.pirate.id, _destroy: true } assert_empty Pirate.where(id: original_pirate_id) assert_nil @ship.pirate_id @@ -490,7 +490,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy [nil, '0', 0, 'false', false].each do |not_truth| - @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => not_truth }) + @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: not_truth }) assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload } end end @@ -498,14 +498,14 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? } - @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => '1' }) + @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: '1' }) assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload } ensure Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } end - def test_should_work_with_update_attributes_as_well - @ship.update_attributes({ :name => 'Mister Pablo', :pirate_attributes => { :catchphrase => 'Arr' } }) + def test_should_work_with_update_as_well + @ship.update({ name: 'Mister Pablo', pirate_attributes: { catchphrase: 'Arr' } }) @ship.reload assert_equal 'Mister Pablo', @ship.name @@ -534,18 +534,18 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def test_should_update_existing_when_update_only_is_true_and_no_id_is_given @pirate.delete - @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye') + @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye') - @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr' }) + @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr' }) assert_equal 'Arr', @pirate.reload.catchphrase assert_equal @pirate, @ship.reload.update_only_pirate end def test_should_update_existing_when_update_only_is_true_and_id_is_given @pirate.delete - @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye') + @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye') - @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id }) + @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id }) assert_equal 'Arr', @pirate.reload.catchphrase assert_equal @pirate, @ship.reload.update_only_pirate @@ -554,9 +554,9 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => true @pirate.delete - @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye') + @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye') - @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id, :_destroy => true }) + @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id, _destroy: true }) assert_raise(ActiveRecord::RecordNotFound) { @pirate.reload } @@ -582,7 +582,7 @@ module NestedAttributesOnACollectionAssociationTests def test_should_take_a_hash_with_string_keys_and_assign_the_attributes_to_the_associated_models @alternate_params[association_getter].stringify_keys! - @pirate.update_attributes @alternate_params + @pirate.update @alternate_params assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name] end @@ -593,7 +593,7 @@ module NestedAttributesOnACollectionAssociationTests end def test_should_also_work_with_a_HashWithIndifferentAccess - @pirate.send(association_setter, HashWithIndifferentAccess.new('foo' => HashWithIndifferentAccess.new(:id => @child_1.id, :name => 'Grace OMalley'))) + @pirate.send(association_setter, ActiveSupport::HashWithIndifferentAccess.new('foo' => ActiveSupport::HashWithIndifferentAccess.new(:id => @child_1.id, :name => 'Grace OMalley'))) @pirate.save assert_equal 'Grace OMalley', @child_1.reload.name end @@ -628,10 +628,10 @@ module NestedAttributesOnACollectionAssociationTests def test_should_refresh_saved_records_when_not_overwriting_unsaved_updates @pirate.reload - record = @pirate.class.reflect_on_association(@association_name).klass.new(:name => 'Grace OMalley') + record = @pirate.class.reflect_on_association(@association_name).klass.new(name: 'Grace OMalley') @pirate.send(@association_name) << record record.save! - @pirate.send(@association_name).last.update_attributes!(:name => 'Polly') + @pirate.send(@association_name).last.update!(name: 'Polly') assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name end @@ -718,17 +718,17 @@ module NestedAttributesOnACollectionAssociationTests end end - def test_should_work_with_update_attributes_as_well - @pirate.update_attributes(:catchphrase => 'Arr', + def test_should_work_with_update_as_well + @pirate.update(catchphrase: 'Arr', association_getter => { 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' }}) assert_equal 'Grace OMalley', @child_1.reload.name end def test_should_update_existing_records_and_add_new_ones_that_have_no_id - @alternate_params[association_getter]['baz'] = { :name => 'Buccaneers Servant' } + @alternate_params[association_getter]['baz'] = { name: 'Buccaneers Servant' } assert_difference('@pirate.send(@association_name).count', +1) do - @pirate.update_attributes @alternate_params + @pirate.update @alternate_params end assert_equal ['Grace OMalley', 'Privateers Greed', 'Buccaneers Servant'].to_set, @pirate.reload.send(@association_name).map(&:name).to_set end @@ -750,7 +750,7 @@ module NestedAttributesOnACollectionAssociationTests [nil, '', '0', 0, 'false', false].each do |false_variable| @alternate_params[association_getter]['foo']['_destroy'] = false_variable assert_no_difference('@pirate.send(@association_name).count') do - @pirate.update_attributes(@alternate_params) + @pirate.update(@alternate_params) end end end @@ -814,7 +814,7 @@ module NestedAttributesOnACollectionAssociationTests man = Man.create(name: 'John') interest = man.interests.create(topic: 'bar', zine_id: 0) assert interest.save - assert !man.update_attributes({interests_attributes: { id: interest.id, zine_id: 'foo' }}) + assert !man.update({interests_attributes: { id: interest.id, zine_id: 'foo' }}) end end @@ -945,18 +945,18 @@ class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase end def test_should_update_existing_records_with_non_standard_primary_key - @owner.update_attributes(@params) + @owner.update(@params) assert_equal ['Foo', 'Bar'], @owner.pets.map(&:name) end - def test_attr_accessor_of_child_should_be_value_provided_during_update_attributes + def test_attr_accessor_of_child_should_be_value_provided_during_update @owner = owners(:ashley) @pet1 = pets(:chew) attributes = {:pets_attributes => { "1"=> { :id => @pet1.id, :name => "Foo2", :current_user => "John", :_destroy=>true }}} - @owner.update_attributes(attributes) + @owner.update(attributes) assert_equal 'John', Pet.after_destroy_output end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 9e0423ab52..b936cca875 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -16,7 +16,6 @@ require 'models/person' require 'models/pet' require 'models/toy' require 'rexml/document' -require 'active_support/core_ext/exception' class PersistencesTest < ActiveRecord::TestCase @@ -243,7 +242,7 @@ class PersistencesTest < ActiveRecord::TestCase assert_equal "David", topic2.author_name end - def test_update + def test_update_object topic = Topic.new topic.title = "Another New Topic" topic.written_on = "2003-12-12 23:23:00" @@ -365,7 +364,7 @@ class PersistencesTest < ActiveRecord::TestCase client.delete assert client.frozen? assert_kind_of Firm, client.firm - assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" } + assert_raise(RuntimeError) { client.name = "something else" } end def test_destroy_new_record @@ -379,7 +378,7 @@ class PersistencesTest < ActiveRecord::TestCase client.destroy assert client.frozen? assert_kind_of Firm, client.firm - assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" } + assert_raise(RuntimeError) { client.name = "something else" } end def test_update_attribute @@ -392,7 +391,7 @@ class PersistencesTest < ActiveRecord::TestCase end def test_update_attribute_does_not_choke_on_nil - assert Topic.find(1).update_attributes(nil) + assert Topic.find(1).update(nil) end def test_update_attribute_for_readonly_attribute @@ -548,7 +547,7 @@ class PersistencesTest < ActiveRecord::TestCase def test_update_columns_should_not_leave_the_object_dirty topic = Topic.find(1) - topic.update_attributes({ "content" => "Have a nice day", :author_name => "Jose" }) + topic.update({ "content" => "Have a nice day", :author_name => "Jose" }) topic.reload topic.update_columns({ content: "You too", "author_name" => "Sebastian" }) @@ -632,6 +631,22 @@ class PersistencesTest < ActiveRecord::TestCase assert developer.update_columns(name: 'Will'), 'did not update record due to default scope' end + def test_update + topic = Topic.find(1) + assert !topic.approved? + assert_equal "The First Topic", topic.title + + topic.update("approved" => true, "title" => "The First Topic Updated") + topic.reload + assert topic.approved? + assert_equal "The First Topic Updated", topic.title + + topic.update(approved: false, title: "The First Topic") + topic.reload + assert !topic.approved? + assert_equal "The First Topic", topic.title + end + def test_update_attributes topic = Topic.find(1) assert !topic.approved? @@ -642,12 +657,33 @@ class PersistencesTest < ActiveRecord::TestCase assert topic.approved? assert_equal "The First Topic Updated", topic.title - topic.update_attributes(:approved => false, :title => "The First Topic") + topic.update_attributes(approved: false, title: "The First Topic") topic.reload assert !topic.approved? assert_equal "The First Topic", topic.title end + def test_update! + Reply.validates_presence_of(:title) + reply = Reply.find(2) + assert_equal "The Second Topic of the day", reply.title + assert_equal "Have a nice day", reply.content + + reply.update!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening") + reply.reload + assert_equal "The Second Topic of the day updated", reply.title + assert_equal "Have a nice evening", reply.content + + reply.update!(title: "The Second Topic of the day", content: "Have a nice day") + reply.reload + assert_equal "The Second Topic of the day", reply.title + assert_equal "Have a nice day", reply.content + + assert_raise(ActiveRecord::RecordInvalid) { reply.update!(title: nil, content: "Have a nice evening") } + ensure + Reply.reset_callbacks(:validate) + end + def test_update_attributes! Reply.validates_presence_of(:title) reply = Reply.find(2) @@ -659,12 +695,12 @@ class PersistencesTest < ActiveRecord::TestCase assert_equal "The Second Topic of the day updated", reply.title assert_equal "Have a nice evening", reply.content - reply.update_attributes!(:title => "The Second Topic of the day", :content => "Have a nice day") + reply.update_attributes!(title: "The Second Topic of the day", content: "Have a nice day") reply.reload assert_equal "The Second Topic of the day", reply.title assert_equal "Have a nice day", reply.content - assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") } + assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(title: nil, content: "Have a nice evening") } ensure Reply.reset_callbacks(:validate) end diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index bf8aacc363..8e5379cb1f 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -201,10 +201,10 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase end end -if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) +if current_adapter?(:MysqlAdapter, :Mysql2Adapter) class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase self.use_transactional_fixtures = false - + def test_primaery_key_method_with_ansi_quotes con = ActiveRecord::Base.connection con.execute("SET SESSION sql_mode='ANSI_QUOTES'") @@ -212,7 +212,7 @@ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) ensure con.reconnect! end - + end end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 4ff481e6a5..136fda664c 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -167,7 +167,7 @@ class QueryCacheTest < ActiveRecord::TestCase # Oracle adapter returns count() as Fixnum or Float if current_adapter?(:OracleAdapter) assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") - elsif current_adapter?(:SQLite3Adapter) || current_adapter?(:Mysql2Adapter) + elsif current_adapter?(:SQLite3Adapter, :Mysql2Adapter) # Future versions of the sqlite3 adapter will return numeric assert_instance_of Fixnum, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index 297e865308..f69a248491 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -82,6 +82,10 @@ module ActiveRecord assert_equal 0, Post.where(:posts => {}).count end + def test_where_with_table_name_and_empty_array + assert_equal 0, Post.where(:id => []).count + end + def test_where_with_empty_hash_and_no_foreign_key assert_equal 0, Edge.where(:sink => {}).count end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index d318dab1e1..7388324a0d 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -161,6 +161,28 @@ class RelationScopingTest < ActiveRecord::TestCase assert !Developer.all.where_values.include?("name = 'Jamis'") end + + def test_default_scope_filters_on_joins + assert_equal 1, DeveloperFilteredOnJoins.all.count + assert_equal DeveloperFilteredOnJoins.all.first, developers(:david).becomes(DeveloperFilteredOnJoins) + end + + def test_update_all_default_scope_filters_on_joins + DeveloperFilteredOnJoins.update_all(:salary => 65000) + assert_equal 65000, Developer.find(developers(:david).id).salary + + # has not changed jamis + assert_not_equal 65000, Developer.find(developers(:jamis).id).salary + end + + def test_delete_all_default_scope_filters_on_joins + assert_not_equal [], DeveloperFilteredOnJoins.all + + DeveloperFilteredOnJoins.delete_all() + + assert_equal [], DeveloperFilteredOnJoins.all + assert_not_equal [], Developer.all + end end class NestedRelationScopingTest < ActiveRecord::TestCase @@ -227,7 +249,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase def test_nested_exclusive_scope_for_create comment = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do Comment.unscoped.create_with(:post_id => 1).scoping do - assert_blank Comment.new.body + assert Comment.new.body.blank? Comment.create :body => "Hey guys" end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 0cd838c0b0..64f1c9f6cc 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -404,6 +404,13 @@ class RelationTest < ActiveRecord::TestCase end end + def test_preload_applies_to_all_chained_preloaded_scopes + assert_queries(3) do + post = Post.with_comments.with_tags.first + assert post + end + end + def test_find_with_included_associations assert_queries(2) do posts = Post.includes(:comments).order('posts.id') @@ -509,7 +516,7 @@ class RelationTest < ActiveRecord::TestCase def test_find_in_empty_array authors = Author.all.where(:id => []) - assert_blank authors.to_a + assert authors.to_a.blank? end def test_where_with_ar_object @@ -723,7 +730,7 @@ class RelationTest < ActiveRecord::TestCase def test_relation_merging_with_locks devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2)) - assert_present devs.locked + assert devs.locked.present? end def test_relation_merging_with_preload diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 7ff0044bd4..cae12e0e3a 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -112,7 +112,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{c_int_4.*}, output assert_no_match %r{c_int_4.*limit:}, output - elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter) assert_match %r{c_int_1.*limit: 1}, output assert_match %r{c_int_2.*limit: 2}, output assert_match %r{c_int_3.*limit: 3}, output @@ -197,7 +197,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r(primary_key: "movieid"), match[1], "non-standard primary key not preserved" end - if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_schema_dump_should_not_add_default_value_for_mysql_text_field output = standard_dump assert_match %r{t.text\s+"body",\s+null: false$}, output @@ -280,6 +280,13 @@ class SchemaDumperTest < ActiveRecord::TestCase end end + def test_schema_dump_includes_ltrees_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_ltrees"} =~ output + assert_match %r[t.ltree "path"], output + end + end + def test_schema_dump_includes_arrays_shorthand_definition output = standard_dump if %r{create_table "postgresql_arrays"} =~ output diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 562ca8d9ff..43bf285ba9 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -67,9 +67,9 @@ class StoreTest < ActiveRecord::TestCase end test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do - @john.json_data = HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy') + @john.json_data = ActiveSupport::HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy') @john.height = 'low' - assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess) + assert_equal true, @john.json_data.instance_of?(ActiveSupport::HashWithIndifferentAccess) assert_equal 'low', @john.json_data[:height] assert_equal 'low', @john.json_data['height'] assert_equal 'heavy', @john.json_data[:weight] @@ -95,7 +95,7 @@ class StoreTest < ActiveRecord::TestCase test "convert store attributes from any format other than Hash or HashWithIndifferent access losing the data" do @john.json_data = "somedata" @john.height = 'low' - assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess) + assert_equal true, @john.json_data.instance_of?(ActiveSupport::HashWithIndifferentAccess) assert_equal 'low', @john.json_data[:height] assert_equal 'low', @john.json_data['height'] assert_equal false, @john.json_data.delete_if { |k, v| k == 'height' }.any? @@ -143,5 +143,4 @@ class StoreTest < ActiveRecord::TestCase assert_raise(NoMethodError) { @john.stored_attributes = Hash.new } assert_raise(NoMethodError) { @john.stored_attributes } end - end diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 4f3489b7a5..659d5eae72 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -11,10 +11,10 @@ module ActiveRecord end ADAPTERS_TASKS = { - :mysql => :mysql_tasks, - :mysql2 => :mysql_tasks, - :postgresql => :postgresql_tasks, - :sqlite3 => :sqlite_tasks + mysql: :mysql_tasks, + mysql2: :mysql_tasks, + postgresql: :postgresql_tasks, + sqlite3: :sqlite_tasks } class DatabaseTasksRegisterTask < ActiveRecord::TestCase @@ -32,7 +32,7 @@ module ActiveRecord ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => :foo}, "awesome-file.sql") end end - + class DatabaseTasksCreateTest < ActiveRecord::TestCase include DatabaseTasksSetupper @@ -258,7 +258,7 @@ module ActiveRecord class DatabaseTasksCharsetTest < ActiveRecord::TestCase include DatabaseTasksSetupper - + ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_charset") do eval("@#{v}").expects(:charset) @@ -269,7 +269,7 @@ module ActiveRecord class DatabaseTasksCollationTest < ActiveRecord::TestCase include DatabaseTasksSetupper - + ADAPTERS_TASKS.each do |k, v| define_method("test_#{k}_collation") do eval("@#{v}").expects(:collation) diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index 69a049fcfa..38b9dd02f0 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -53,6 +53,16 @@ module ActiveRecord ActiveRecord::Tasks::DatabaseTasks.create @configuration end + + def test_create_when_database_exists_outputs_info_to_stderr + $stderr.expects(:puts).with("my-app-db already exists").once + + ActiveRecord::Base.connection.stubs(:create_database).raises( + ActiveRecord::StatementInvalid.new("Can't create database 'dev'; database exists:") + ) + + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end end class MysqlDBCreateAsRootTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb index 62acd53003..3006a87589 100644 --- a/activerecord/test/cases/tasks/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -61,6 +61,16 @@ module ActiveRecord ActiveRecord::Tasks::DatabaseTasks.create @configuration end + + def test_create_when_database_exists_outputs_info_to_stderr + $stderr.expects(:puts).with("my-app-db already exists").once + + ActiveRecord::Base.connection.stubs(:create_database).raises( + ActiveRecord::StatementInvalid.new('database "my-app-db" already exists') + ) + + ActiveRecord::Tasks::DatabaseTasks.create @configuration + end end class PostgreSQLDBDropTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb index a396da6645..4f1cb99b68 100644 --- a/activerecord/test/cases/transaction_isolation_test.rb +++ b/activerecord/test/cases/transaction_isolation_test.rb @@ -77,7 +77,7 @@ class TransactionIsolationTest < ActiveRecord::TestCase Tag.transaction(isolation: :repeatable_read) do tag.reload - Tag2.find(tag.id).update_attributes(name: 'emily') + Tag2.find(tag.id).update(name: 'emily') tag.reload assert_equal 'jon', tag.name diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 0c8b372e79..9d278480ef 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -117,21 +117,21 @@ class TransactionTest < ActiveRecord::TestCase assert !Topic.find(1).approved? end - def test_update_attributes_should_rollback_on_failure + def test_update_should_rollback_on_failure author = Author.find(1) posts_count = author.posts.size assert posts_count > 0 - status = author.update_attributes(:name => nil, :post_ids => []) + status = author.update(name: nil, post_ids: []) assert !status assert_equal posts_count, author.posts(true).size end - def test_update_attributes_should_rollback_on_failure! + def test_update_should_rollback_on_failure! author = Author.find(1) posts_count = author.posts.size assert posts_count > 0 assert_raise(ActiveRecord::RecordInvalid) do - author.update_attributes!(:name => nil, :post_ids => []) + author.update!(name: nil, post_ids: []) end assert_equal posts_count, author.posts(true).size end @@ -451,6 +451,26 @@ class TransactionTest < ActiveRecord::TestCase end end + def test_transactions_state_from_rollback + connection = Topic.connection + transaction = ActiveRecord::ConnectionAdapters::ClosedTransaction.new(connection).begin + + assert transaction.open? + transaction.perform_rollback + + assert transaction.rolledback? + end + + def test_transactions_state_from_commit + connection = Topic.connection + transaction = ActiveRecord::ConnectionAdapters::ClosedTransaction.new(connection).begin + + assert transaction.open? + transaction.perform_commit + + assert transaction.committed? + end + private %w(validation save destroy).each do |filter| diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 683cb54a10..81bc87bd42 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -101,6 +101,15 @@ class DeveloperWithIncludes < ActiveRecord::Base default_scope { includes(:audit_logs) } end +class DeveloperFilteredOnJoins < ActiveRecord::Base + self.table_name = 'developers' + has_and_belongs_to_many :projects, -> { order('projects.id') }, :foreign_key => 'developer_id', :join_table => 'developers_projects' + + def self.default_scope + joins(:projects).where(:projects => { :name => 'Active Controller' }) + end +end + class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = 'developers' default_scope { order('salary DESC') } diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index c995f59a15..603f1f2555 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -5,6 +5,12 @@ class Post < ActiveRecord::Base end end + module NamedExtension2 + def greeting + "hello" + end + end + scope :containing_the_letter_a, -> { where("body LIKE '%a%'") } scope :ranked_by_comments, -> { order("comments_count DESC") } @@ -29,6 +35,9 @@ class Post < ActiveRecord::Base scope :with_very_special_comments, -> { joins(:comments).where(:comments => {:type => 'VerySpecialComment'}) } scope :with_post, ->(post_id) { joins(:comments).where(:comments => { :post_id => post_id }) } + scope :with_comments, -> { preload(:comments) } + scope :with_tags, -> { preload(:taggings) } + has_many :comments do def find_most_recent order("id DESC").first @@ -43,6 +52,14 @@ class Post < ActiveRecord::Base end end + has_many :comments_with_extend, extend: NamedExtension, class_name: "Comment", foreign_key: "post_id" do + def greeting + "hello" + end + end + + has_many :comments_with_extend_2, extend: [NamedExtension, NamedExtension2], class_name: "Comment", foreign_key: "post_id" + has_many :author_favorites, :through => :author has_many :author_categorizations, :through => :author, :source => :categorizations has_many :author_addresses, :through => :author diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb index 1636c118a2..c2f9068f57 100644 --- a/activerecord/test/models/reference.rb +++ b/activerecord/test/models/reference.rb @@ -11,7 +11,7 @@ class Reference < ActiveRecord::Base def make_comments if self.class.make_comments - person.update_attributes :comments => "Reference destroyed" + person.update comments: "Reference destroyed" end end end diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb index 079e325aad..c88262580e 100644 --- a/activerecord/test/models/reply.rb +++ b/activerecord/test/models/reply.rb @@ -1,8 +1,6 @@ require 'models/topic' class Reply < Topic - scope :base, -> { scoped } - belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count" has_many :replies, :class_name => "SillyReply", :dependent => :destroy, :foreign_key => "parent_id" diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 96fef3a831..ae13f2cd8a 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,6 +1,6 @@ ActiveRecord::Schema.define do - %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids + %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type postgresql_intrange_data_type).each do |table_name| execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" end @@ -89,6 +89,15 @@ _SQL _SQL end + if 't' == select_value("select 'ltree'=ANY(select typname from pg_type)") + execute <<_SQL + CREATE TABLE postgresql_ltrees ( + id SERIAL PRIMARY KEY, + path ltree + ); +_SQL + end + if 't' == select_value("select 'json'=ANY(select typname from pg_type)") execute <<_SQL CREATE TABLE postgresql_json_data_type ( diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index fb975dcfc3..72f28aefc7 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,6 +1,55 @@ ## Rails 4.0.0 (unreleased) ## -* Introduce assert_not to replace warty 'assert !foo'. *Jeremy Kemper* +* 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. + + 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. + + CVE-2013-0156 + + *Jeremy Kemper* + +* Deprecate `assert_present` and `assert_blank` in favor of + `assert object.blank?` and `assert object.present?` + + *Yves Senn* + +* Change `String#to_date` to use `Date.parse`. This gives more consistent error + messages and allows the use of partial dates. + + "gibberish".to_date => Argument Error: invalid date + "3rd Feb".to_date => Sun, 03 Feb 2013 + + *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. + Example: + + range = Range.new(Date.today, Float::INFINITY) + + Also it's possible to check inclusion of date/time in range with conversion. + + range.include?(Time.now + 1.year) # => true + range.include?(DateTime.now + 1.year) # => true + + *Alexander Grebennik* + +* Remove meaningless `ActiveSupport::FrozenObjectError`, which was just an alias of `RuntimeError`. + + *Akira Matsuda* + +* Introduce `assert_not` to replace warty `assert !foo`. *Jeremy Kemper* * Prevent `Callbacks#set_callback` from setting the same callback twice. @@ -11,7 +60,7 @@ *Dmitriy Kiriyenko* -* Add ActiveSupport::Logger#silence that works the same as the old Logger#silence extension. +* Add `ActiveSupport::Logger#silence` that works the same as the old `Logger#silence` extension. *DHH* diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index f1aff8a8e3..4b41e6247d 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -72,6 +72,9 @@ module ActiveSupport @silencers = [] end + # Removes all filters, but leaves in silencers. Useful if you suddenly + # need to see entire filepaths in the backtrace that you had already + # filtered out. def remove_filters! @filters = [] end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index fdec2de1d5..edbe697962 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -3,7 +3,6 @@ require 'zlib' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/benchmark' -require 'active_support/core_ext/exception' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/numeric/bytes' require 'active_support/core_ext/numeric/time' @@ -276,34 +275,14 @@ module ActiveSupport if block_given? options = merged_options(options) key = namespaced_key(name, options) - unless options[:force] - entry = instrument(:read, name, options) do |payload| - payload[:super_operation] = :fetch if payload - read_entry(key, options) - end - end - if entry && entry.expired? - race_ttl = options[:race_condition_ttl].to_i - if race_ttl && (Time.now - entry.expires_at <= race_ttl) - # When an entry has :race_condition_ttl defined, put the stale entry back into the cache - # for a brief period while the entry is begin recalculated. - entry.expires_at = Time.now + race_ttl - write_entry(key, entry, :expires_in => race_ttl * 2) - else - delete_entry(key, options) - end - entry = nil - end + + cached_entry = find_cached_entry(key, name, options) unless options[:force] + entry = handle_expired_entry(cached_entry, key, options) if entry - instrument(:fetch_hit, name, options) { |payload| } - entry.value + get_entry_value(entry, name, options) else - result = instrument(:generate, name, options) do |payload| - yield(name) - end - write(name, result, options) - result + save_block_result_to_cache(name, options) { |_name| yield _name } end else read(name, options) @@ -532,6 +511,42 @@ module ActiveSupport return unless logger && logger.debug? && !silence? logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}") end + + def find_cached_entry(key, name, options) + instrument(:read, name, options) do |payload| + payload[:super_operation] = :fetch if payload + read_entry(key, options) + end + end + + def handle_expired_entry(entry, key, options) + if entry && entry.expired? + race_ttl = options[:race_condition_ttl].to_i + if race_ttl && (Time.now - entry.expires_at <= race_ttl) + # When an entry has :race_condition_ttl defined, put the stale entry back into the cache + # for a brief period while the entry is begin recalculated. + entry.expires_at = Time.now + race_ttl + write_entry(key, entry, :expires_in => race_ttl * 2) + else + delete_entry(key, options) + end + entry = nil + end + entry + end + + def get_entry_value(entry, name, options) + instrument(:fetch_hit, name, options) { |payload| } + entry.value + end + + def save_block_result_to_cache(name, options) + result = instrument(:generate, name, options) do |payload| + yield(name) + end + write(name, result, options) + result + end end # This class is used to represent cache entries. Cache entries have a value and an optional diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index a8f9dddae5..4f1e432b61 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -21,28 +21,28 @@ class Array # Equal to <tt>self[1]</tt>. # - # %w( a b c d e).second # => "b" + # %w( a b c d e ).second # => "b" def second self[1] end # Equal to <tt>self[2]</tt>. # - # %w( a b c d e).third # => "c" + # %w( a b c d e ).third # => "c" def third self[2] end # Equal to <tt>self[3]</tt>. # - # %w( a b c d e).fourth # => "d" + # %w( a b c d e ).fourth # => "d" def fourth self[3] end # Equal to <tt>self[4]</tt>. # - # %w( a b c d e).fifth # => "e" + # %w( a b c d e ).fifth # => "e" def fifth self[4] end diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 64e9945ef5..430a35fbaf 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -8,7 +8,7 @@ class Array # Converts the array to a comma-separated sentence where the last element is # joined by the connector word. # - # You can pass the following options to change the default behaviour. If you + # You can pass the following options to change the default behavior. If you # pass an option key that doesn't exist in the list below, it will raise an # <tt>ArgumentError</tt>. # diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb index 465fedda80..5f13f5f70f 100644 --- a/activesupport/lib/active_support/core_ext/date.rb +++ b/activesupport/lib/active_support/core_ext/date.rb @@ -2,4 +2,5 @@ require 'active_support/core_ext/date/acts_like' require 'active_support/core_ext/date/calculations' require 'active_support/core_ext/date/conversions' require 'active_support/core_ext/date/zones' +require 'active_support/core_ext/date/infinite_comparable' diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 421aa12100..106a65610c 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -8,8 +8,6 @@ require 'active_support/core_ext/date_and_time/calculations' class Date include DateAndTime::Calculations - @beginning_of_week_default = nil - class << self attr_accessor :beginning_of_week_default diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index fe08ade7e0..cdf606f28c 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -17,10 +17,10 @@ class Date } # Ruby 1.9 has Date#to_time which converts to localtime only. - remove_possible_method :to_time + remove_method :to_time # Ruby 1.9 has Date#xmlschema which converts to a string without the time component. - remove_possible_method :xmlschema + remove_method :xmlschema # Convert to a formatted string. See DATE_FORMATS for predefined formats. # diff --git a/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb new file mode 100644 index 0000000000..ca5d793942 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb @@ -0,0 +1,5 @@ +require 'active_support/core_ext/infinite_comparable' + +class Date + include InfiniteComparable +end diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb index e8a27b9f38..024af91738 100644 --- a/activesupport/lib/active_support/core_ext/date_time.rb +++ b/activesupport/lib/active_support/core_ext/date_time.rb @@ -2,3 +2,4 @@ require 'active_support/core_ext/date_time/acts_like' require 'active_support/core_ext/date_time/calculations' require 'active_support/core_ext/date_time/conversions' require 'active_support/core_ext/date_time/zones' +require 'active_support/core_ext/date_time/infinite_comparable' 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 fca5d4d679..4e4852a5e6 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -142,11 +142,4 @@ class DateTime def utc_offset (offset * 86400).to_i end - - # Layers additional behavior on DateTime#<=> so that Time and - # ActiveSupport::TimeWithZone instances can be compared with a DateTime. - def <=>(other) - super other.to_datetime - end - end diff --git a/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb new file mode 100644 index 0000000000..8a282b19f2 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb @@ -0,0 +1,5 @@ +require 'active_support/core_ext/infinite_comparable' + +class DateTime + include InfiniteComparable +end diff --git a/activesupport/lib/active_support/core_ext/exception.rb b/activesupport/lib/active_support/core_ext/exception.rb deleted file mode 100644 index ba7757ea07..0000000000 --- a/activesupport/lib/active_support/core_ext/exception.rb +++ /dev/null @@ -1,3 +0,0 @@ -module ActiveSupport - FrozenObjectError = RuntimeError -end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 6cb7434e5f..8930376ac8 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -101,17 +101,33 @@ class Hash # # hash = Hash.from_xml(xml) # # => {"hash"=>{"foo"=>1, "bar"=>2}} - def from_xml(xml) - ActiveSupport::XMLConverter.new(xml).to_h + # + # DisallowedType is raise if the XML contains attributes with <tt>type="yaml"</tt> or + # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML. + def from_xml(xml, disallowed_types = nil) + ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h end + # Builds a Hash from XML just like <tt>Hash.from_xml</tt>, but also allows Symbol and YAML. + def from_trusted_xml(xml) + from_xml xml, [] + end end end module ActiveSupport class XMLConverter # :nodoc: - def initialize(xml) + class DisallowedType < StandardError + def initialize(type) + super "Disallowed type attribute: #{type.inspect}" + end + end + + DISALLOWED_TYPES = %w(symbol yaml) + + def initialize(xml, disallowed_types = nil) @xml = normalize_keys(XmlMini.parse(xml)) + @disallowed_types = disallowed_types || DISALLOWED_TYPES end def to_h @@ -119,7 +135,6 @@ module ActiveSupport end private - def normalize_keys(params) case params when Hash @@ -145,6 +160,10 @@ module ActiveSupport end def process_hash(value) + if value.include?('type') && !value['type'].is_a?(Hash) && @disallowed_types.include?(value['type']) + raise DisallowedType, value['type'] + end + if become_array?(value) _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) }) if entries.nil? || value['__content__'].try(:empty?) diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb index 5cb00d0ebd..d90e996ad4 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -2,7 +2,7 @@ class Hash # Return a hash that includes everything but the given keys. This is useful for # limiting a set of parameters to everything but a few known toggles: # - # @person.update_attributes(params[:person].except(:admin)) + # @person.update(params[:person].except(:admin)) def except(*keys) dup.except!(*keys) end diff --git a/activesupport/lib/active_support/core_ext/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/infinite_comparable.rb new file mode 100644 index 0000000000..b78b2deaad --- /dev/null +++ b/activesupport/lib/active_support/core_ext/infinite_comparable.rb @@ -0,0 +1,35 @@ +require 'active_support/concern' +require 'active_support/core_ext/module/aliasing' +require 'active_support/core_ext/object/try' + +module InfiniteComparable + extend ActiveSupport::Concern + + included do + alias_method_chain :<=>, :infinity + end + + define_method :'<=>_with_infinity' do |other| + if other.class == self.class + public_send :'<=>_without_infinity', other + else + infinite = try(:infinite?) + other_infinite = other.try(:infinite?) + + # inf <=> inf + if infinite && other_infinite + infinite <=> other_infinite + # not_inf <=> inf + elsif other_infinite + -other_infinite + # inf <=> not_inf + elsif infinite + infinite + else + conversion = "to_#{self.class.name.downcase}" + other = other.public_send(conversion) if other.respond_to?(conversion) + public_send :'<=>_without_infinity', other + end + end + end +end diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 7b518821c8..79d3303b41 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -59,10 +59,9 @@ module Kernel # # puts 'This code gets executed and nothing related to ZeroDivisionError was seen' def suppress(*exception_classes) - begin yield - rescue Exception => e - raise unless exception_classes.any? { |cls| e.kind_of?(cls) } - end + yield + rescue Exception => e + raise unless exception_classes.any? { |cls| e.kind_of?(cls) } end # Captures the given stream and returns it: diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb index fec3051c0c..c7a8348b1d 100644 --- a/activesupport/lib/active_support/core_ext/marshal.rb +++ b/activesupport/lib/active_support/core_ext/marshal.rb @@ -1,21 +1,19 @@ module Marshal class << self def load_with_autoloading(source) - begin - load_without_autoloading(source) - rescue ArgumentError, NameError => exc - if exc.message.match(%r|undefined class/module (.+)|) - # try loading the class/module - $1.constantize - # if it is a IO we need to go back to read the object - source.rewind if source.respond_to?(:rewind) - retry - else - raise exc - end + load_without_autoloading(source) + rescue ArgumentError, NameError => exc + if exc.message.match(%r|undefined class/module (.+)|) + # try loading the class/module + $1.constantize + # if it is a IO we need to go back to read the object + source.rewind if source.respond_to?(:rewind) + retry + else + raise exc end end alias_method_chain :load, :autoloading end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb index a6bc0624be..d5cfc2ece4 100644 --- a/activesupport/lib/active_support/core_ext/numeric.rb +++ b/activesupport/lib/active_support/core_ext/numeric.rb @@ -1,3 +1,4 @@ require 'active_support/core_ext/numeric/bytes' require 'active_support/core_ext/numeric/time' require 'active_support/core_ext/numeric/conversions' +require 'active_support/core_ext/numeric/infinite_comparable' diff --git a/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb new file mode 100644 index 0000000000..b5f1b0487b --- /dev/null +++ b/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb @@ -0,0 +1,9 @@ +require 'active_support/core_ext/infinite_comparable' + +class Float + include InfiniteComparable +end + +class BigDecimal + include InfiniteComparable +end diff --git a/activesupport/lib/active_support/core_ext/object/acts_like.rb b/activesupport/lib/active_support/core_ext/object/acts_like.rb index fcc8e50f06..3912cc5ace 100644 --- a/activesupport/lib/active_support/core_ext/object/acts_like.rb +++ b/activesupport/lib/active_support/core_ext/object/acts_like.rb @@ -1,9 +1,9 @@ class Object # A duck-type assistant method. For example, Active Support extends Date - # to define an acts_like_date? method, and extends Time to define - # acts_like_time?. As a result, we can do "x.acts_like?(:time)" and - # "x.acts_like?(:date)" to do duck-type-safe comparisons, since classes that - # we want to act like Time simply need to define an acts_like_time? method. + # to define an <tt>acts_like_date?</tt> method, and extends Time to define + # <tt>acts_like_time?</tt>. As a result, we can do <tt>x.acts_like?(:time)</tt> and + # <tt>x.acts_like?(:date)</tt> to do duck-type-safe comparisons, since classes that + # we want to act like Time simply need to define an <tt>acts_like_time?</tt> method. def acts_like?(duck) respond_to? :"acts_like_#{duck}?" end diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb index 40821fd619..755e1c6b16 100644 --- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb +++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb @@ -13,7 +13,7 @@ class Object Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }] end - # Returns an array of instance variable names including "@". + # Returns an array of instance variable names as strings including "@". # # class C # def initialize(x, y) diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index 9d3b81cf38..428fa1f826 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -3,26 +3,36 @@ require 'active_support/core_ext/time/calculations' class String # Converts a string to a Time value. - # The +form+ can be either :utc or :local (default :utc). + # The +form+ can be either :utc or :local (default :local). # - # The time is parsed using Date._parse method. - # If +form+ is :local, then time is formatted using Time.zone + # The time is parsed using Time.parse method. + # If +form+ is :local, then the time is in the system timezone. + # If the date part is missing then the current date is used and if + # the time part is missing then it is assumed to be 00:00:00. # - # "3-2-2012".to_time # => 2012-02-03 00:00:00 UTC - # "12:20".to_time # => ArgumentError: invalid date - # "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 UTC - # "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 UTC - # "2012-12-13T06:12".to_time(:local) # => 2012-12-13 06:12:00 +0100 - def to_time(form = :utc) - unless blank? - date_values = ::Date._parse(self, false). - values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset). - map! { |arg| arg || 0 } - date_values[6] *= 1000000 - offset = date_values.pop + # "13-12-2012".to_time # => 2012-12-13 00:00:00 +0100 + # "06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 05:12:00 UTC + def to_time(form = :local) + parts = Date._parse(self, false) + return if parts.empty? - ::Time.send(form, *date_values) - offset - end + now = Time.now + offset = parts[:offset] + utc_offset = form == :utc ? 0 : now.utc_offset + adjustment = offset ? offset - utc_offset : 0 + + Time.send( + form, + parts.fetch(:year, now.year), + parts.fetch(:mon, now.month), + parts.fetch(:mday, now.day), + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0) + ) - adjustment end # Converts a string to a Date value. @@ -32,11 +42,7 @@ class String # "2012-12-13".to_date #=> Thu, 13 Dec 2012 # "12/13/2012".to_date #=> ArgumentError: invalid date def to_date - unless blank? - date_values = ::Date._parse(self, false).values_at(:year, :mon, :mday) - - ::Date.new(*date_values) - end + ::Date.parse(self, false) unless blank? end # Converts a string to a DateTime value. @@ -46,13 +52,6 @@ class String # "2012-12-13 12:50".to_datetime #=> Thu, 13 Dec 2012 12:50:00 +0000 # "12/13/2012".to_datetime #=> ArgumentError: invalid date def to_datetime - unless blank? - date_values = ::Date._parse(self, false). - values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction). - map! { |arg| arg || 0 } - date_values[5] += date_values.pop - - ::DateTime.civil(*date_values) - end + ::DateTime.parse(self, false) unless blank? end end diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb index 32cffe237d..af6b589b71 100644 --- a/activesupport/lib/active_support/core_ext/time.rb +++ b/activesupport/lib/active_support/core_ext/time.rb @@ -3,3 +3,4 @@ require 'active_support/core_ext/time/calculations' require 'active_support/core_ext/time/conversions' require 'active_support/core_ext/time/marshal' require 'active_support/core_ext/time/zones' +require 'active_support/core_ext/time/infinite_comparable' diff --git a/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb new file mode 100644 index 0000000000..63795885f5 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb @@ -0,0 +1,5 @@ +require 'active_support/core_ext/infinite_comparable' + +class Time + include InfiniteComparable +end diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 796c5f9805..139d48f59c 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -1,8 +1,6 @@ require 'active_support/time_with_zone' class Time - @zone_default = nil - class << self attr_accessor :zone_default diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb index 9fc58a338f..c0dba5f7fd 100644 --- a/activesupport/lib/active_support/dependencies/autoload.rb +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -34,7 +34,7 @@ module ActiveSupport def autoload(const_name, path = @_at_path) unless path - full = [name, @_under_path, const_name.to_s, path].compact.join("::") + full = [name, @_under_path, const_name.to_s].compact.join("::") path = Inflector.underscore(full) end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 1eb2b4212b..39648727fd 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -266,14 +266,12 @@ module ActiveSupport # 'UnknownModule'.safe_constantize # => nil # 'UnknownModule::Foo::Bar'.safe_constantize # => nil def safe_constantize(camel_cased_word) - begin - constantize(camel_cased_word) - rescue NameError => e - raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ || - e.name.to_s == camel_cased_word.to_s - rescue ArgumentError => e - raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/ - end + constantize(camel_cased_word) + rescue NameError => e + raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ || + e.name.to_s == camel_cased_word.to_s + rescue ArgumentError => e + raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/ end # Returns the suffix that should be added to a number to denote the position diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 832d1ce6d5..9bf1ea35b3 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -235,7 +235,7 @@ class BigDecimal # real value. # # Use <tt>ActiveSupport.use_standard_json_big_decimal_format = true</tt> to - # override this behaviour. + # override this behavior. def as_json(options = nil) #:nodoc: if finite? ActiveSupport.encode_big_decimal_as_string ? to_s : self diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml index f4900dc935..a4563ace8f 100644 --- a/activesupport/lib/active_support/locale/en.yml +++ b/activesupport/lib/active_support/locale/en.yml @@ -16,9 +16,9 @@ en: abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] # Used in date_select and datetime_select. order: - - :year - - :month - - :day + - year + - month + - day time: formats: diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index a58afc6b9d..21a04a9152 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -53,7 +53,9 @@ module ActiveSupport class << self def logger - @logger ||= Rails.logger if defined?(Rails) + if defined?(Rails) && Rails.respond_to?(:logger) + @logger ||= Rails.logger + end @logger end diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 63dad7e01a..f9a98686d3 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -15,7 +15,7 @@ module ActiveSupport # end # # def test_basic_query_logging - # Developer.all + # Developer.all.to_a # wait # assert_equal 1, @logger.logged(:debug).size # assert_match(/Developer Load/, @logger.logged(:debug).last) diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index ab0b162ee0..1ee7ca06bb 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -7,7 +7,7 @@ module ActiveSupport attr_reader :id def initialize(notifier) - @id = unique_id + @id = unique_id @notifier = notifier end @@ -15,21 +15,32 @@ module ActiveSupport # and publish it. Notice that events get sent even if an error occurs # in the passed-in block. def instrument(name, payload={}) - @notifier.start(name, @id, payload) + start name, payload begin yield rescue Exception => e payload[:exception] = [e.class.name, e.message] raise e ensure - @notifier.finish(name, @id, payload) + finish name, payload end end + # Send a start notification with +name+ and +payload+. + def start(name, payload) + @notifier.start name, @id, payload + end + + # Send a finish notification with +name+ and +payload+. + def finish(name, payload) + @notifier.finish name, @id, payload + end + private - def unique_id - SecureRandom.hex(10) - end + + def unique_id + SecureRandom.hex(10) + end end class Event diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 133aa6a054..72ac597d99 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -13,20 +13,6 @@ module ActiveSupport end end - # Sets the default value for Time.zone - # If assigned value cannot be matched to a TimeZone, an exception will be raised. - initializer "active_support.initialize_time_zone" do |app| - require 'active_support/core_ext/time/zones' - zone_default = Time.find_zone!(app.config.time_zone) - - unless zone_default - raise 'Value assigned to config.time_zone not recognized. ' \ - 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.' - end - - Time.zone_default = zone_default - end - # Sets the default week start # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised. initializer "active_support.initialize_beginning_of_week" do |app| @@ -42,5 +28,21 @@ module ActiveSupport ActiveSupport.send(k, v) if ActiveSupport.respond_to? k end end + + # Sets the default value for Time.zone after initialization since the default configuration + # lives in application initializers. + # If assigned value cannot be matched to a TimeZone, an exception will be raised. + config.after_initialize do |app| + require 'active_support/core_ext/time/zones' + zone_default = Time.find_zone!(app.config.time_zone) + + unless zone_default + raise 'Value assigned to config.time_zone not recognized. ' \ + 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.' + end + + Time.zone_default = zone_default + end + end end diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 88aebba5c5..175f7ffe5a 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -103,6 +103,7 @@ module ActiveSupport # # assert_blank [], 'this should be blank' def assert_blank(object, message=nil) + ActiveSupport::Deprecation.warn('"assert_blank" is deprecated. Please use "assert object.blank?" instead') message ||= "#{object.inspect} is not blank" assert object.blank?, message end @@ -117,6 +118,7 @@ module ActiveSupport # # assert_present({ data: 'x' }, 'this should not be blank') def assert_present(object, message=nil) + ActiveSupport::Deprecation.warn('"assert_present" is deprecated. Please use "assert object.present?" instead') message ||= "#{object.inspect} is blank" assert object.present?, message end diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 27d444fd91..e4f8959e7a 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -12,8 +12,8 @@ module ActiveSupport end class ProxyTestResult - def initialize - @calls = [] + def initialize(calls = []) + @calls = calls end def add_error(e) @@ -27,6 +27,14 @@ module ActiveSupport end end + def marshal_dump + @calls + end + + def marshal_load(calls) + initialize(calls) + end + def method_missing(name, *args) @calls << [name, args] end @@ -35,32 +43,36 @@ module ActiveSupport module Isolation require 'thread' - class ParallelEach - include Enumerable + # 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 + # default to 2 cores + CORES = (ENV['TEST_CORES'] || 2).to_i - def initialize list - @list = list - @queue = SizedQueue.new CORES - end + def initialize list + @list = list + @queue = SizedQueue.new CORES + end - def grep pattern - self.class.new super - end + def grep pattern + self.class.new super + end - def each - threads = CORES.times.map { - Thread.new { - while job = @queue.pop - yield job - 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) + @list.each { |i| @queue << i } + CORES.times { @queue << nil } + threads.each(&:join) + end end end @@ -76,10 +88,14 @@ module ActiveSupport !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/)) end + @@class_setup_mutex = Mutex.new + def _run_class_setup # class setup method should only happen in parent - unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST'] - self.class.setup if self.class.respond_to?(:setup) - @@ran_class_setup = true + @@class_setup_mutex.synchronize do + unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST'] + self.class.setup if self.class.respond_to?(:setup) + @@ran_class_setup = true + end end end diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb deleted file mode 100644 index 7102ffe2ed..0000000000 --- a/activesupport/lib/active_support/testing/performance.rb +++ /dev/null @@ -1,271 +0,0 @@ -require 'fileutils' -require 'active_support/concern' -require 'active_support/core_ext/class/delegating_attributes' -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/module/delegation' -require 'active_support/number_helper' - -module ActiveSupport - module Testing - module Performance - extend ActiveSupport::Concern - - included do - superclass_delegating_accessor :profile_options - self.profile_options = {} - end - - # each implementation should define metrics and freeze the defaults - DEFAULTS = - if ARGV.include?('--benchmark') # HAX for rake test - { :runs => 4, - :output => 'tmp/performance', - :benchmark => true } - else - { :runs => 1, - :output => 'tmp/performance', - :benchmark => false } - end - - def full_profile_options - DEFAULTS.merge(profile_options) - end - - def full_test_name - "#{self.class.name}##{method_name}" - end - - def run(runner) - @runner = runner - - run_warmup - if full_profile_options && metrics = full_profile_options[:metrics] - metrics.each do |metric_name| - if klass = Metrics[metric_name.to_sym] - run_profile(klass.new) - end - end - end - - return - end - - def run_test(metric, mode) - result = '.' - begin - run_callbacks :setup - setup - metric.send(mode) { __send__ method_name } - rescue Exception => e - result = @runner.puke(self.class, method_name, e) - ensure - begin - teardown - run_callbacks :teardown - rescue Exception => e - result = @runner.puke(self.class, method_name, e) - end - end - result - end - - protected - # overridden by each implementation. - def run_gc; end - - def run_warmup - run_gc - - time = Metrics::Time.new - run_test(time, :benchmark) - puts "%s (%s warmup)" % [full_test_name, time.format(time.total)] - - run_gc - end - - def run_profile(metric) - klass = full_profile_options[:benchmark] ? Benchmarker : Profiler - performer = klass.new(self, metric) - - performer.run - puts performer.report - performer.record - end - - class Performer - delegate :run_test, :full_profile_options, :full_test_name, :to => :@harness - - def initialize(harness, metric) - @harness, @metric, @supported = harness, metric, false - end - - def report - if @supported - rate = @total / full_profile_options[:runs] - '%20s: %s' % [@metric.name, @metric.format(rate)] - else - '%20s: unsupported' % @metric.name - end - end - - protected - def output_filename - "#{full_profile_options[:output]}/#{full_test_name}_#{@metric.name}" - end - end - - # overridden by each implementation. - class Profiler < Performer - def time_with_block - before = Time.now - yield - Time.now - before - end - - def run; end - def record; end - end - - class Benchmarker < Performer - def initialize(*args) - super - @supported = @metric.respond_to?('measure') - end - - def run - return unless @supported - - full_profile_options[:runs].to_i.times { run_test(@metric, :benchmark) } - @total = @metric.total - end - - def record - avg = @metric.total / full_profile_options[:runs].to_i - now = Time.now.utc.xmlschema - with_output_file do |file| - file.puts "#{avg},#{now},#{environment}" - end - end - - def environment - @env ||= [].tap do |env| - env << "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/ - env << rails_version if defined?(Rails::VERSION::STRING) - env << "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" - env << RUBY_PLATFORM - end.join(',') - end - - protected - if defined?(Rails::VERSION::STRING) - HEADER = 'measurement,created_at,app,rails,ruby,platform' - else - HEADER = 'measurement,created_at,app,ruby,platform' - end - - def with_output_file - fname = output_filename - - if new = !File.exist?(fname) - FileUtils.mkdir_p(File.dirname(fname)) - end - - File.open(fname, 'ab') do |file| - file.puts(HEADER) if new - yield file - end - end - - def output_filename - "#{super}.csv" - end - - def rails_version - "rails-#{Rails::VERSION::STRING}#{rails_branch}" - end - - def rails_branch - if File.directory?('vendor/rails/.git') - Dir.chdir('vendor/rails') do - ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/ - end - end - end - end - - module Metrics - def self.[](name) - const_get(name.to_s.camelize) - rescue NameError - nil - end - - class Base - include ActiveSupport::NumberHelper - - attr_reader :total - - def initialize - @total = 0 - end - - def name - @name ||= self.class.name.demodulize.underscore - end - - def benchmark - with_gc_stats do - before = measure - yield - @total += (measure - before) - end - end - - # overridden by each implementation. - def profile; end - - protected - # overridden by each implementation. - def with_gc_stats; end - end - - class Time < Base - def measure - ::Time.now.to_f - end - - def format(measurement) - if measurement < 1 - '%d ms' % (measurement * 1000) - else - '%.2f sec' % measurement - end - end - end - - class Amount < Base - def format(measurement) - number_to_delimited(measurement.floor) - end - end - - class DigitalInformationUnit < Base - def format(measurement) - number_to_human_size(measurement, :precision => 2) - end - end - - # each implementation provides its own metrics like ProcessTime, Memory or GcRuns - end - end - end -end - -case RUBY_ENGINE - when 'ruby' then require 'active_support/testing/performance/ruby' - when 'rbx' then require 'active_support/testing/performance/rubinius' - when 'jruby' then require 'active_support/testing/performance/jruby' - else - $stderr.puts 'Your ruby interpreter is not supported for benchmarking.' - exit -end diff --git a/activesupport/lib/active_support/testing/performance/jruby.rb b/activesupport/lib/active_support/testing/performance/jruby.rb deleted file mode 100644 index 34e3f9f45f..0000000000 --- a/activesupport/lib/active_support/testing/performance/jruby.rb +++ /dev/null @@ -1,115 +0,0 @@ -require 'jruby/profiler' -require 'java' -java_import java.lang.management.ManagementFactory - -module ActiveSupport - module Testing - module Performance - DEFAULTS.merge!( - if ARGV.include?('--benchmark') - {:metrics => [:wall_time, :user_time, :memory, :gc_runs, :gc_time]} - else - { :metrics => [:wall_time], - :formats => [:flat, :graph] } - end).freeze - - protected - def run_gc - ManagementFactory.memory_mx_bean.gc - end - - class Profiler < Performer - def initialize(*args) - super - @supported = @metric.is_a?(Metrics::WallTime) - end - - def run - return unless @supported - - @total = time_with_block do - @data = JRuby::Profiler.profile do - full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } - end - end - end - - def record - return unless @supported - - klasses = full_profile_options[:formats].map { |f| JRuby::Profiler.const_get("#{f.to_s.camelize}ProfilePrinter") }.compact - - klasses.each do |klass| - fname = output_filename(klass) - FileUtils.mkdir_p(File.dirname(fname)) - File.open(fname, 'wb') do |file| - klass.new(@data).printProfile(file) - end - end - end - - protected - def output_filename(printer_class) - suffix = - case printer_class.name.demodulize - when 'FlatProfilePrinter'; 'flat.txt' - when 'GraphProfilePrinter'; 'graph.txt' - else printer_class.name.sub(/ProfilePrinter$/, '').underscore - end - - "#{super()}_#{suffix}" - end - end - - module Metrics - class Base - def profile - yield - end - - protected - def with_gc_stats - ManagementFactory.memory_mx_bean.gc - yield - end - end - - class WallTime < Time - def measure - super - end - end - - class CpuTime < Time - def measure - ManagementFactory.thread_mx_bean.get_current_thread_cpu_time / 1000 / 1000 / 1000.0 # seconds - end - end - - class UserTime < Time - def measure - ManagementFactory.thread_mx_bean.get_current_thread_user_time / 1000 / 1000 / 1000.0 # seconds - end - end - - class Memory < DigitalInformationUnit - def measure - ManagementFactory.memory_mx_bean.non_heap_memory_usage.used + ManagementFactory.memory_mx_bean.heap_memory_usage.used - end - end - - class GcRuns < Amount - def measure - ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_runs, current_gc| total_runs += current_gc.collection_count } - end - end - - class GcTime < Time - def measure - ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_time, current_gc| total_time += current_gc.collection_time } / 1000.0 # seconds - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/testing/performance/rubinius.rb b/activesupport/lib/active_support/testing/performance/rubinius.rb deleted file mode 100644 index d9ebfbe352..0000000000 --- a/activesupport/lib/active_support/testing/performance/rubinius.rb +++ /dev/null @@ -1,113 +0,0 @@ -require 'rubinius/agent' - -module ActiveSupport - module Testing - module Performance - DEFAULTS.merge!( - if ARGV.include?('--benchmark') - {:metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time]} - else - { :metrics => [:wall_time], - :formats => [:flat, :graph] } - end).freeze - - protected - def run_gc - GC.run(true) - end - - class Performer; end - - class Profiler < Performer - def initialize(*args) - super - @supported = @metric.is_a?(Metrics::WallTime) - end - - def run - return unless @supported - - @profiler = Rubinius::Profiler::Instrumenter.new - - @total = time_with_block do - @profiler.profile(false) do - full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } - end - end - end - - def record - return unless @supported - - if(full_profile_options[:formats].include?(:flat)) - create_path_and_open_file(:flat) do |file| - @profiler.show(file) - end - end - - if(full_profile_options[:formats].include?(:graph)) - create_path_and_open_file(:graph) do |file| - @profiler.show(file) - end - end - end - - protected - def create_path_and_open_file(printer_name) - fname = "#{output_filename}_#{printer_name}.txt" - FileUtils.mkdir_p(File.dirname(fname)) - File.open(fname, 'wb') do |file| - yield(file) - end - end - end - - module Metrics - class Base - attr_reader :loopback - - def profile - yield - end - - protected - def with_gc_stats - @loopback = Rubinius::Agent.loopback - GC.run(true) - yield - end - end - - class WallTime < Time - def measure - super - end - end - - class Memory < DigitalInformationUnit - def measure - loopback.get("system.memory.counter.bytes").last - end - end - - class Objects < Amount - def measure - loopback.get("system.memory.counter.objects").last - end - end - - class GcRuns < Amount - def measure - loopback.get("system.gc.full.count").last + loopback.get("system.gc.young.count").last - end - end - - class GcTime < Time - def measure - (loopback.get("system.gc.full.wallclock").last + loopback.get("system.gc.young.wallclock").last) / 1000.0 - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb deleted file mode 100644 index 7c149df1e4..0000000000 --- a/activesupport/lib/active_support/testing/performance/ruby.rb +++ /dev/null @@ -1,173 +0,0 @@ -begin - require 'ruby-prof' -rescue LoadError - $stderr.puts 'Specify ruby-prof as application\'s dependency in Gemfile to run benchmarks.' - raise -end - -module ActiveSupport - module Testing - module Performance - DEFAULTS.merge!( - if ARGV.include?('--benchmark') - { :metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time] } - else - { :min_percent => 0.01, - :metrics => [:process_time, :memory, :objects], - :formats => [:flat, :graph_html, :call_tree, :call_stack] } - end).freeze - - protected - remove_method :run_gc - def run_gc - GC.start - end - - class Profiler < Performer - def initialize(*args) - super - @supported = @metric.measure_mode rescue false - end - - remove_method :run - def run - return unless @supported - - RubyProf.measure_mode = @metric.measure_mode - RubyProf.start - RubyProf.pause - full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } - @data = RubyProf.stop - @total = @data.threads.sum(0) { |thread| thread.methods.max.total_time } - end - - remove_method :record - def record - return unless @supported - - klasses = full_profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact - - klasses.each do |klass| - fname = output_filename(klass) - FileUtils.mkdir_p(File.dirname(fname)) - File.open(fname, 'wb') do |file| - klass.new(@data).print(file, full_profile_options.slice(:min_percent)) - end - end - end - - protected - def output_filename(printer_class) - suffix = - case printer_class.name.demodulize - when 'FlatPrinter'; 'flat.txt' - when 'FlatPrinterWithLineNumbers'; 'flat_line_numbers.txt' - when 'GraphPrinter'; 'graph.txt' - when 'GraphHtmlPrinter'; 'graph.html' - when 'GraphYamlPrinter'; 'graph.yml' - when 'CallTreePrinter'; 'tree.txt' - when 'CallStackPrinter'; 'stack.html' - when 'DotPrinter'; 'graph.dot' - else printer_class.name.sub(/Printer$/, '').underscore - end - - "#{super()}_#{suffix}" - end - end - - module Metrics - class Base - def measure_mode - self.class::Mode - end - - remove_method :profile - def profile - RubyProf.resume - yield - ensure - RubyProf.pause - end - - protected - remove_method :with_gc_stats - def with_gc_stats - GC::Profiler.enable - GC.start - yield - ensure - GC::Profiler.disable - end - end - - class ProcessTime < Time - Mode = RubyProf::PROCESS_TIME if RubyProf.const_defined?(:PROCESS_TIME) - - def measure - RubyProf.measure_process_time - end - end - - class WallTime < Time - Mode = RubyProf::WALL_TIME if RubyProf.const_defined?(:WALL_TIME) - - def measure - RubyProf.measure_wall_time - end - end - - class CpuTime < Time - Mode = RubyProf::CPU_TIME if RubyProf.const_defined?(:CPU_TIME) - - def initialize(*args) - # FIXME: yeah my CPU is 2.33 GHz - RubyProf.cpu_frequency = 2.33e9 unless RubyProf.cpu_frequency > 0 - super - end - - def measure - RubyProf.measure_cpu_time - end - end - - class Memory < DigitalInformationUnit - Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY) - - # Ruby 1.9 + GCdata patch - if GC.respond_to?(:malloc_allocated_size) - def measure - GC.malloc_allocated_size - end - end - end - - class Objects < Amount - Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS) - - # Ruby 1.9 + GCdata patch - if GC.respond_to?(:malloc_allocations) - def measure - GC.malloc_allocations - end - end - end - - class GcRuns < Amount - Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS) - - def measure - GC.count - end - end - - class GcTime < Time - Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME) - - def measure - GC::Profiler.total_time - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 0dbc198ea2..ff13efa990 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -80,22 +80,43 @@ module ActiveSupport end alias_method :getlocal, :localtime + # Returns true if the current time is within Daylight Savings Time for the + # specified time zone. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # Time.zone.parse("2012-5-30").dst? # => true + # Time.zone.parse("2012-11-30").dst? # => false def dst? period.dst? end alias_method :isdst, :dst? + # Returns true if the current time zone is set to UTC. + # + # Time.zone = 'UTC' # => 'UTC' + # Time.zone.now.utc? # => true + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # Time.zone.now.utc? # => false def utc? time_zone.name == 'UTC' end alias_method :gmt?, :utc? + # Returns the offset from current time to UTC time in seconds. def utc_offset period.utc_total_offset end alias_method :gmt_offset, :utc_offset alias_method :gmtoff, :utc_offset + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)" + # Time.zone.now.formatted_offset(true) # => "-05:00" + # Time.zone.now.formatted_offset(false) # => "-0500" + # Time.zone = 'UTC' # => "UTC" + # Time.zone.now.formatted_offset(true, "0") # => "0" def formatted_offset(colon = true, alternate_utc_string = nil) utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon) end @@ -147,10 +168,18 @@ module ActiveSupport end end + # Returns a string of the object's date and time in the format used by + # HTTP requests. + # + # Time.zone.now.httpdate # => "Tue, 01 Jan 2013 04:39:43 GMT" def httpdate utc.httpdate end + # Returns a string of the object's date and time in the RFC 2822 standard + # format. + # + # Time.zone.now.rfc2822 # => "Tue, 01 Jan 2013 04:51:39 +0000" def rfc2822 to_s(:rfc822) end @@ -185,18 +214,24 @@ module ActiveSupport utc <=> other end + # Returns true if the current object's time is within the specified + # +min+ and +max+ time. def between?(min, max) utc.between?(min, max) end + # Returns true if the current object's time is in the past. def past? utc.past? end + # Returns true if the current object's time falls within + # the current day. def today? time.today? end + # Returns true if the current object's time is in the future. def future? utc.future? end @@ -282,9 +317,9 @@ module ActiveSupport end alias_method :tv_sec, :to_i - # A TimeWithZone acts like a Time, so just return +self+. + # Return an instance of Time in the system timezone. def to_time - utc + utc.to_time end def to_datetime diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index a2e2c51283..5158bbc196 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -675,7 +675,7 @@ class FileStoreTest < ActiveSupport::TestCase def test_log_exception_when_cache_read_fails File.expects(:exist?).raises(StandardError, "failed") @cache.send(:read_entry, "winston", {}) - assert_present @buffer.string + assert @buffer.string.present? end end @@ -897,12 +897,12 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase def test_logging @cache.fetch('foo') { 'bar' } - assert_present @buffer.string + assert @buffer.string.present? end def test_mute_logging @cache.mute { @cache.fetch('foo') { 'bar' } } - assert_blank @buffer.string + assert @buffer.string.blank? end end diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 5e89a6e00b..f3fa96ec6f 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -33,8 +33,12 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_to_time - assert_equal Time.local(2005, 2, 21), Date.new(2005, 2, 21).to_time - assert_equal Time.local(2039, 2, 21), Date.new(2039, 2, 21).to_time + with_env_tz 'US/Eastern' do + assert_equal Time, Date.new(2005, 2, 21).to_time.class + assert_equal Time.local(2005, 2, 21), Date.new(2005, 2, 21).to_time + assert_equal Time.local(2005, 2, 21).utc_offset, Date.new(2005, 2, 21).to_time.utc_offset + end + silence_warnings do 0.upto(138) do |year| [:utc, :local].each do |format| @@ -353,6 +357,11 @@ class DateExtBehaviorTest < ActiveSupport::TestCase Date.today.freeze.freeze end end + + def test_compare_with_infinity + assert_equal(-1, Date.today <=> Float::INFINITY) + assert_equal(1, Date.today <=> -Float::INFINITY) + end end class DateExtConversionsTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index dfad3a90f8..f74b7b9917 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -41,11 +41,14 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_to_time - assert_equal Time.utc(2005, 2, 21, 10, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time - assert_equal Time.utc(2039, 2, 21, 10, 11, 12), DateTime.new(2039, 2, 21, 10, 11, 12, 0).to_time - # DateTimes with offsets other than 0 are returned unaltered - assert_equal DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)), DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).to_time - # Fractional seconds are preserved + with_env_tz 'US/Eastern' do + assert_equal Time, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.class + assert_equal Time.local(2005, 2, 21, 5, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time + assert_equal Time.local(2005, 2, 21, 5, 11, 12).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset + end + end + + def test_to_time_preserves_fractional_seconds assert_equal Time.utc(2005, 2, 21, 10, 11, 12, 256), DateTime.new(2005, 2, 21, 10, 11, 12 + Rational(256, 1000000), 0).to_time end @@ -317,3 +320,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end end + +class DateTimeExtBehaviorTest < ActiveSupport::TestCase + def test_compare_with_infinity + assert_equal(-1, DateTime.now <=> Float::INFINITY) + assert_equal(1, DateTime.now <=> -Float::INFINITY) + end +end diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 2826f51f2d..5e3987265b 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -50,14 +50,12 @@ class DurationTest < ActiveSupport::TestCase end def test_argument_error - begin - 1.second.ago('') - flunk("no exception was raised") - rescue ArgumentError => e - assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb" - rescue Exception => e - flunk("ArgumentError should be raised, but we got #{e.class} instead") - end + 1.second.ago('') + flunk("no exception was raised") + rescue ArgumentError => e + assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb" + rescue Exception => e + flunk("ArgumentError should be raised, but we got #{e.class} instead") end def test_fractional_weeks diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 5fc81ba6fc..30d95b75bc 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -8,7 +8,7 @@ require 'active_support/core_ext/object/deep_dup' require 'active_support/inflections' class HashExtTest < ActiveSupport::TestCase - class IndifferentHash < HashWithIndifferentAccess + class IndifferentHash < ActiveSupport::HashWithIndifferentAccess end class SubclassingArray < Array @@ -1015,12 +1015,10 @@ class HashToXmlTest < ActiveSupport::TestCase <replies-close-in type="integer">2592000000</replies-close-in> <written-on type="date">2003-07-16</written-on> <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at> - <content type="yaml">--- \n1: should be an integer\n:message: Have a nice day\narray: \n- should-have-dashes: true\n should_have_underscores: true\n</content> <author-email-address>david@loudthinking.com</author-email-address> <parent-id></parent-id> <ad-revenue type="decimal">1.5</ad-revenue> <optimum-viewing-angle type="float">135</optimum-viewing-angle> - <resident type="symbol">yes</resident> </topic> EOT @@ -1033,12 +1031,10 @@ class HashToXmlTest < ActiveSupport::TestCase :replies_close_in => 2592000000, :written_on => Date.new(2003, 7, 16), :viewed_at => Time.utc(2003, 7, 16, 9, 28), - :content => { :message => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] }, :author_email_address => "david@loudthinking.com", :parent_id => nil, :ad_revenue => BigDecimal("1.50"), :optimum_viewing_angle => 135.0, - :resident => :yes }.stringify_keys assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["topic"] @@ -1052,7 +1048,6 @@ class HashToXmlTest < ActiveSupport::TestCase <approved type="boolean"></approved> <written-on type="date"></written-on> <viewed-at type="datetime"></viewed-at> - <content type="yaml"></content> <parent-id></parent-id> </topic> EOT @@ -1063,7 +1058,6 @@ class HashToXmlTest < ActiveSupport::TestCase :approved => nil, :written_on => nil, :viewed_at => nil, - :content => nil, :parent_id => nil }.stringify_keys @@ -1290,6 +1284,28 @@ class HashToXmlTest < ActiveSupport::TestCase assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"] end + def test_from_xml_raises_on_disallowed_type_attributes + assert_raise ActiveSupport::XMLConverter::DisallowedType do + Hash.from_xml '<product><name type="foo">value</name></product>', %w(foo) + end + end + + def test_from_xml_disallows_symbol_and_yaml_types_by_default + assert_raise ActiveSupport::XMLConverter::DisallowedType do + Hash.from_xml '<product><name type="symbol">value</name></product>' + end + + assert_raise ActiveSupport::XMLConverter::DisallowedType do + Hash.from_xml '<product><name type="yaml">value</name></product>' + end + end + + def test_from_trusted_xml_allows_symbol_and_yaml_types + expected = { 'product' => { 'name' => :value }} + assert_equal expected, Hash.from_trusted_xml('<product><name type="symbol">value</name></product>') + assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>') + end + def test_should_use_default_value_for_unknown_key hash_wia = HashWithIndifferentAccess.new(3) assert_equal 3, hash_wia[:new_key] diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index 435f4aa5a1..8c7d00cae1 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -203,7 +203,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase def terabytes(number) gigabytes(number) * 1024 end - + def test_to_s__phone assert_equal("555-1234", 5551234.to_s(:phone)) assert_equal("800-555-1212", 8005551212.to_s(:phone)) @@ -217,7 +217,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal("22-555-1212", 225551212.to_s(:phone)) assert_equal("+45-22-555-1212", 225551212.to_s(:phone, :country_code => 45)) end - + def test_to_s__currency assert_equal("$1,234,567,890.50", 1234567890.50.to_s(:currency)) assert_equal("$1,234,567,890.51", 1234567890.506.to_s(:currency)) @@ -228,8 +228,8 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal("$1,234,567,890.5", 1234567890.50.to_s(:currency, :precision => 1)) assert_equal("£1234567890,50", 1234567890.50.to_s(:currency, :unit => "£", :separator => ",", :delimiter => "")) end - - + + def test_to_s__rounded assert_equal("-111.235", -111.2346.to_s(:rounded)) assert_equal("111.235", 111.2346.to_s(:rounded)) @@ -246,7 +246,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal("11.00", 10.995.to_s(:rounded, :precision => 2)) assert_equal("0.00", -0.001.to_s(:rounded, :precision => 2)) end - + def test_to_s__percentage assert_equal("100.000%", 100.to_s(:percentage)) assert_equal("100%", 100.to_s(:percentage, :precision => 0)) @@ -274,7 +274,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :separator => ',', :delimiter => '.') assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :delimiter => '.', :separator => ',') end - + def test_to_s__rounded_with_custom_delimiter_and_separator assert_equal '31,83', 31.825.to_s(:rounded, :precision => 2, :separator => ',') @@ -350,7 +350,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si) assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si) end - + def test_to_s__human_size_with_options_hash assert_equal '1.2 MB', 1234567.to_s(:human_size, :precision => 2) assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :precision => 4) @@ -366,13 +366,13 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal '1.012 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :significant => false) assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 0, :significant => true) #ignores significant it precision is 0 end - + def test_to_s__human_size_with_custom_delimiter_and_separator assert_equal '1,01 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :separator => ',') assert_equal '1,01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4, :separator => ',') assert_equal '1.000,1 TB', terabytes(1000.1).to_s(:human_size, :precision => 5, :delimiter => '.', :separator => ',') end - + def test_number_to_human assert_equal '-123', -123.to_s(:human) assert_equal '-0.5', -0.5.to_s(:human) @@ -436,7 +436,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase def test_to_s__injected_on_proper_types assert_equal Fixnum, 1230.class assert_equal '1.23 Thousand', 1230.to_s(:human) - + assert_equal Float, Float(1230).class assert_equal '1.23 Thousand', Float(1230).to_s(:human) @@ -447,3 +447,57 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal '1 Million', BigDecimal("1000010").to_s(:human) end end + +class NumericExtBehaviorTest < ActiveSupport::TestCase + def setup + @inf = BigDecimal.new('Infinity') + end + + def test_compare_infinity_with_date + assert_equal(-1, -Float::INFINITY <=> Date.today) + assert_equal(1, Float::INFINITY <=> Date.today) + assert_equal(-1, -@inf <=> Date.today) + assert_equal(1, @inf <=> Date.today) + end + + def test_compare_infinty_with_infinty + assert_equal(-1, -Float::INFINITY <=> Float::INFINITY) + assert_equal(1, Float::INFINITY <=> -Float::INFINITY) + assert_equal(0, Float::INFINITY <=> Float::INFINITY) + assert_equal(0, -Float::INFINITY <=> -Float::INFINITY) + + assert_equal(-1, -Float::INFINITY <=> BigDecimal::INFINITY) + assert_equal(1, Float::INFINITY <=> -BigDecimal::INFINITY) + assert_equal(0, Float::INFINITY <=> BigDecimal::INFINITY) + assert_equal(0, -Float::INFINITY <=> -BigDecimal::INFINITY) + + assert_equal(-1, -BigDecimal::INFINITY <=> Float::INFINITY) + assert_equal(1, BigDecimal::INFINITY <=> -Float::INFINITY) + assert_equal(0, BigDecimal::INFINITY <=> Float::INFINITY) + assert_equal(0, -BigDecimal::INFINITY <=> -Float::INFINITY) + end + + def test_compare_infinity_with_time + assert_equal(-1, -Float::INFINITY <=> Time.now) + assert_equal(1, Float::INFINITY <=> Time.now) + assert_equal(-1, -@inf <=> Time.now) + assert_equal(1, @inf <=> Time.now) + end + + def test_compare_infinity_with_datetime + assert_equal(-1, -Float::INFINITY <=> DateTime.now) + assert_equal(1, Float::INFINITY <=> DateTime.now) + assert_equal(-1, -@inf <=> DateTime.now) + assert_equal(1, @inf <=> DateTime.now) + end + + def test_compare_infinity_with_twz + time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + twz = ActiveSupport::TimeWithZone.new(Time.now, time_zone) + + assert_equal(-1, -Float::INFINITY <=> twz) + assert_equal(1, Float::INFINITY <=> twz) + assert_equal(-1, -@inf <=> twz) + assert_equal(1, @inf <=> twz) + end +end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index f0cdc0bfd4..6e94d5e10d 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -1,6 +1,7 @@ require 'abstract_unit' require 'active_support/time' require 'active_support/core_ext/range' +require 'active_support/core_ext/numeric' class RangeTest < ActiveSupport::TestCase def test_to_s_from_dates @@ -12,6 +13,12 @@ class RangeTest < ActiveSupport::TestCase date_range = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30) assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db) end + + def test_date_range + assert_instance_of Range, DateTime.new..DateTime.new + assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new + assert_instance_of Range, DateTime.new..DateTime::Infinity.new + end def test_overlaps_last_inclusive assert((1..5).overlaps?(5..10)) @@ -85,4 +92,28 @@ class RangeTest < ActiveSupport::TestCase time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00) assert !time_range_1.overlaps?(time_range_2) end + + def test_infinite_bounds + time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + + time = Time.now + date = Date.today + datetime = DateTime.now + twz = ActiveSupport::TimeWithZone.new(time, time_zone) + + infinity1 = Float::INFINITY + infinity2 = BigDecimal.new('Infinity') + + [infinity1, infinity2].each do |infinity| + [time, date, datetime, twz].each do |bound| + [time, date, datetime, twz].each do |value| + assert Range.new(bound, infinity).include?(value + 10.years) + assert Range.new(-infinity, bound).include?(value - 10.years) + + assert !Range.new(bound, infinity).include?(value - 10.years) + assert !Range.new(-infinity, bound).include?(value + 10.years) + end + end + end + end end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index fa8839bcb3..db1cf14abf 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -286,14 +286,37 @@ end class StringConversionsTest < ActiveSupport::TestCase def test_string_to_time - assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time - assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:local) - assert_equal Time.utc(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time - assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:local) - assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time - assert_equal Time.local(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:local) - assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time - assert_nil "".to_time + with_env_tz "US/Eastern" do + assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:utc) + assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time + assert_equal Time.utc(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:utc) + assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time + assert_equal Time.utc(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:utc) + assert_equal Time.local(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time + assert_equal Time.local(2011, 2, 27, 18, 50), "2011-02-27 22:50 -0100".to_time + assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time(:utc) + assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50 -0500".to_time + assert_nil "".to_time + end + end + + def test_string_to_time_utc_offset + with_env_tz "US/Eastern" do + assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset + assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset) + assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset + assert_equal(-18000, "2005-02-27 22:50 -0100".to_time.utc_offset) + end + end + + def test_partial_string_to_time + with_env_tz "US/Eastern" do + now = Time.now + assert_equal Time.local(now.year, now.month, now.day, 23, 50), "23:50".to_time + assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "23:50".to_time(:utc) + assert_equal Time.local(now.year, now.month, now.day, 18, 50), "22:50 -0100".to_time + assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "22:50 -0100".to_time(:utc) + end end def test_string_to_datetime @@ -304,10 +327,25 @@ class StringConversionsTest < ActiveSupport::TestCase assert_nil "".to_datetime end + def test_partial_string_to_datetime + now = DateTime.now + assert_equal DateTime.civil(now.year, now.month, now.day, 23, 50), "23:50".to_datetime + assert_equal DateTime.civil(now.year, now.month, now.day, 23, 50, 0, "-04:00"), "23:50 -0400".to_datetime + end + def test_string_to_date assert_equal Date.new(2005, 2, 27), "2005-02-27".to_date assert_nil "".to_date + assert_equal Date.new(Date.today.year, 2, 3), "Feb 3rd".to_date end + + protected + def with_env_tz(new_tz = 'US/Eastern') + old_tz, ENV['TZ'] = ENV['TZ'], new_tz + yield + ensure + old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') + end end class StringBehaviourTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 70e07cefd1..43c92003dc 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -526,7 +526,11 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_to_time - assert_equal Time.local(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30).to_time + with_env_tz 'US/Eastern' do + assert_equal Time, Time.local(2005, 2, 21, 17, 44, 30).to_time.class + assert_equal Time.local(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30).to_time + assert_equal Time.local(2005, 2, 21, 17, 44, 30).utc_offset, Time.local(2005, 2, 21, 17, 44, 30).to_time.utc_offset + end end # NOTE: this test seems to fail (changeset 1958) only on certain platforms, @@ -843,3 +847,10 @@ class TimeExtMarshalingTest < ActiveSupport::TestCase assert_equal Time.local(2004, 2, 29), Time.local(2004, 5, 31).last_quarter end end + +class TimeExtBehaviorTest < ActiveSupport::TestCase + def test_compare_with_infinity + assert_equal(-1, Time.now <=> Float::INFINITY) + assert_equal(1, Time.now <=> -Float::INFINITY) + end +end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 56020da035..18eca4cd8b 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -327,7 +327,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_to_time - assert_equal @twz, @twz.to_time + with_env_tz 'US/Eastern' do + assert_equal Time, @twz.to_time.class + assert_equal Time.local(1999, 12, 31, 19), @twz.to_time + assert_equal Time.local(1999, 12, 31, 19).utc_offset, @twz.to_time.utc_offset + end end def test_to_date @@ -1081,3 +1085,13 @@ class TimeWithZoneMethodsForString < ActiveSupport::TestCase Time.zone = old_tz end end + +class TimeWithZoneExtBehaviorTest < ActiveSupport::TestCase + def test_compare_with_infinity + time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + twz = ActiveSupport::TimeWithZone.new(Time.now, time_zone) + + assert_equal(-1, twz <=> Float::INFINITY) + assert_equal(1, twz <=> -Float::INFINITY) + end +end diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 9d913c27b0..615808090d 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -363,12 +363,10 @@ class DependenciesTest < ActiveSupport::TestCase end def test_smart_name_error_strings - begin - Object.module_eval "ImaginaryObject" - flunk "No raise!!" - rescue NameError => e - assert e.message.include?("uninitialized constant ImaginaryObject") - end + Object.module_eval "ImaginaryObject" + flunk "No raise!!" + rescue NameError => e + assert e.message.include?("uninitialized constant ImaginaryObject") end def test_loadable_constants_for_path_should_handle_empty_autoloads diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 332100f5a1..c1a468ec86 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -119,9 +119,10 @@ class DeprecationTest < ActiveSupport::TestCase ActiveSupport::Deprecation.behavior = :silence behavior = ActiveSupport::Deprecation.behavior.first - assert_blank capture(:stderr) { + stderr_output = capture(:stderr) { assert_nil behavior.call('Some error!', ['call stack!']) } + assert stderr_output.blank? end def test_deprecated_instance_variable_proxy @@ -254,10 +255,10 @@ class DeprecationTest < ActiveSupport::TestCase klass::OLD.to_s end end - + def test_deprecated_instance_variable_with_instance_deprecator deprecator = deprecator_with_messages - + klass = Class.new() do def initialize(deprecator) @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator) diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb index ddbba444cf..5ef59b6e6b 100644 --- a/activesupport/test/i18n_test.rb +++ b/activesupport/test/i18n_test.rb @@ -62,7 +62,7 @@ class I18nTest < ActiveSupport::TestCase end def test_date_order - assert_equal [:year, :month, :day], I18n.translate(:'date.order') + assert_equal %w(year month day), I18n.translate(:'date.order') end def test_time_am diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index aa41e57928..a1e5db6a2e 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -361,7 +361,7 @@ class InflectorTest < ActiveSupport::TestCase inflect.singular(/s$/, '') inflect.singular(/es$/, '') - + inflect.irregular('el', 'los') end diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index d6c31396b6..06c7e8a1a7 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -29,9 +29,9 @@ class MessageEncryptorTest < ActiveSupport::TestCase end def test_encrypting_twice_yields_differing_cipher_text - first_messqage = @encryptor.encrypt_and_sign(@data).split("--").first + first_message = @encryptor.encrypt_and_sign(@data).split("--").first second_message = @encryptor.encrypt_and_sign(@data).split("--").first - assert_not_equal first_messqage, second_message + assert_not_equal first_message, second_message end def test_messing_with_either_encrypted_values_causes_failure diff --git a/activesupport/test/notifications/instrumenter_test.rb b/activesupport/test/notifications/instrumenter_test.rb new file mode 100644 index 0000000000..62a9b61464 --- /dev/null +++ b/activesupport/test/notifications/instrumenter_test.rb @@ -0,0 +1,50 @@ +require 'abstract_unit' +require 'active_support/notifications/instrumenter' + +module ActiveSupport + module Notifications + class InstrumenterTest < ActiveSupport::TestCase + class TestNotifier + attr_reader :starts, :finishes + + def initialize + @starts = [] + @finishes = [] + end + + def start(*args); @starts << args; end + def finish(*args); @finishes << args; end + end + + attr_reader :instrumenter, :notifier, :payload + + def setup + super + @notifier = TestNotifier.new + @instrumenter = Instrumenter.new @notifier + @payload = { :foo => Object.new } + end + + def test_instrument + called = false + instrumenter.instrument("foo", payload) { + called = true + } + + assert called + end + + def test_start + instrumenter.start("foo", payload) + assert_equal [["foo", instrumenter.id, payload]], notifier.starts + assert_predicate notifier.finishes, :empty? + end + + def test_finish + instrumenter.finish("foo", payload) + assert_equal [["foo", instrumenter.id, payload]], notifier.finishes + assert_predicate notifier.starts, :empty? + end + end + end +end diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index 0f93c8b59e..3e6ac811a4 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -92,17 +92,21 @@ class AssertBlankTest < ActiveSupport::TestCase NOT_BLANK = [ EmptyFalse.new, Object.new, true, 0, 1, 'x', [nil], { nil => 0 } ] def test_assert_blank_true - BLANK.each { |v| assert_blank v } + BLANK.each { |value| + assert_deprecated { assert_blank value } + } end def test_assert_blank_false NOT_BLANK.each { |v| - begin - assert_blank v - fail 'should not get to here' - rescue Exception => e - assert_match(/is not blank/, e.message) - end + assert_deprecated { + begin + assert_blank v + fail 'should not get to here' + rescue Exception => e + assert_match(/is not blank/, e.message) + end + } } end end @@ -112,17 +116,21 @@ class AssertPresentTest < ActiveSupport::TestCase NOT_BLANK = [ EmptyFalse.new, Object.new, true, 0, 1, 'x', [nil], { nil => 0 } ] def test_assert_present_true - NOT_BLANK.each { |v| assert_present v } + NOT_BLANK.each { |v| + assert_deprecated { assert_present v } + } end def test_assert_present_false BLANK.each { |v| - begin - assert_present v - fail 'should not get to here' - rescue Exception => e - assert_match(/is blank/, e.message) - end + assert_deprecated { + begin + assert_present v + fail 'should not get to here' + rescue Exception => e + assert_match(/is blank/, e.message) + end + } } end end diff --git a/activesupport/test/testing/performance_test.rb b/activesupport/test/testing/performance_test.rb deleted file mode 100644 index 6918110cce..0000000000 --- a/activesupport/test/testing/performance_test.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'abstract_unit' - -module ActiveSupport - module Testing - class PerformanceTest < ActiveSupport::TestCase - begin - require 'active_support/testing/performance' - HAVE_RUBYPROF = true - rescue LoadError - HAVE_RUBYPROF = false - end - - def setup - skip "no rubyprof" unless HAVE_RUBYPROF - end - - def test_amount_format - amount_metric = ActiveSupport::Testing::Performance::Metrics[:amount].new - assert_equal "0", amount_metric.format(0) - assert_equal "1", amount_metric.format(1.23) - assert_equal "40,000,000", amount_metric.format(40000000) - end - - def test_time_format - time_metric = ActiveSupport::Testing::Performance::Metrics[:time].new - assert_equal "0 ms", time_metric.format(0) - assert_equal "40 ms", time_metric.format(0.04) - assert_equal "41 ms", time_metric.format(0.0415) - assert_equal "1.23 sec", time_metric.format(1.23) - assert_equal "40000.00 sec", time_metric.format(40000) - assert_equal "-5000 ms", time_metric.format(-5) - end - - def test_space_format - space_metric = ActiveSupport::Testing::Performance::Metrics[:digital_information_unit].new - assert_equal "0 Bytes", space_metric.format(0) - assert_equal "0 Bytes", space_metric.format(0.4) - assert_equal "1 Byte", space_metric.format(1.23) - assert_equal "123 Bytes", space_metric.format(123) - assert_equal "123 Bytes", space_metric.format(123.45) - assert_equal "12 KB", space_metric.format(12345) - assert_equal "1.2 MB", space_metric.format(1234567) - assert_equal "9.3 GB", space_metric.format(10**10) - assert_equal "91 TB", space_metric.format(10**14) - assert_equal "910000 TB", space_metric.format(10**18) - end - - def test_environment_format_without_rails - metric = ActiveSupport::Testing::Performance::Metrics[:time].new - benchmarker = ActiveSupport::Testing::Performance::Benchmarker.new(self, metric) - assert_equal "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL},#{RUBY_PLATFORM}", benchmarker.environment - end - - def test_environment_format_with_rails - rails, version = Module.new, Module.new - version.const_set :STRING, "4.0.0" - rails.const_set :VERSION, version - Object.const_set :Rails, rails - - metric = ActiveSupport::Testing::Performance::Metrics[:time].new - benchmarker = ActiveSupport::Testing::Performance::Benchmarker.new(self, metric) - assert_equal "rails-4.0.0,#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL},#{RUBY_PLATFORM}", benchmarker.environment - ensure - Object.send :remove_const, :Rails - end - end - end -end diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css index 589c96e0e9..dd029e6314 100644 --- a/guides/assets/stylesheets/main.css +++ b/guides/assets/stylesheets/main.css @@ -83,6 +83,10 @@ table th { padding: 0.5em 1em; } +img { + max-width: 100%; +} + /* Structure and Layout --------------------------------------- */ @@ -573,7 +577,7 @@ h6 { #mainCol div.warning, #subCol dd.warning { background: #f9d9d8 url(../images/tab_red.gif) no-repeat left top; border: none; - padding: 1.25em 1.25em 1.25em 48px; + padding: 1.25em 1.25em 0.25em 48px; margin-left: 0; margin-top: 0.25em; } diff --git a/guides/code/getting_started/README.rdoc b/guides/code/getting_started/README.rdoc index b5d7b6436b..232856ca5f 100644 --- a/guides/code/getting_started/README.rdoc +++ b/guides/code/getting_started/README.rdoc @@ -1,259 +1,28 @@ -== Welcome to Rails +== README -Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Control pattern. +This README would normally document whatever steps are necessary to get the +application up and running. -This pattern splits the view (also called the presentation) into "dumb" -templates that are primarily responsible for inserting pre-built data in between -HTML tags. The model contains the "smart" domain objects (such as Account, -Product, Person, Post) that holds all the business logic and knows how to -persist themselves to a database. The controller handles the incoming requests -(such as Save New Account, Update Product, Show Post) by manipulating the model -and directing data to the view. +Things you may want to cover: -In Rails, the model is handled by what's called an object-relational mapping -layer entitled Active Record. This layer allows you to present the data from -database rows as objects and embellish these data objects with business logic -methods. You can read more about Active Record in -link:files/vendor/rails/activerecord/README.html. +* Ruby version -The controller and view are handled by the Action Pack, which handles both -layers by its two parts: Action View and Action Controller. These two layers -are bundled in a single package due to their heavy interdependence. This is -unlike the relationship between the Active Record and Action Pack that is much -more separate. Each of these packages can be used independently outside of -Rails. You can read more about Action Pack in -link:files/vendor/rails/actionpack/README.html. +* System dependencies +* Configuration -== Getting Started +* Database creation -1. At the command prompt, create a new Rails application: - <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name) +* Database initialization -2. Change directory to <tt>myapp</tt> and start the web server: - <tt>cd myapp; rails server</tt> (run with --help for options) +* How to run the test suite -3. Go to http://localhost:3000/ and you'll see: - "Welcome aboard: You're riding Ruby on Rails!" +* Services (job queues, cache servers, search engines, etc.) -4. Follow the guidelines to start developing your application. You can find -the following resources handy: +* Deployment instructions -* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html -* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ +* ... - -== Debugging Rails - -Sometimes your application goes wrong. Fortunately there are a lot of tools that -will help you debug it and get it back on the rails. - -First area to check is the application log files. Have "tail -f" commands -running on the server.log and development.log. Rails will automatically display -debugging and runtime information to these files. Debugging info will also be -shown in the browser on requests from 127.0.0.1. - -You can also log your own messages directly into the log file from your code -using the Ruby logger class from inside your controllers. Example: - - class WeblogController < ActionController::Base - def destroy - @weblog = Weblog.find(params[:id]) - @weblog.destroy - logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") - end - end - -The result will be a message in your log file along the lines of: - - Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! - -More information on how to use the logger is at http://www.ruby-doc.org/core/ - -Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are -several books available online as well: - -* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) -* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) - -These two books will bring you up to speed on the Ruby language and also on -programming in general. - - -== Debugger - -Debugger support is available through the debugger command when you start your -Mongrel or WEBrick server with --debugger. This means that you can break out of -execution at any point in the code, investigate and change the model, and then, -resume execution! You need to install the 'debugger' gem to run the server in debugging -mode. Add gem 'debugger' to your Gemfile and run <tt>bundle</tt> to install it. Example: - - class WeblogController < ActionController::Base - def index - @posts = Post.all - debugger - end - end - -So the controller will accept the action, run the first line, then present you -with a IRB prompt in the server window. Here you can do things like: - - >> @posts.inspect - => "[#<Post:0x14a6be8 - @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>, - #<Post:0x14a6620 - @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]" - >> @posts.first.title = "hello from a debugger" - => "hello from a debugger" - -...and even better, you can examine how your runtime objects actually work: - - >> f = @posts.first - => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}> - >> f. - Display all 152 possibilities? (y or n) - -Finally, when you're ready to resume execution, you can enter "cont". - - -== Console - -The console is a Ruby shell, which allows you to interact with your -application's domain model. Here you'll have all parts of the application -configured, just like it is when the application is running. You can inspect -domain models, change values, and save to the database. Starting the script -without arguments will launch it in the development environment. - -To start the console, run <tt>rails console</tt> from the application -directory. - -Options: - -* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications - made to the database. -* Passing an environment name as an argument will load the corresponding - environment. Example: <tt>rails console production</tt>. - -To reload your controllers and models after launching the console run -<tt>reload!</tt> - -More information about irb can be found at: -link:http://www.rubycentral.org/pickaxe/irb.html - - -== dbconsole - -You can go to the command line of your database directly through <tt>rails -dbconsole</tt>. You would be connected to the database with the credentials -defined in database.yml. Starting the script without arguments will connect you -to the development database. Passing an argument will connect you to a different -database, like <tt>rails dbconsole production</tt>. Currently works for MySQL, -PostgreSQL and SQLite 3. - -== Description of Contents - -The default directory structure of a generated Ruby on Rails application: - - |-- app - | |-- assets - | |-- images - | |-- javascripts - | `-- stylesheets - | |-- controllers - | |-- helpers - | |-- mailers - | |-- models - | `-- views - | `-- layouts - |-- config - | |-- environments - | |-- initializers - | `-- locales - |-- db - |-- doc - |-- lib - | `-- tasks - |-- log - |-- public - |-- script - |-- test - | |-- fixtures - | |-- functional - | |-- integration - | |-- performance - | `-- unit - |-- tmp - | |-- cache - | |-- pids - | |-- sessions - | `-- sockets - `-- vendor - |-- assets - `-- stylesheets - -app - Holds all the code that's specific to this particular application. - -app/assets - Contains subdirectories for images, stylesheets, and JavaScript files. - -app/controllers - Holds controllers that should be named like weblogs_controller.rb for - automated URL mapping. All controllers should descend from - ApplicationController which itself descends from ActionController::Base. - -app/models - Holds models that should be named like post.rb. Models descend from - ActiveRecord::Base by default. - -app/views - Holds the template files for the view that should be named like - weblogs/index.html.erb for the WeblogsController#index action. All views use - eRuby syntax by default. - -app/views/layouts - Holds the template files for layouts to be used with views. This models the - common header/footer method of wrapping views. In your views, define a layout - using the <tt>layout :default</tt> and create a file named default.html.erb. - Inside default.html.erb, call <% yield %> to render the view using this - layout. - -app/helpers - Holds view helpers that should be named like weblogs_helper.rb. These are - generated for you automatically when using generators for controllers. - Helpers can be used to wrap functionality for your views into methods. - -config - Configuration files for the Rails environment, the routing map, the database, - and other dependencies. - -db - Contains the database schema in schema.rb. db/migrate contains all the - sequence of Migrations for your schema. - -doc - This directory is where your application documentation will be stored when - generated using <tt>rake doc:app</tt> - -lib - Application specific libraries. Basically, any kind of custom code that - doesn't belong under controllers, models, or helpers. This directory is in - the load path. - -public - The directory available for the web server. Also contains the dispatchers and the - default HTML files. This should be set as the DOCUMENT_ROOT of your web - server. - -script - Helper scripts for automation and generation. - -test - Unit and functional tests along with fixtures. When using the rails generate - command, template test files will be generated for you and placed in this - directory. - -vendor - External libraries that the application depends on. If the app has frozen rails, - those gems also go here, under vendor/rails/. This directory is in the load path. +If you plan to generate application documentation with `rake doc:app` this file +is expected to be `README.rdoc`, otherwise please feel free to rename it and use +a different markup language.
\ No newline at end of file diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb index a8ac9aba5a..b74c66ef13 100644 --- a/guides/code/getting_started/app/controllers/posts_controller.rb +++ b/guides/code/getting_started/app/controllers/posts_controller.rb @@ -31,7 +31,7 @@ class PostsController < ApplicationController def update @post = Post.find(params[:id]) - if @post.update_attributes(params[:post]) + if @post.update(params[:post]) redirect_to :action => :show, :id => @post.id else render 'edit' diff --git a/guides/code/getting_started/doc/README_FOR_APP b/guides/code/getting_started/doc/README_FOR_APP deleted file mode 100644 index fe41f5cc24..0000000000 --- a/guides/code/getting_started/doc/README_FOR_APP +++ /dev/null @@ -1,2 +0,0 @@ -Use this README file to introduce your application and point to useful places in the API for learning more. -Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. diff --git a/guides/code/getting_started/test/performance/browsing_test.rb b/guides/code/getting_started/test/performance/browsing_test.rb deleted file mode 100644 index 9342a57b20..0000000000 --- a/guides/code/getting_started/test/performance/browsing_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'test_helper' -require 'rails/performance_test_help' - -class BrowsingTest < ActionDispatch::PerformanceTest - # Refer to the documentation for all available options - # self.profile_options = { runs: 5, metrics: [:wall_time, :memory], - # output: 'tmp/performance', formats: [:flat] } - - def test_homepage - get '/' - end -end diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb index 650489e6cb..547c6d2c15 100644 --- a/guides/rails_guides/markdown.rb +++ b/guides/rails_guides/markdown.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 + require 'redcarpet' require 'nokogiri' require 'rails_guides/markdown/renderer' @@ -129,7 +131,7 @@ module RailsGuides def generate_title if heading = Nokogiri::HTML(@header).at(:h2) - @title = "Ruby on Rails Guides: #{heading.text}".html_safe + @title = "#{heading.text} — Ruby on Rails Guides".html_safe else @title = "Ruby on Rails Guides" end diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 597ac99be0..52571fab60 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -68,6 +68,7 @@ In Rails 4.0, several features have been extracted into gems. You can simply add * Action Caching ([Github](https://github.com/rails/actionpack-action_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) * Page Caching ([Github](https://github.com/rails/actionpack-page_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) * Sprockets ([Github](https://github.com/rails/sprockets-rails)) +* Performance tests ([Github](https://github.com/rails/rails-perftest), [Pull Request](https://github.com/rails/rails/pull/8876)) Documentation ------------- @@ -85,6 +86,8 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt * New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well. ([Pull Request](https://github.com/rails/rails/pull/7878)) +* Your app's executables now live in the `bin/` dir. Run `rake rails:update:bin` to get `bin/bundle`, `bin/rails`, and `bin/rake`. + * Threadsafe on by default ### Deprecations @@ -142,6 +145,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ * BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby stdlib. +* Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?` Action Pack ----------- @@ -150,6 +154,8 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt ### Notable changes +* Change the stylesheet of exception pages for development mode. Additionally display also the line of code and fragment that raised the exception in all exceptions pages. + ### Deprecations diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 46ff9027fd..cc80334af3 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -114,7 +114,7 @@ To send a hash you include the key name inside the brackets: When this form is submitted, the value of `params[:client]` will be `{"name" => "Acme", "phone" => "12345", "address" => {"postcode" => "12345", "city" => "Carrot City"}}`. Note the nested hash in `params[:client][:address]`. -Note that the `params` hash is actually an instance of `HashWithIndifferentAccess` from Active Support, which acts like a hash that lets you use symbols and strings interchangeably as keys. +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 @@ -174,10 +174,10 @@ Session Your application has a session for each user in which you can store small amounts of data that will be persisted between requests. The session is only available in the controller and the view and can use one of a number of different storage mechanisms: -* ActionDispatch::Session::CookieStore - Stores everything on the client. -* ActionDispatch::Session::CacheStore - Stores the data in the Rails cache. -* @ActionDispatch::Session::ActiveRecordStore@ - Stores the data in a database using Active Record. (require `activerecord-session_store` gem). -* @ActionDispatch::Session::MemCacheStore@ - Stores the data in a memcached cluster (this is a legacy implementation; consider using CacheStore instead). +* `ActionDispatch::Session::CookieStore` - Stores everything on the client. +* `ActionDispatch::Session::CacheStore` - Stores the data in the Rails cache. +* `ActionDispatch::Session::ActiveRecordStore` - Stores the data in a database using Active Record. (require `activerecord-session_store` gem). +* `ActionDispatch::Session::MemCacheStore` - Stores the data in a memcached cluster (this is a legacy implementation; consider using CacheStore instead). All session stores use a cookie to store a unique ID for each session (you must use a cookie, Rails will not allow you to pass the session ID in the URL as this is less secure). @@ -194,7 +194,7 @@ If you need a different session storage mechanism, you can change it in the `con ```ruby # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information -# (create the session table with "script/rails g active_record:session_migration") +# (create the session table with "rails g active_record:session_migration") # YourApp::Application.config.session_store :active_record_store ``` diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 795afd0150..590ad5738e 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -419,7 +419,7 @@ Receiving and parsing emails with Action Mailer can be a rather complex endeavor * Implement a `receive` method in your mailer. -* Configure your email server to forward emails from the address(es) you would like your app to receive to `/path/to/app/script/rails runner 'UserMailer.receive(STDIN.read)'`. +* Configure your email server to forward emails from the address(es) you would like your app to receive to `/path/to/app/bin/rails runner 'UserMailer.receive(STDIN.read)'`. Once a method called `receive` is defined in any mailer, Action Mailer will parse the raw incoming email into an email object, decode it, instantiate a new mailer, and pass the email object to the mailer `receive` instance method. Here's an example: @@ -575,3 +575,23 @@ end ``` In the test we send the email and store the returned object in the `email` variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect. + +Intercepting Emails +------------------- +There are situations where you need to edit an email before it's delivered. Fortunately Action Mailer provides hooks to intercept every email. You can register an interceptor to make modifications to mail messages right before they are handed to the delivery agents. + +```ruby +class SandboxEmailInterceptor + def self.delivering_email(message) + message.to = ['sandbox@example.com'] + end +end +``` + +Before the interceptor can do its job you need to register it with the Action Mailer framework. You can do this in an initializer file `config/initializers/sandbox_email_interceptor.rb` + +```ruby +ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.staging? +``` + +NOTE: The example above uses a custom environment called "staging" for a production like server but for testing purposes. diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 883c2dda4a..062bcd49f4 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -239,12 +239,12 @@ Active Record provides a rich API for accessing data within a database. Below are a few examples of different data access methods provided by Active Record. ```ruby -# return array with all records +# return a collection with all users users = User.all ``` ```ruby -# return the first record +# return the first user user = User.first ``` @@ -277,7 +277,7 @@ value, like so: ```ruby user = User.find_by_name('David') -user.update_attributes(name: 'Dave') +user.update(name: 'Dave') ``` This is most useful when updating several attributes at once. If, on the other @@ -307,10 +307,10 @@ models and validate that an attribute value is not empty, is unique and not already in the database, follows a specific format and many more. Validation is a very important issue to consider when persisting to database, so -the methods `create`, `save` and `update_attributes` take it into account when +the methods `create`, `save` and `update` take it into account when running: they return `false` when validation fails and they didn't actually perform any operation on database. All of these have a bang counterpart (that -is, `create!`, `save!` and `update_attributes!`), which are stricter in that +is, `create!`, `save!` and `update!`), which are stricter in that they raise the exception `ActiveRecord::RecordInvalid` if validation fails. A quick example to illustrate: diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 0a93f61f6d..3747b00b82 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -159,8 +159,8 @@ The following methods trigger callbacks: * `toggle!` * `update` * `update_attribute` -* `update_attributes` -* `update_attributes!` +* `update` +* `update!` * `valid?` Additionally, the `after_find` callback is triggered by the following finder methods: @@ -168,7 +168,6 @@ Additionally, the `after_find` callback is triggered by the following finder met * `all` * `first` * `find` -* `find_all_by_*` * `find_by_*` * `find_by_*!` * `find_by_sql` @@ -176,7 +175,7 @@ Additionally, the `after_find` callback is triggered by the following finder met The `after_initialize` callback is triggered every time a new object of the class is initialized. -NOTE: The `find_all_by_*`, `find_by_*` and `find_by_*!` methods are dynamic finders generated automatically for every attribute. Learn more about them at the [Dynamic finders section](active_record_querying.html#dynamic-finders) +NOTE: The `find_by_*` and `find_by_*!` methods are dynamic finders generated automatically for every attribute. Learn more about them at the [Dynamic finders section](active_record_querying.html#dynamic-finders) Skipping Callbacks ------------------ diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 24f98f68ca..62d6294ae5 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1228,9 +1228,7 @@ Client.unscoped { Dynamic Finders --------------- -For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` and `find_all_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` and `find_all_by_locked` methods. - -You can also use `find_last_by_*` methods which will find the last record matching your argument. +For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` and methods. You can specify an exclamation point (`!`) on the end of the dynamic finders to get them to raise an `ActiveRecord::RecordNotFound` error if they do not return any records, like `Client.find_by_name!("Ryan")` diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 5797c93ae7..eaa47d4ebf 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -117,11 +117,10 @@ database only if the object is valid: * `save` * `save!` * `update` -* `update_attributes` -* `update_attributes!` +* `update!` The bang versions (e.g. `save!`) raise an exception if the record is invalid. -The non-bang versions don't: `save` and `update_attributes` return `false`, +The non-bang versions don't: `save` and `update` return `false`, `create` and `update` just return the objects. ### Skipping Validations diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 23736020ec..3c1bb0f132 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -422,24 +422,6 @@ NOTE: Defined in `active_support/core_ext/object/with_options.rb`. Active Support provides several methods to ease access to instance variables. -#### `instance_variable_names` - -Ruby 1.8 and 1.9 have a method called `instance_variables` that returns the names of the defined instance variables. But they behave differently, in 1.8 it returns strings whereas in 1.9 it returns symbols. Active Support defines `instance_variable_names` as a portable way to obtain them as strings: - -```ruby -class C - def initialize(x, y) - @x, @y = x, y - end -end - -C.new(0, 1).instance_variable_names # => ["@y", "@x"] -``` - -WARNING: The order in which the names are returned is unspecified, and it indeed depends on the version of the interpreter. - -NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. - #### `instance_values` The method `instance_values` returns a hash that maps instance variable names without "@" to their @@ -920,7 +902,7 @@ When interpolated into a string, the `:to` option should become an expression th delegate :logger, to: :Rails # delegates to the receiver's class -delegate :table_name, to: 'self.class' +delegate :table_name, to: :class ``` WARNING: If the `:prefix` option is `true` this is less generic, see below. @@ -1449,11 +1431,10 @@ As the previous example shows, Active Support knows some irregular plurals and u Active Record uses this method to compute the default table name that corresponds to a model: ```ruby -# active_record/base.rb +# active_record/model_schema.rb def undecorated_table_name(class_name = base_class.name) table_name = class_name.to_s.demodulize.underscore - table_name = table_name.pluralize if pluralize_table_names - table_name + pluralize_table_names ? table_name.pluralize : table_name end ``` @@ -2413,9 +2394,9 @@ or yields them in turn if a block is passed: ```html+erb <% sample.in_groups_of(3) do |a, b, c| %> <tr> - <td><%=h a %></td> - <td><%=h b %></td> - <td><%=h c %></td> + <td><%= a %></td> + <td><%= b %></td> + <td><%= c %></td> </tr> <% end %> ``` diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index b302ef76c6..fffa31927d 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -720,7 +720,31 @@ A good example of this is the `jquery-rails` gem which comes with Rails as the s Making Your Library or Gem a Pre-Processor ------------------------------------------ -TODO: Registering gems on [Tilt](https://github.com/rtomayko/tilt) enabling Sprockets to find them. +As Sprockets uses [Tilt](https://github.com/rtomayko/tilt) as a generic +interface to different templating engines, your gem should just +implement the Tilt template protocol. Normally, you would subclass +`Tilt::Template` and reimplement `evaluate` method to return final +output. Template source is stored at `@code`. Have a look at +[`Tilt::Template`](https://github.com/rtomayko/tilt/blob/master/lib/tilt/template.rb) +sources to learn more. + +```ruby +module BangBang + class Template < ::Tilt::Template + # Adds a "!" to original template. + def evaluate(scope, locals, &block) + "#{@code}!" + end + end +end +``` + +Now that you have a `Template` class, it's time to associate it with an +extenstion for template files: + +```ruby +Sprockets.register_engine '.bang', BangBang::Template +``` Upgrading from Old Versions of Rails ------------------------------------ diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 773102400a..0228d463cf 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -30,101 +30,13 @@ config.action_controller.perform_caching = true Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or nginx), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with. -To enable page caching, you need to use the `caches_page` method. - -```ruby -class ProductsController < ActionController - - caches_page :index - - def index - @products = Product.all - end -end -``` - -Let's say you have a controller called `ProductsController` and an `index` action that lists all the products. The first time anyone requests `/products`, Rails will generate a file called `products.html` and the webserver will then look for that file before it passes the next request for `/products` to your Rails application. - -By default, the page cache directory is set to `Rails.public_path` (which is usually set to the `public` folder) and this can be configured by changing the configuration setting `config.action_controller.page_cache_directory`. Changing the default from `public` helps avoid naming conflicts, since you may want to put other static html in `public`, but changing this will require web server reconfiguration to let the web server know where to serve the cached files from. - -The Page Caching mechanism will automatically add a `.html` extension to requests for pages that do not have an extension to make it easy for the webserver to find those pages and this can be configured by changing the configuration setting `config.action_controller.default_static_extension`. - -In order to expire this page when a new product is added we could extend our example controller like this: - -```ruby -class ProductsController < ActionController - - caches_page :index - - def index - @products = Product.all - end - - def create - expire_page action: :index - end - -end -``` - -By default, page caching automatically gzips files (for example, to `products.html.gz` if user requests `/products`) to reduce the size of data transmitted (web servers are typically configured to use a moderate compression ratio as a compromise, but since precompilation happens once, compression ratio is maximum). - -Nginx is able to serve compressed content directly from disk by enabling `gzip_static`: - -```nginx -location / { - gzip_static on; # to serve pre-gzipped version -} -``` - -You can disable gzipping by setting `:gzip` option to false (for example, if action returns image): - -```ruby -caches_page :image, gzip: false -``` - -Or, you can set custom gzip compression level (level names are taken from `Zlib` constants): - -```ruby -caches_page :image, gzip: :best_speed -``` - -NOTE: Page caching ignores all parameters. For example `/products?page=1` will be written out to the filesystem as `products.html` with no reference to the `page` parameter. Thus, if someone requests `/products?page=2` later, they will get the cached first page. A workaround for this limitation is to include the parameters in the page's path, e.g. `/products/page/1`. - -INFO: Page caching runs in an after filter. Thus, invalid requests won't generate spurious cache entries as long as you halt them. Typically, a redirection in some before filter that checks request preconditions does the job. +INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching) ### Action Caching Page Caching cannot be used for actions that have before filters - for example, pages that require authentication. This is where Action Caching comes in. Action Caching works like Page Caching except the incoming web request hits the Rails stack so that before filters can be run on it before the cache is served. This allows authentication and other restrictions to be run while still serving the result of the output from a cached copy. -Clearing the cache works in a similar way to Page Caching, except you use `expire_action` instead of `expire_page`. - -Let's say you only wanted authenticated users to call actions on `ProductsController`. - -```ruby -class ProductsController < ActionController - - before_action :authenticate - caches_action :index - - def index - @products = Product.all - end - - def create - expire_action action: :index - end - -end -``` - -You can also use `:if` (or `:unless`) to pass a Proc that specifies when the action should be cached. Also, you can use `layout: false` to cache without layout so that dynamic information in the layout such as logged in user info or the number of items in the cart can be left uncached. This feature is available as of Rails 2.2. - -You can modify the default action cache path by passing a `:cache_path` option. This will be passed directly to `ActionCachePath.path_for`. This is handy for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance. - -Finally, if you are using memcached or Ehcache, you can also pass `:expires_in`. In fact, all parameters not used by `caches_action` are sent to the underlying cache store. - -INFO: Action caching runs in an after filter. Thus, invalid requests won't generate spurious cache entries as long as you halt them. Typically, a redirection in some before filter that checks request preconditions does the job. +INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching) ### Fragment Caching @@ -196,10 +108,6 @@ class ProductsController < ActionController end ``` -The second time the same query is run against the database, it's not actually going to hit the database. The first time the result is returned from the query it is stored in the query cache (in memory) and the second time it's pulled from memory. - -However, it's important to note that query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action. If you'd like to store query results in a more persistent fashion, you can in Rails by using low level caching. - Cache Stores ------------ diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 746226fa96..2790a4740a 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -225,7 +225,8 @@ $ rails generate scaffold HighScore game:string score:integer invoke test_unit create test/models/high_score_test.rb create test/fixtures/high_scores.yml - route resources :high_scores + invoke resource_route + route resources :high_scores invoke scaffold_controller create app/controllers/high_scores_controller.rb invoke erb @@ -354,7 +355,7 @@ rake assets:clean # Remove compiled assets rake assets:precompile # Compile all the assets named in config.assets.precompile rake db:create # Create the database from config/database.yml for the current Rails.env ... -rake log:clear # Truncates all *.log files in log/ to zero bytes +rake log:clear # Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development) rake middleware # Prints out your Rack middleware stack ... rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear) @@ -377,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::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, ActionDispatch::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, ActionDispatch::BestStandardsSupport Application root /home/foobar/commandsapp Environment development Database adapter sqlite3 @@ -441,7 +442,7 @@ app/model/post.rb: NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines. -By default, `rake notes` will look in the `app`, `config`, `lib`, `script` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`. +By default, `rake notes` will look in the `app`, `config`, `lib`, `bin` and `test` directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable `SOURCE_ANNOTATION_DIRECTORIES`. ```bash $ export SOURCE_ANNOTATION_DIRECTORIES='rspec,vendor' @@ -563,14 +564,20 @@ We had to create the **gitapp** directory and initialize an empty git repository $ cat config/database.yml # PostgreSQL. Versions 8.2 and up are supported. # -# Install the ruby-postgres driver: -# gem install ruby-postgres -# On Mac OS X: -# gem install ruby-postgres -- --include=/usr/local/pgsql +# Install the pg driver: +# gem install pg +# On OS X with Homebrew: +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config +# On OS X with MacPorts: +# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config # On Windows: -# gem install ruby-postgres +# gem install pg # Choose the win32 build. # Install PostgreSQL and put its /bin directory on your path. +# +# Configure Using Gemfile +# gem 'pg' +# development: adapter: postgresql encoding: unicode @@ -585,28 +592,3 @@ development: It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. NOTE. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the `rails new` command to generate the basis of your app. - -### `server` with Different Backends - -Many people have created a large number of different web servers in Ruby, and many of them can be used to run Rails. Since version 2.3, Rails uses Rack to serve its webpages, which means that any webserver that implements a Rack handler can be used. This includes WEBrick, Mongrel, Thin, and Phusion Passenger (to name a few!). - -NOTE: For more details on the Rack integration, see [Rails on Rack](rails_on_rack.html). - -To use a different server, just install its gem, then use its name for the first parameter to `rails server`: - -```bash -$ sudo gem install mongrel -Building native extensions. This could take a while... -Building native extensions. This could take a while... -Successfully installed gem_plugin-0.2.3 -Successfully installed fastthread-1.0.1 -Successfully installed cgi_multipart_eof_fix-2.5.0 -Successfully installed mongrel-1.1.5 -... -... -Installing RDoc documentation for mongrel-1.1.5... -$ rails server mongrel -=> Booting Mongrel (use 'rails server webrick' to force WEBrick) -=> Rails 3.1.0 application starting on http://0.0.0.0:3000 -... -``` diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 6e93932d49..cd0b99b177 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -177,7 +177,6 @@ The full set of methods that can be used in this block are as follows: * `javascripts` turns on the hook for JavaScript files in generators. Used in Rails for when the `scaffold` generator is run. Defaults to `true`. * `javascript_engine` configures the engine to be used (for eg. coffee) when generating assets. Defaults to `nil`. * `orm` defines which orm to use. Defaults to `false` and will use Active Record by default. -* `performance_tool` defines which performance tool to use. Defaults to `nil`. * `resource_controller` defines which generator to use for generating a controller when using `rails generate resource`. Defaults to `:controller`. * `scaffold_controller` different from `resource_controller`, defines which generator to use for generating a _scaffolded_ controller when using `rails generate scaffold`. Defaults to `:scaffold_controller`. * `stylesheets` turns on the hook for stylesheets in generators. Used in Rails for when the `scaffold` generator is run, but this hook can be used in other generates as well. Defaults to `true`. @@ -197,7 +196,7 @@ Every Rails application comes with a standard set of middleware which it uses in * `Rails::Rack::Logger` notifies the logs that the request has began. After request is complete, flushes all the logs. * `ActionDispatch::ShowExceptions` rescues any exception returned by the application and renders nice exception pages if the request is local or if `config.consider_all_requests_local` is set to `true`. If `config.action_dispatch.show_exceptions` is set to `false`, exceptions will be raised regardless. * `ActionDispatch::RequestId` makes a unique X-Request-Id header available to the response and enables the `ActionDispatch::Request#uuid` method. -* `ActionDispatch::RemoteIp` checks for IP spoofing attacks. Configurable with the `config.action_dispatch.ip_spoofing_check` and `config.action_dispatch.trusted_proxies` settings. +* `ActionDispatch::RemoteIp` checks for IP spoofing attacks and gets valid `client_ip` from request headers. Configurable with the `config.action_dispatch.ip_spoofing_check`, and `config.action_dispatch.trusted_proxies` options. * `Rack::Sendfile` intercepts responses whose body is being served from a file and replaces it with a server specific X-Sendfile header. Configurable with `config.action_dispatch.x_sendfile_header`. * `ActionDispatch::Callbacks` runs the prepare callbacks before serving the request. * `ActiveRecord::ConnectionAdapters::ConnectionManagement` cleans active connections after each request, unless the `rack.test` key in the request environment is set to `true`. @@ -304,6 +303,8 @@ The schema dumper adds one additional configuration option: * `config.action_controller.permit_all_parameters` sets all the parameters for mass assignment to be permitted by default. The default value is `false`. +* `config.action_controller.raise_on_unpermitted_parameters` enables raising an exception if parameters that are not explicitly permitted are found. The default value is `true` in development and test environments, `false` otherwise. + ### Configuring Action Dispatch * `config.action_dispatch.session_store` sets the name of the store for session data. The default is `:cookie_store`; other valid options include `:active_record_store`, `:mem_cache_store` or the name of your own custom class. @@ -583,7 +584,7 @@ Rails has 5 initialization events which can be hooked into (listed in the order * `to_prepare`: Run after the initializers are run for all Railties (including the application itself), but before eager loading and the middleware stack is built. More importantly, will run upon every request in `development`, but only once (during boot-up) in `production` and `test`. -* `before_eager_load`: This is run directly before eager loading occurs, which is the default behaviour for the `production` environment and not for the `development` environment. +* `before_eager_load`: This is run directly before eager loading occurs, which is the default behavior for the `production` environment and not for the `development` environment. * `after_initialize`: Run directly after the initialization of the application, but before the application initializers are run. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 7c5a472971..baef620c6c 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -216,6 +216,7 @@ Rails follows a simple set of coding style conventions: * Use `MyClass.my_method(my_arg)` not `my_method( my_arg )` or `my_method my_arg`. * Use `a = b` and not `a=b`. * Use assert_not methods instead of refute. +* Prefer `method { do_stuff }` instead of `method{do_stuff}` for single-line blocks. * Follow the conventions in the source you see used already. The above are guidelines — please use your best judgment in using them. diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb index e25168d58d..ff76fa2b85 100644 --- a/guides/source/credits.html.erb +++ b/guides/source/credits.html.erb @@ -28,7 +28,7 @@ Ruby on Rails Guides: Credits <h3 class="section">Rails Guides Authors</h3> <%= author('Ryan Bigg', 'radar', 'radar.png') do %> -Ryan Bigg works as a consultant at <a href="http://rubyx.com">RubyX</a> and has been working with Rails since 2006. He's co-authoring a book called <a href="http://manning.com/katz">Rails 3 in Action</a> and he's written many gems which can be seen on <a href="http://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>. +Ryan Bigg works as a consultant at <a href="http://rubyx.com">RubyX</a> and has been working with Rails since 2006. He's co-authoring a book called <a href="http://manning.com/katz">Rails 3 in Action</a> and he's written many gems which can be seen on <a href="https://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>. <% end %> <%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %> diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 2e90e8728c..5531dee343 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -29,7 +29,7 @@ The `debug` helper will return a \<pre>-tag that renders the object using the YA <%= debug @post %> <p> <b>Title:</b> - <%=h @post.title %> + <%= @post.title %> </p> ``` @@ -58,7 +58,7 @@ Displaying an instance variable, or any other object or method, in YAML format c <%= simple_format @post.to_yaml %> <p> <b>Title:</b> - <%=h @post.title %> + <%= @post.title %> </p> ``` @@ -88,7 +88,7 @@ Another useful method for displaying object values is `inspect`, especially when <%= [1, 2, 3, 4, 5].inspect %> <p> <b>Title:</b> - <%=h @post.title %> + <%= @post.title %> </p> ``` @@ -664,7 +664,7 @@ References ---------- * [ruby-debug Homepage](http://bashdb.sourceforge.net/ruby-debug/home-page.html) -* [debugger Homepage](http://github.com/cldwalker/debugger) +* [debugger Homepage](https://github.com/cldwalker/debugger) * [Article: Debugging a Rails application with ruby-debug](http://www.sitepoint.com/article/debug-rails-app-ruby-debug/) * [ruby-debug Basics screencast](http://brian.maybeyoureinsane.net/blog/2007/05/07/ruby-debug-basics-screencast/) * [Ryan Bates' debugging ruby (revised) screencast](http://railscasts.com/episodes/54-debugging-ruby-revised) diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index e779407fab..c73bbeb90d 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -84,10 +84,6 @@ url: debugging_rails_applications.html description: This guide describes how to debug Rails applications. It covers the different ways of achieving this and how to understand what is happening "behind the scenes" of your code. - - name: Performance Testing Rails Applications - url: performance_testing.html - description: This guide covers the various ways of performance testing a Ruby on Rails application. - - name: Configuring Rails Applications url: configuring.html description: This guide covers the basic configuration settings for a Rails application. @@ -106,7 +102,6 @@ description: This guide documents the asset pipeline. - name: Working with JavaScript in Rails - work_in_progress: true url: working_with_javascript_in_rails.html description: This guide covers the built-in Ajax/JavaScript functionality of Rails. - diff --git a/guides/source/engines.md b/guides/source/engines.md index 116a7e67cd..f35233993c 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -16,9 +16,9 @@ After reading this guide, you will know: What are engines? ----------------- -Engines can be considered miniature applications that provide functionality to their host applications. A Rails application is actually just a "supercharged" engine, with the `Rails::Application` class inheriting a lot of its behaviour from `Rails::Engine`. +Engines can be considered miniature applications that provide functionality to their host applications. A Rails application is actually just a "supercharged" engine, with the `Rails::Application` class inheriting a lot of its behavior from `Rails::Engine`. -Therefore, engines and applications can be thought of almost the same thing, just with very minor differences, as you'll see throughout this guide. Engines and applications also share a common structure. +Therefore, engines and applications can be thought of almost the same thing, just with subtle differences, as you'll see throughout this guide. Engines and applications also share a common structure. Engines are also closely related to plugins where the two share a common `lib` directory structure and are both generated using the `rails plugin new` generator. The difference being that an engine is considered a "full plugin" by Rails as indicated by the `--full` option that's passed to the generator command, but this guide will refer to them simply as "engines" throughout. An engine **can** be a plugin, and a plugin **can** be an engine. @@ -149,9 +149,9 @@ Lastly, the `app/views` directory contains a `layouts` folder which contains a f If you don't want to force a layout on to users of the engine, then you can delete this file and reference a different layout in the controllers of your engine. -#### `script` directory +#### `bin` directory -This directory contains one file, `script/rails`, which enables you to use the `rails` sub-commands and generators just like you would within an application. This means that you will very easily be able to generate new controllers and models for this engine by running commands like this: +This directory contains one file, `bin/rails`, which enables you to use the `rails` sub-commands and generators just like you would within an application. This means that you will very easily be able to generate new controllers and models for this engine by running commands like this: ```bash rails g model @@ -171,7 +171,7 @@ end This line mounts the engine at the path `/blorgh`, which will make it accessible through the application only at that path. -Also in the test directory is the `test/integration` directory, where integration tests for the engine should be placed. Other directories can be created in the `test` directory also. For example, you may wish to create a `test/models` directory for your models tests. +In the test directory there is the `test/integration` directory, where integration tests for the engine should be placed. Other directories can be created in the `test` directory as well. For example, you may wish to create a `test/models` directory for your models tests. Providing engine functionality ------------------------------ @@ -232,7 +232,8 @@ Blorgh::Engine.routes.draw do end ``` -Note here that the routes are drawn upon the `Blorgh::Engine` object rather than the `YourApp::Application` class. This is so that the engine routes are confined to the engine itself and can be mounted at a specific point as shown in the [test directory](#test-directory) section. This is also what causes the engine's routes to be isolated from those routes that are within the application. This is discussed further in the [Routes](#routes) section of this guide. +Note here that the routes are drawn upon the `Blorgh::Engine` object rather than the `YourApp::Application` class. This is so that the engine routes are confined to the engine itself and can be mounted at a specific point as shown in the [test directory](#test-directory) section. It also causes the engine's routes to be isolated from those routes that are within the application. The [Routes](#routes) section of +this guide describes it in details. Next, the `scaffold_controller` generator is invoked, generating a controller called `Blorgh::PostsController` (at `app/controllers/blorgh/posts_controller.rb`) and its related views at `app/views/blorgh/posts`. This generator also generates a test for the controller (`test/controllers/blorgh/posts_controller_test.rb`) and a helper (`app/helpers/blorgh/posts_controller.rb`). @@ -258,7 +259,7 @@ module Blorgh end ``` -This helps prevent conflicts with any other engine or application that may have a post resource also. +This helps prevent conflicts with any other engine or application that may have a post resource as well. Finally, two files that are the assets for this resource are generated, `app/assets/javascripts/blorgh/posts.js` and `app/assets/javascripts/blorgh/posts.css`. You'll see how to use these a little later. @@ -287,7 +288,7 @@ Now people will only need to go to the root of the engine to see all the posts, ### Generating a comments resource -Now that the engine has the ability to create new blog posts, it only makes sense to add commenting functionality as well. To do get this, you'll need to generate a comment model, a comment controller and then modify the posts scaffold to display comments and allow people to create new ones. +Now that the engine can to create new blog posts, it only makes sense to add commenting functionality as well. To do get this, you'll need to generate a comment model, a comment controller and then modify the posts scaffold to display comments and allow people to create new ones. Run the model generator and tell it to generate a `Comment` model, with the related table having two columns: a `post_id` integer and `text` text column. @@ -531,7 +532,7 @@ before_save :set_author private def set_author - self.author = User.find_or_create_by_name(author_name) + self.author = User.find_or_create_by(name: author_name) end ``` @@ -630,7 +631,7 @@ belongs_to :author, class_name: Blorgh.user_class The `set_author` method also located in this class should also use this class: ```ruby -self.author = Blorgh.user_class.constantize.find_or_create_by_name(author_name) +self.author = Blorgh.user_class.constantize.find_or_create_by(name: author_name) ``` To save having to call `constantize` on the `user_class` result all the time, you could instead just override the `user_class` getter method inside the `Blorgh` module in the `lib/blorgh.rb` file to always call `constantize` on the saved value before returning the result: @@ -644,10 +645,10 @@ end This would then turn the above code for `set_author` into this: ```ruby -self.author = Blorgh.user_class.find_or_create_by_name(author_name) +self.author = Blorgh.user_class.find_or_create_by(name: author_name) ``` -Resulting in something a little shorter, and more implicit in its behaviour. The `user_class` method should always return a `Class` object. +Resulting in something a little shorter, and more implicit in its behavior. The `user_class` method should always return a `Class` object. 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. @@ -661,7 +662,7 @@ WARNING: It's very important here to use the `String` version of the class, rath Go ahead and try to create a new post. You will see that it works exactly in the same way as before, except this time the engine is using the configuration setting in `config/initializers/blorgh.rb` to learn what the class is. -There are now no strict dependencies on what the class is, only what the API for the class must be. The engine simply requires this class to define a `find_or_create_by_name` method which returns an object of that class to be associated with a post when it's created. This object, of course, should have some sort of identifier by which it can be referenced. +There are now no strict dependencies on what the class is, only what the API for the class must be. The engine simply requires this class to define a `find_or_create_by` method which returns an object of that class to be associated with a post when it's created. This object, of course, should have some sort of identifier by which it can be referenced. #### General engine configuration @@ -800,7 +801,7 @@ module Blorgh::Concerns::Models::Post private def set_author - self.author = User.find_or_create_by_name(author_name) + self.author = User.find_or_create_by(name: author_name) end end @@ -914,9 +915,10 @@ For more information, read the [Asset Pipeline guide](http://guides.rubyonrails. ### Other gem dependencies -Gem dependencies inside an engine should be specified inside the `.gemspec` file at the root of the engine. The reason for this is because the engine may +Gem dependencies inside an engine should be specified inside the +`.gemspec` file at the root of the engine. The reason is that the engine may be installed as a gem. If dependencies were to be specified inside the `Gemfile`, -these would not be recognised by a traditional gem install and so they would not +these would not be recognized by a traditional gem install and so they would not be installed, causing the engine to malfunction. To specify a dependency that should be installed with the engine during a diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index 8ab44ea0bb..b8681d493a 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -1,7 +1,7 @@ Form Helpers ============ -Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of form control naming and their numerous attributes. Rails deals away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use. +Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of form control naming and their numerous attributes. Rails does away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use. After reading this guide, you will know: @@ -148,7 +148,9 @@ Output: As with `check_box_tag`, the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one, and `params[:age]` will contain either "child" or "adult". -NOTE: Always use labels for checkbox and radio buttons. They associate text with a specific option and make it easier for users to click the inputs by expanding the clickable region. +NOTE: Always use labels for checkbox and radio buttons. They associate text with a specific option and, +by expanding the clickable region, +make it easier for users to click the inputs. ### Other Helpers of Interest @@ -215,7 +217,7 @@ will produce output similar to <input id="person_name" name="person[name]" type="text" value="Henry"/> ``` -Upon form submission the value entered by the user will be stored in `params[:person][:name]`. The `params[:person]` hash is suitable for passing to `Person.new` or, if `@person` is an instance of Person, `@person.update_attributes`. While the name of an attribute is the most common second parameter to these helpers this is not compulsory. In the example above, as long as person objects have a `name` and a `name=` method Rails will be happy. +Upon form submission the value entered by the user will be stored in `params[:person][:name]`. The `params[:person]` hash is suitable for passing to `Person.new` or, if `@person` is an instance of Person, `@person.update`. While the name of an attribute is the most common second parameter to these helpers this is not compulsory. In the example above, as long as person objects have a `name` and a `name=` method Rails will be happy. WARNING: You must pass the name of an instance variable, i.e. `:person` or `"person"`, not an actual instance of your model object. @@ -236,7 +238,7 @@ end The corresponding view `app/views/articles/new.html.erb` using `form_for` looks like this: ```erb -<%= form_for @article, url: {action: "create"}, html => {class: "nifty_form"} do |f| %> +<%= form_for @article, url: {action: "create"}, html: {class: "nifty_form"} do |f| %> <%= f.text_field :title %> <%= f.text_area :body, size: "60x12" %> <%= f.submit "Create" %> @@ -426,7 +428,7 @@ WARNING: when `:inlude_blank` or `:prompt:` are not present, `:include_blank` is You can add arbitrary attributes to the options using hashes: ```html+erb -<%= options_for_select([['Lisbon', 1, 'data-size': '2.8 million'], ['Madrid', 2, 'data-size': '3.2 million']], 2) %> +<%= options_for_select([['Lisbon', 1, {'data-size' => '2.8 million'}], ['Madrid', 2, {'data-size' => '3.2 million'}]], 2) %> output: @@ -458,7 +460,7 @@ As with other helpers, if you were to use the `select` helper on a form builder <%= f.select(:city_id, ...) %> ``` -WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of ` ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) ` when you pass the `params` hash to `Person.new` or `update_attributes`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. +WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of ` ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) ` when you pass the `params` hash to `Person.new` or `update`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. ### Option Tags from a Collection of Arbitrary Objects @@ -495,7 +497,7 @@ To leverage time zone support in Rails, you have to ask your users what time zon There is also `time_zone_options_for_select` helper for a more manual (therefore more customizable) way of doing this. Read the API documentation to learn about the possible arguments for these two methods. -Rails _used_ to have a `country_select` helper for choosing countries, but this has been extracted to the [country_select plugin](https://github.com/chrislerum/country_select). When using this, be aware that the exclusion or inclusion of certain names from the list can be somewhat controversial (and was the reason this functionality was extracted from Rails). +Rails _used_ to have a `country_select` helper for choosing countries, but this has been extracted to the [country_select plugin](https://github.com/stefanpenner/country_select). When using this, be aware that the exclusion or inclusion of certain names from the list can be somewhat controversial (and was the reason this functionality was extracted from Rails). Using Date and Time Form Helpers -------------------------------- @@ -534,7 +536,7 @@ The `:prefix` option is the key used to retrieve the hash of date components fro ### Model Object Helpers `select_date` does not work well with forms that update or create Active Record objects as Active Record expects each element of the `params` hash to correspond to one attribute. -The model object helpers for dates and times submit parameters with special names, when Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example: +The model object helpers for dates and times submit parameters with special names; when Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example: ```erb <%= date_select :person, :birth_date %> @@ -554,7 +556,7 @@ which results in a `params` hash like {:person => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}} ``` -When this is passed to `Person.new` (or `update_attributes`), Active Record spots that these parameters should all be used to construct the `birth_date` attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as `Date.civil`. +When this is passed to `Person.new` (or `update`), Active Record spots that these parameters should all be used to construct the `birth_date` attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as `Date.civil`. ### Common Options @@ -620,7 +622,7 @@ Unlike other forms making an asynchronous file upload form is not as simple as p Customizing Form Builders ------------------------- -As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way you can also subclass FormBuilder and add the helpers there. For example +As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way, you can also subclass FormBuilder and add the helpers there. For example ```erb <%= form_for @person do |f| %> @@ -805,7 +807,7 @@ Sometimes when you submit data to an external resource, like payment gateway, fi <% end %> ``` -The same technique is available for the `form_for` too: +The same technique is also available for `form_for`: ```erb <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %> diff --git a/guides/source/generators.md b/guides/source/generators.md index 62de5a70bb..8b91dfc5a5 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -176,7 +176,8 @@ $ rails generate scaffold User name:string invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml - route resources :users + invoke resource_route + route resources :users invoke scaffold_controller create app/controllers/users_controller.rb invoke erb @@ -192,8 +193,13 @@ $ rails generate scaffold User name:string create app/helpers/users_helper.rb invoke test_unit create test/helpers/users_helper_test.rb - invoke stylesheets - create app/assets/stylesheets/scaffold.css + invoke assets + invoke coffee + create app/assets/javascripts/users.js.coffee + invoke scss + create app/assets/stylesheets/users.css.scss + invoke scss + create app/assets/stylesheets/scaffolds.css.scss ``` Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication. @@ -350,6 +356,7 @@ $ rails generate scaffold Comment body:text invoke shoulda create test/models/comment_test.rb create test/fixtures/comments.yml + invoke resource_route route resources :comments invoke scaffold_controller create app/controllers/comments_controller.rb @@ -360,13 +367,16 @@ $ rails generate scaffold Comment body:text create app/views/comments/show.html.erb create app/views/comments/new.html.erb create app/views/comments/_form.html.erb - create app/views/layouts/comments.html.erb invoke shoulda create test/controllers/comments_controller_test.rb invoke my_helper create app/helpers/comments_helper.rb invoke shoulda create test/helpers/comments_helper_test.rb + invoke assets + invoke coffee + create app/assets/javascripts/comments.js.coffee + invoke scss ``` Fallbacks allow your generators to have a single responsibility, increasing code reuse and reducing the amount of duplication. diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 02ec024e5b..7d86b3866a 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -45,7 +45,7 @@ code while accomplishing more than many other languages and frameworks. Experienced Rails developers also report that it makes web application development more fun. -Rails is opinionated software. It makes the assumption that there is a "best" +Rails is opinionated software. It makes the assumption that there is the "best" way to do things, and it's designed to encourage that way - and in some cases to discourage alternatives. If you learn "The Rails Way" you'll probably discover a tremendous increase in productivity. If you persist in bringing old habits from @@ -71,7 +71,9 @@ By following along with this guide, you'll create a Rails project called (very) simple weblog. Before you can start building the application, you need to make sure that you have Rails itself installed. -TIP: The examples below use # and $ to denote superuser and regular user terminal prompts respectively in a UNIX-like OS. If you are using Windows, your prompt will look something like c:\source_code> +TIP: The examples below use `#` and `$` to denote superuser and regular +user terminal prompts respectively in a UNIX-like OS. If you are using +Windows, your prompt will look something like `c:\source_code>` ### Installing Rails @@ -132,17 +134,16 @@ application. Most of the work in this tutorial will happen in the `app/` folder, | File/Folder | Purpose | | ----------- | ------- | |app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.| +|bin/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.| |config/|Configure your application's runtime rules, routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html)| |config.ru|Rack configuration for Rack based servers used to start the application.| |db/|Contains your current database schema, as well as the database migrations.| -|doc/|In-depth documentation for your application.| |Gemfile<br />Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see [the Bundler website](http://gembundler.com) | |lib/|Extended modules for your application.| |log/|Application log files.| |public/|The only folder seen to the world as-is. Contains the static files and compiled assets.| |Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.| |README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.| -|script/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.| |test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html)| |tmp/|Temporary files (like cache, pid and session files)| |vendor/|A place for all third-party code. In a typical Rails application, this includes Ruby Gems and the Rails source code (if you optionally install it into your project).| @@ -417,7 +418,7 @@ def create end ``` -The `render` method here is taking a very simple hash with a key of `text` and value of `params[:post].inspect`. The `params` method is the object which represents the parameters (or fields) coming in from the form. The `params` method returns a `HashWithIndifferentAccess` object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form. +The `render` method here is taking a very simple hash with a key of `text` and value of `params[:post].inspect`. The `params` method is the object which represents the parameters (or fields) coming in from the form. The `params` method returns an `ActiveSupport::HashWithIndifferentAccess` object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form. If you re-submit the form one more time you'll now no longer get the missing template error. Instead, you'll see something that looks like the following: @@ -830,7 +831,7 @@ it look as follows: <h1>Editing post</h1> <%= form_for :post, url: { action: :update, id: @post.id }, -method: :put do |f| %> +method: :patch do |f| %> <% if @post.errors.any? %> <div id="errorExplanation"> <h2><%= pluralize(@post.errors.count, "error") %> prohibited @@ -863,7 +864,7 @@ method: :put do |f| %> This time we point the form to the `update` action, which is not defined yet but will be very soon. -The `method: :put` option tells Rails that we want this form to be +The `method: :patch` option tells Rails that we want this form to be submitted via the `PUT` HTTP method which is the HTTP method you're expected to use to **update** resources according to the REST protocol. @@ -873,7 +874,7 @@ Next, we need to add the `update` action. The file `config/routes.rb` will need just one more line: ```ruby -put "posts/:id" => "posts#update" +patch "posts/:id" => "posts#update" ``` And then create the `update` action in `app/controllers/posts_controller.rb`: @@ -882,7 +883,7 @@ And then create the `update` action in `app/controllers/posts_controller.rb`: def update @post = Post.find(params[:id]) - if @post.update_attributes(params[:post]) + if @post.update(params[:post]) redirect_to action: :show, id: @post.id else render 'edit' @@ -890,13 +891,13 @@ def update end ``` -The new method, `update_attributes`, is used when you want to update a record +The new method, `update`, is used when you want to update a record that already exists, and it accepts a hash containing the attributes that you want to update. As before, if there was an error updating the post we want to show the form back to the user. -TIP: You don't need to pass all attributes to `update_attributes`. For -example, if you'd call `@post.update_attributes(title: 'A new title')` +TIP: You don't need to pass all attributes to `update`. For +example, if you'd call `@post.update(title: 'A new title')` Rails would only update the `title` attribute, leaving all other attributes untouched. @@ -1051,7 +1052,7 @@ called `post_url` and `post_path` available to our application. These are precisely the methods that the `form_for` needs when editing a post, and so now you'll be able to update posts again. -NOTE: The `:as` option is available on the `post`, `put`, `delete` and `match` +NOTE: The `:as` option is available on the `post`, `patch`, `put`, `delete` and `match` routing methods also. ### Deleting Posts @@ -1145,7 +1146,7 @@ get "posts/new" post "posts" => "posts#create" get "posts/:id" => "posts#show", as: :post get "posts/:id/edit" => "posts#edit" -put "posts/:id" => "posts#update" +patch "posts/:id" => "posts#update" delete "posts/:id" => "posts#destroy" ``` diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 2e61bea5ea..69232d9bd4 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -897,7 +897,7 @@ The I18n API will catch all of these exceptions when they are thrown in the back The reason for this is that during development you'd usually want your views to still render even though a translation is missing. -In other contexts you might want to change this behaviour, though. E.g. the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose a different exception handler can be specified. The specified exception handler must be a method on the I18n module or a class with `#call` method: +In other contexts you might want to change this behavior, though. E.g. the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose a different exception handler can be specified. The specified exception handler must be a method on the I18n module or a class with `#call` method: ```ruby module I18n @@ -927,7 +927,7 @@ else end ``` -Another example where the default behaviour is less desirable is the Rails TranslationHelper which provides the method `#t` (as well as `#translate`). When a `MissingTranslationData` exception occurs in this context, the helper wraps the message into a span with the CSS class `translation_missing`. +Another example where the default behavior is less desirable is the Rails TranslationHelper which provides the method `#t` (as well as `#translate`). When a `MissingTranslationData` exception occurs in this context, the helper wraps the message into a span with the CSS class `translation_missing`. To do so, the helper forces `I18n#translate` to raise exceptions no matter what exception handler is defined by setting the `:raise` option: diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 32df508f9c..8ba5fa4601 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -26,126 +26,16 @@ quickly. Launch! ------- -A Rails application is usually started with the command `rails server`. +Now we finally boot and initialize the app. It all starts with your app's +`bin/rails` executable. A Rails application is usually started by running +`rails console` or `rails server`. ### `bin/rails` -The actual `rails` command is kept in _bin/rails_: - -```ruby -#!/usr/bin/env ruby - -if File.exists?(File.join(File.expand_path('../../..', __FILE__), '.git')) - railties_path = File.expand_path('../../lib', __FILE__) - $:.unshift(railties_path) -end -require "rails/cli" -``` - -This file will first attempt to push the `railties/lib` directory if -present, and then requires `rails/cli`. - -### `railties/lib/rails/cli.rb` - -This file looks like this: - -```ruby -require 'rbconfig' -require 'rails/script_rails_loader' - -# If we are inside a Rails application this method performs an exec and thus -# the rest of this script is not run. -Rails::ScriptRailsLoader.exec_script_rails! - -require 'rails/ruby_version_check' -Signal.trap("INT") { puts; exit(1) } - -if ARGV.first == 'plugin' - ARGV.shift - require 'rails/commands/plugin_new' -else - require 'rails/commands/application' -end -``` - -The `rbconfig` file from the Ruby standard library provides us with the `RbConfig` class which contains detailed information about the Ruby environment, including how Ruby was compiled. We can see this in use in `railties/lib/rails/script_rails_loader`. - -```ruby -require 'pathname' - -module Rails - module ScriptRailsLoader - RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"] - SCRIPT_RAILS = File.join('script', 'rails') - ... - - end -end -``` - -The `rails/script_rails_loader` file uses `RbConfig::Config` to obtain the `bin_dir` and `ruby_install_name` values for the configuration which together form the path to the Ruby interpreter. The `RbConfig::CONFIG["EXEEXT"]` will suffix this path with ".exe" if the script is running on Windows. This constant is used later on in `exec_script_rails!`. As for the `SCRIPT_RAILS` constant, we'll see that when we get to the `in_rails_application?` method. - -Back in `rails/cli`, the next line is this: - -```ruby -Rails::ScriptRailsLoader.exec_script_rails! -``` - -This method is defined in `rails/script_rails_loader`: - -```ruby -def self.exec_script_rails! - cwd = Dir.pwd - return unless in_rails_application? || in_rails_application_subdirectory? - exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? - 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. - exec_script_rails! unless cwd == Dir.pwd - end -rescue SystemCallError - # could not chdir, no problem just return -end -``` - -This method will first check if the current working directory (`cwd`) is a Rails application or a subdirectory of one. This is determined by the `in_rails_application?` method: - -```ruby -def self.in_rails_application? - File.exists?(SCRIPT_RAILS) -end -``` - -The `SCRIPT_RAILS` constant defined earlier is used here, with `File.exists?` checking for its presence in the current directory. If this method returns `false` then `in_rails_application_subdirectory?` will be used: - -```ruby -def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd)) - File.exists?(File.join(path, SCRIPT_RAILS)) || !path.root? && in_rails_application_subdirectory?(path.parent) -end -``` - -This climbs the directory tree until it reaches a path which contains a `script/rails` file. If a directory containing this file is reached then this line will run: - -```ruby -exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? -``` - -This is effectively the same as running `ruby script/rails [arguments]`, where `[arguments]` at this point in time is simply "server". - -Rails Initialization --------------------- - -Only now we finally start the real initialization process, beginning -with `script/rails`. - -TIP: If you execute `script/rails` directly from your Rails app you will -skip executing all the code that we've just described. - -### `script/rails` - This file is as follows: ```ruby +#!/usr/bin/env ruby APP_PATH = File.expand_path('../../config/application', __FILE__) require File.expand_path('../../config/boot', __FILE__) require 'rails/commands' @@ -227,18 +117,18 @@ If we used `s` rather than `server`, Rails will use the `aliases` defined in the ```ruby when 'server' # Change to the application's path if there is no config.ru file in current dir. - # This allows us to run script/rails server from other directories, but still get + # This allows us to run `rails server` from other directories, but still get # the main config.ru and properly set the tmp directory. Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru")) require 'rails/commands/server' - Rails::Server.new.tap { |server| + Rails::Server.new.tap do |server| # We need to require application after the server sets environment, # otherwise the --environment option given to the server won't propagate. require APP_PATH Dir.chdir(Rails.application.root) server.start - } + end ``` This file will change into the root of the directory (a path two directories back from `APP_PATH` which points at `config/application.rb`), but only if the `config.ru` file isn't found. This then requires `rails/commands/server` which sets up the `Rails::Server` class. diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index dbaa3ec576..7c26512046 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -97,7 +97,7 @@ NOTE: The actual rendering is done by subclasses of `ActionView::TemplateHandler ### Using `render` -In most cases, the `ActionController::Base#render` method does the heavy lifting of rendering your application's content for use by a browser. There are a variety of ways to customize the behaviour of `render`. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well. +In most cases, the `ActionController::Base#render` method does the heavy lifting of rendering your application's content for use by a browser. There are a variety of ways to customize the behavior of `render`. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well. TIP: If you want to see the exact results of a call to `render` without needing to inspect it in a browser, you can call `render_to_string`. This method takes exactly the same options as `render`, but it returns a string instead of sending a response back to the browser. @@ -137,7 +137,7 @@ If you want to render the view that corresponds to a different template within t ```ruby def update @book = Book.find(params[:id]) - if @book.update_attributes(params[:book]) + if @book.update(params[:book]) redirect_to(@book) else render "edit" @@ -145,14 +145,14 @@ def update end ``` -If the call to `update_attributes` fails, calling the `update` action in this controller will render the `edit.html.erb` template belonging to the same controller. +If the call to `update` fails, calling the `update` action in this controller will render the `edit.html.erb` template belonging to the same controller. If you prefer, you can use a symbol instead of a string to specify the action to render: ```ruby def update @book = Book.find(params[:id]) - if @book.update_attributes(params[:book]) + if @book.update(params[:book]) redirect_to(@book) else render :edit @@ -782,7 +782,7 @@ To include `app/assets/stylesheets/main.css` and `app/assets/stylesheets/columns To include `app/assets/stylesheets/main.css` and `app/assets/stylesheets/photos/columns.css`: ```erb -<%= stylesheet_link_tag "main", "/photos/columns" %> +<%= stylesheet_link_tag "main", "photos/columns" %> ``` To include `http://example.com/main.css`: diff --git a/guides/source/migrations.md b/guides/source/migrations.md index ca3d4bb4fa..617e01bd15 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -202,6 +202,25 @@ end This migration will create a `user_id` column and appropriate index. +There is also a generator which will produce join tables if `JoinTable` is part of the name: + +```bash +rails g migration CreateJoinTableCustomerProduct customer product +``` + +will produce the following migration: + +```ruby +class CreateJoinTableCustomerProduct < ActiveRecord::Migration + def change + create_join_table :customers, :products do |t| + # t.index [:customer_id, :product_id] + # t.index [:product_id, :customer_id] + end + end +end +``` + ### Model Generators The model and scaffold generators will create migrations appropriate for adding diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md index 2b46a9d51e..93d8e8dfcd 100644 --- a/guides/source/nested_model_forms.md +++ b/guides/source/nested_model_forms.md @@ -56,7 +56,7 @@ end ### Custom model -As you might have inflected from this explanation, you _don’t_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behaviour: +As you might have inflected from this explanation, you _don’t_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behavior: #### Single associated object diff --git a/guides/source/performance_testing.md b/guides/source/performance_testing.md deleted file mode 100644 index ee0059623c..0000000000 --- a/guides/source/performance_testing.md +++ /dev/null @@ -1,686 +0,0 @@ -Performance Testing Rails Applications -====================================== - -This guide covers the various ways of performance testing a Ruby on Rails -application. - -After reading this guide, you will know: - -* The various types of benchmarking and profiling metrics. -* How to generate performance and benchmarking tests. -* How to install and use a GC-patched Ruby binary to measure memory usage and object - allocation. -* The benchmarking information provided by Rails inside the log files. -* Various tools facilitating benchmarking and profiling. - -Performance testing is an integral part of the development cycle. It is very -important that you don't make your end users wait for too long before the page -is completely loaded. Ensuring a pleasant browsing experience for end users and -cutting the cost of unnecessary hardware is important for any non-trivial web -application. - --------------------------------------------------------------------------------- - -Performance Test Cases ----------------------- - -Rails performance tests are a special type of integration tests, designed for -benchmarking and profiling the test code. With performance tests, you can -determine where your application's memory or speed problems are coming from, -and get a more in-depth picture of those problems. - -In a freshly generated Rails application, `test/performance/browsing_test.rb` -contains an example of a performance test: - -```ruby -require 'test_helper' -require 'rails/performance_test_help' - -class BrowsingTest < ActionDispatch::PerformanceTest - # Refer to the documentation for all available options - # self.profile_options = { runs: 5, metrics: [:wall_time, :memory], - # output: 'tmp/performance', formats: [:flat] } - - test "homepage" do - get '/' - end -end -``` - -This example is a simple performance test case for profiling a GET request to -the application's homepage. - -### Generating Performance Tests - -Rails provides a generator called `performance_test` for creating new -performance tests: - -```bash -$ rails generate performance_test homepage -``` - -This generates `homepage_test.rb` in the `test/performance` directory: - -```ruby -require 'test_helper' -require 'rails/performance_test_help' - -class HomepageTest < ActionDispatch::PerformanceTest - # Refer to the documentation for all available options - # self.profile_options = { runs: 5, metrics: [:wall_time, :memory], - # output: 'tmp/performance', formats: [:flat] } - - test "homepage" do - get '/' - end -end -``` - -### Examples - -Let's assume your application has the following controller and model: - -```ruby -# routes.rb -root to: 'home#dashboard' -resources :posts - -# home_controller.rb -class HomeController < ApplicationController - def dashboard - @users = User.last_ten.includes(:avatars) - @posts = Post.all_today - end -end - -# posts_controller.rb -class PostsController < ApplicationController - def create - @post = Post.create(params[:post]) - redirect_to(@post) - end -end - -# post.rb -class Post < ActiveRecord::Base - before_save :recalculate_costly_stats - - def slow_method - # I fire gallzilion queries sleeping all around - end - - private - - def recalculate_costly_stats - # CPU heavy calculations - end -end -``` - -#### Controller Example - -Because performance tests are a special kind of integration test, you can use -the `get` and `post` methods in them. - -Here's the performance test for `HomeController#dashboard` and -`PostsController#create`: - -```ruby -require 'test_helper' -require 'rails/performance_test_help' - -class PostPerformanceTest < ActionDispatch::PerformanceTest - def setup - # Application requires logged-in user - login_as(:lifo) - end - - test "homepage" do - get '/dashboard' - end - - test "creating new post" do - post '/posts', post: { body: 'lifo is fooling you' } - end -end -``` - -You can find more details about the `get` and `post` methods in the -[Testing Rails Applications](testing.html) guide. - -#### Model Example - -Even though the performance tests are integration tests and hence closer to -the request/response cycle by nature, you can still performance test pure model -code. - -Performance test for `Post` model: - -```ruby -require 'test_helper' -require 'rails/performance_test_help' - -class PostModelTest < ActionDispatch::PerformanceTest - test "creation" do - Post.create body: 'still fooling you', cost: '100' - end - - test "slow method" do - # Using posts(:awesome) fixture - posts(:awesome).slow_method - end -end -``` - -### Modes - -Performance tests can be run in two modes: Benchmarking and Profiling. - -#### Benchmarking - -Benchmarking makes it easy to quickly gather a few metrics about each test run. -By default, each test case is run **4 times** in benchmarking mode. - -To run performance tests in benchmarking mode: - -```bash -$ rake test:benchmark -``` - -#### Profiling - -Profiling allows you to make an in-depth analysis of each of your tests by using -an external profiler. Depending on your Ruby interpreter, this profiler can be -native (Rubinius, JRuby) or not (MRI, which uses RubyProf). By default, each -test case is run **once** in profiling mode. - -To run performance tests in profiling mode: - -```bash -$ rake test:profile -``` - -### Metrics - -Benchmarking and profiling run performance tests and give you multiple metrics. -The availability of each metric is determined by the interpreter being used—none -of them support all metrics—and by the mode in use. A brief description of each -metric and their availability across interpreters/modes is given below. - -#### Wall Time - -Wall time measures the real world time elapsed during the test run. It is -affected by any other processes concurrently running on the system. - -#### Process Time - -Process time measures the time taken by the process. It is unaffected by any -other processes running concurrently on the same system. Hence, process time -is likely to be constant for any given performance test, irrespective of the -machine load. - -#### CPU Time - -Similar to process time, but leverages the more accurate CPU clock counter -available on the Pentium and PowerPC platforms. - -#### User Time - -User time measures the amount of time the CPU spent in user-mode, i.e. within -the process. This is not affected by other processes and by the time it possibly -spends blocked. - -#### Memory - -Memory measures the amount of memory used for the performance test case. - -#### Objects - -Objects measures the number of objects allocated for the performance test case. - -#### GC Runs - -GC Runs measures the number of times GC was invoked for the performance test case. - -#### GC Time - -GC Time measures the amount of time spent in GC for the performance test case. - -#### Metric Availability - -##### Benchmarking - -| Interpreter | Wall Time | Process Time | CPU Time | User Time | Memory | Objects | GC Runs | GC Time | -| ------------ | --------- | ------------ | -------- | --------- | ------ | ------- | ------- | ------- | -| **MRI** | yes | yes | yes | no | yes | yes | yes | yes | -| **REE** | yes | yes | yes | no | yes | yes | yes | yes | -| **Rubinius** | yes | no | no | no | yes | yes | yes | yes | -| **JRuby** | yes | no | no | yes | yes | yes | yes | yes | - -##### Profiling - -| Interpreter | Wall Time | Process Time | CPU Time | User Time | Memory | Objects | GC Runs | GC Time | -| ------------ | --------- | ------------ | -------- | --------- | ------ | ------- | ------- | ------- | -| **MRI** | yes | yes | no | no | yes | yes | yes | yes | -| **REE** | yes | yes | no | no | yes | yes | yes | yes | -| **Rubinius** | yes | no | no | no | no | no | no | no | -| **JRuby** | yes | no | no | no | no | no | no | no | - -NOTE: To profile under JRuby you'll need to run `export JRUBY_OPTS="-Xlaunch.inproc=false --profile.api"` -**before** the performance tests. - -### Understanding the Output - -Performance tests generate different outputs inside `tmp/performance` directory -depending on their mode and metric. - -#### Benchmarking - -In benchmarking mode, performance tests generate two types of outputs. - -##### Command Line - -This is the primary form of output in benchmarking mode. Example: - -```bash -BrowsingTest#test_homepage (31 ms warmup) - wall_time: 6 ms - memory: 437.27 KB - objects: 5,514 - gc_runs: 0 - gc_time: 19 ms -``` - -##### CSV Files - -Performance test results are also appended to `.csv` files inside `tmp/performance`. -For example, running the default `BrowsingTest#test_homepage` will generate -following five files: - -* BrowsingTest#test_homepage_gc_runs.csv -* BrowsingTest#test_homepage_gc_time.csv -* BrowsingTest#test_homepage_memory.csv -* BrowsingTest#test_homepage_objects.csv -* BrowsingTest#test_homepage_wall_time.csv - -As the results are appended to these files each time the performance tests are -run in benchmarking mode, you can collect data over a period of time. This can -be very helpful in analyzing the effects of code changes. - -Sample output of `BrowsingTest#test_homepage_wall_time.csv`: - -```bash -measurement,created_at,app,rails,ruby,platform -0.00738224999999992,2009-01-08T03:40:29Z,,3.0.0,ruby-1.8.7.249,x86_64-linux -0.00755874999999984,2009-01-08T03:46:18Z,,3.0.0,ruby-1.8.7.249,x86_64-linux -0.00762099999999993,2009-01-08T03:49:25Z,,3.0.0,ruby-1.8.7.249,x86_64-linux -0.00603075000000008,2009-01-08T04:03:29Z,,3.0.0,ruby-1.8.7.249,x86_64-linux -0.00619899999999995,2009-01-08T04:03:53Z,,3.0.0,ruby-1.8.7.249,x86_64-linux -0.00755449999999991,2009-01-08T04:04:55Z,,3.0.0,ruby-1.8.7.249,x86_64-linux -0.00595999999999997,2009-01-08T04:05:06Z,,3.0.0,ruby-1.8.7.249,x86_64-linux -0.00740450000000004,2009-01-09T03:54:47Z,,3.0.0,ruby-1.8.7.249,x86_64-linux -0.00603150000000008,2009-01-09T03:54:57Z,,3.0.0,ruby-1.8.7.249,x86_64-linux -0.00771250000000012,2009-01-09T15:46:03Z,,3.0.0,ruby-1.8.7.249,x86_64-linux -``` - -#### Profiling - -In profiling mode, performance tests can generate multiple types of outputs. -The command line output is always presented but support for the others is -dependent on the interpreter in use. A brief description of each type and -their availability across interpreters is given below. - -##### Command Line - -This is a very basic form of output in profiling mode: - -```bash -BrowsingTest#test_homepage (58 ms warmup) - process_time: 63 ms - memory: 832.13 KB - objects: 7,882 -``` - -##### Flat - -Flat output shows the metric—time, memory, etc—measure in each method. -[Check Ruby-Prof documentation for a better explanation](http://ruby-prof.rubyforge.org/files/examples/flat_txt.html). - -##### Graph - -Graph output shows the metric measure in each method, which methods call it and -which methods it calls. [Check Ruby-Prof documentation for a better explanation](http://ruby-prof.rubyforge.org/files/examples/graph_txt.html). - -##### Tree - -Tree output is profiling information in calltree format for use by [kcachegrind](http://kcachegrind.sourceforge.net/html/Home.html) -and similar tools. - -##### Output Availability - -| | Flat | Graph | Tree | -| ------------ | ---- | ----- | ---- | -| **MRI** | yes | yes | yes | -| **REE** | yes | yes | yes | -| **Rubinius** | yes | yes | no | -| **JRuby** | yes | yes | no | - -### Tuning Test Runs - -Test runs can be tuned by setting the `profile_options` class variable on your -test class. - -```ruby -require 'test_helper' -require 'rails/performance_test_help' - -class BrowsingTest < ActionDispatch::PerformanceTest - self.profile_options = { runs: 5, metrics: [:wall_time, :memory] } - - test "homepage" - get '/' - end -end -``` - -In this example, the test would run 5 times and measure wall time and memory. -There are a few configurable options: - -| Option | Description | Default | Mode | -| ---------- | ------------------------------------------ | ----------------------------- | --------- | -| `:runs` | Number of runs. | Benchmarking: 4, Profiling: 1 | Both | -| `:output` | Directory to use when writing the results. | `tmp/performance` | Both | -| `:metrics` | Metrics to use. | See below. | Both | -| `:formats` | Formats to output to. | See below. | Profiling | - -Metrics and formats have different defaults depending on the interpreter in use. - -| Interpreter | Mode | Default metrics | Default formats | -| -------------- | ------------ | ------------------------------------------------------- | ----------------------------------------------- | -| **MRI/REE** | Benchmarking | `[:wall_time, :memory, :objects, :gc_runs, :gc_time]` | N/A | -| | Profiling | `[:process_time, :memory, :objects]` | `[:flat, :graph_html, :call_tree, :call_stack]` | -| **Rubinius** | Benchmarking | `[:wall_time, :memory, :objects, :gc_runs, :gc_time]` | N/A | -| | Profiling | `[:wall_time]` | `[:flat, :graph]` | -| **JRuby** | Benchmarking | `[:wall_time, :user_time, :memory, :gc_runs, :gc_time]` | N/A | -| | Profiling | `[:wall_time]` | `[:flat, :graph]` | - -As you've probably noticed by now, metrics and formats are specified using a -symbol array with each name [underscored.](http://api.rubyonrails.org/classes/String.html#method-i-underscore) - -### Performance Test Environment - -Performance tests are run in the `test` environment. But running performance -tests will set the following configuration parameters: - -```bash -ActionController::Base.perform_caching = true -ActiveSupport::Dependencies.mechanism = :require -Rails.logger.level = ActiveSupport::Logger::INFO -``` - -As `ActionController::Base.perform_caching` is set to `true`, performance tests -will behave much as they do in the `production` environment. - -### Installing GC-Patched MRI - -To get the best from Rails' performance tests under MRI, you'll need to build -a special Ruby binary with some super powers. - -The recommended patches for each MRI version are: - -| Version | Patch | -| --------------- | --------- | -| 1.8.6 | ruby186gc | -| 1.8.7 | ruby187gc | -| 1.9.2 and above | gcdata | - -All of these can be found on [RVM's _patches_ directory](https://github.com/wayneeseguin/rvm/tree/master/patches/ruby) -under each specific interpreter version. - -Concerning the installation itself, you can either do this easily by using -[RVM](http://rvm.beginrescueend.com) or you can build everything from source, -which is a little bit harder. - -#### Install Using RVM - -The process of installing a patched Ruby interpreter is very easy if you let RVM -do the hard work. All of the following RVM commands will provide you with a -patched Ruby interpreter: - -```bash -$ rvm install 1.9.2-p180 --patch gcdata -$ rvm install 1.8.7 --patch ruby187gc -$ rvm install 1.9.2-p180 --patch ~/Downloads/downloaded_gcdata_patch.patch -``` - -You can even keep your regular interpreter by assigning a name to the patched -one: - -```bash -$ rvm install 1.9.2-p180 --patch gcdata --name gcdata -$ rvm use 1.9.2-p180 # your regular ruby -$ rvm use 1.9.2-p180-gcdata # your patched ruby -``` - -And it's done! You have installed a patched Ruby interpreter. - -#### Install From Source - -This process is a bit more complicated, but straightforward nonetheless. If -you've never compiled a Ruby binary before, follow these steps to build a -Ruby binary inside your home directory. - -##### Download and Extract - -```bash -$ mkdir rubygc -$ wget <the version you want from ftp://ftp.ruby-lang.org/pub/ruby> -$ tar -xzvf <ruby-version.tar.gz> -$ cd <ruby-version> -``` - -##### Apply the Patch - -```bash -$ curl http://github.com/wayneeseguin/rvm/raw/master/patches/ruby/1.9.2/p180/gcdata.patch | patch -p0 # if you're on 1.9.2! -$ curl http://github.com/wayneeseguin/rvm/raw/master/patches/ruby/1.8.7/ruby187gc.patch | patch -p0 # if you're on 1.8.7! -``` - -##### Configure and Install - -The following will install Ruby in your home directory's `/rubygc` directory. -Make sure to replace `<homedir>` with a full patch to your actual home -directory. - -```bash -$ ./configure --prefix=/<homedir>/rubygc -$ make && make install -``` - -##### Prepare Aliases - -For convenience, add the following lines in your `~/.profile`: - -```bash -alias gcruby='~/rubygc/bin/ruby' -alias gcrake='~/rubygc/bin/rake' -alias gcgem='~/rubygc/bin/gem' -alias gcirb='~/rubygc/bin/irb' -alias gcrails='~/rubygc/bin/rails' -``` - -Don't forget to use your aliases from now on. - -### Using Ruby-Prof on MRI and REE - -Add Ruby-Prof to your applications' Gemfile if you want to benchmark/profile -under MRI or REE: - -```ruby -gem 'ruby-prof' -``` - -Now run `bundle install` and you're ready to go. - -Command Line Tools ------------------- - -Writing performance test cases could be an overkill when you are looking for one -time tests. Rails ships with two command line tools that enable quick and dirty -performance testing: - -### `benchmarker` - -Usage: - -```bash -Usage: rails benchmarker 'Ruby.code' 'Ruby.more_code' ... [OPTS] - -r, --runs N Number of runs. - Default: 4 - -o, --output PATH Directory to use when writing the results. - Default: tmp/performance - -m, --metrics a,b,c Metrics to use. - Default: wall_time,memory,objects,gc_runs,gc_time -``` - -Example: - -```bash -$ rails benchmarker 'Item.all' 'CouchItem.all' --runs 3 --metrics wall_time,memory -``` - -### `profiler` - -Usage: - -```bash -Usage: rails profiler 'Ruby.code' 'Ruby.more_code' ... [OPTS] - -r, --runs N Number of runs. - Default: 1 - -o, --output PATH Directory to use when writing the results. - Default: tmp/performance - -m, --metrics a,b,c Metrics to use. - Default: process_time,memory,objects - -f, --formats x,y,z Formats to output to. - Default: flat,graph_html,call_tree -``` - -Example: - -```bash -$ rails profiler 'Item.all' 'CouchItem.all' --runs 2 --metrics process_time --formats flat -``` - -NOTE: Metrics and formats vary from interpreter to interpreter. Pass `--help` to -each tool to see the defaults for your interpreter. - -Helper Methods --------------- - -Rails provides various helper methods inside Active Record, Action Controller -and Action View to measure the time taken by a given piece of code. The method -is called `benchmark()` in all the three components. - -### Model - -```ruby -Project.benchmark("Creating project") do - project = Project.create("name" => "stuff") - project.create_manager("name" => "David") - project.milestones << Milestone.all -end -``` - -This benchmarks the code enclosed in the `Project.benchmark("Creating project") do...end` -block and prints the result to the log file: - -```ruby -Creating project (185.3ms) -``` - -Please refer to the [API docs](http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html#method-i-benchmark) -for additional options to `benchmark()`. - -### Controller - -Similarly, you could use this helper method inside [controllers.](http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html) - -```ruby -def process_projects - benchmark("Processing projects") do - Project.process(params[:project_ids]) - Project.update_cached_projects - end -end -``` - -NOTE: `benchmark` is a class method inside controllers. - -### View - -And in [views](http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html:) - -```erb -<% benchmark("Showing projects partial") do %> - <%= render @projects %> -<% end %> -``` - -Request Logging ---------------- - -Rails log files contain very useful information about the time taken to serve -each request. Here's a typical log file entry: - -```bash -Processing ItemsController#index (for 127.0.0.1 at 2009-01-08 03:06:39) [GET] -Rendering template within layouts/items -Rendering items/index -Completed in 5ms (View: 2, DB: 0) | 200 OK [http://0.0.0.0/items] -``` - -For this section, we're only interested in the last line: - -```bash -Completed in 5ms (View: 2, DB: 0) | 200 OK [http://0.0.0.0/items] -``` - -This data is fairly straightforward to understand. Rails uses millisecond(ms) as -the metric to measure the time taken. The complete request spent 5 ms inside -Rails, out of which 2 ms were spent rendering views and none was spent -communication with the database. It's safe to assume that the remaining 3 ms -were spent inside the controller. - -Michael Koziarski has an [interesting blog post](http://www.therailsway.com/2009/1/6/requests-per-second) -explaining the importance of using milliseconds as the metric. - -Useful Links ------------- - -### Rails Plugins and Gems - -* [Rails Analyzer](http://rails-analyzer.rubyforge.org) -* [Rails Footnotes](https://github.com/josevalim/rails-footnotes/tree/master) -* [Query Reviewer](https://github.com/nesquena/query_reviewer) -* [MiniProfiler](http://www.miniprofiler.com) - -### Generic Tools - -* [httperf](http://www.hpl.hp.com/research/linux/httperf/) -* [ab](http://httpd.apache.org/docs/2.2/programs/ab.html) -* [JMeter](http://jakarta.apache.org/jmeter/) -* [kcachegrind](http://kcachegrind.sourceforge.net/html/Home.html) - -### Tutorials and Documentation - -* [ruby-prof API Documentation](http://ruby-prof.rubyforge.org) -* [Request Profiling Railscast](http://railscasts.com/episodes/98-request-profiling) - Outdated, but useful for understanding call graphs. - -Commercial Products -------------------- - -Rails has been lucky to have a few companies dedicated to Rails-specific -performance tools. A couple of those are: - -* [New Relic](http://www.newrelic.com) -* [Scout](http://scoutapp.com) diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index 9e694acb98..77138d8871 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -34,7 +34,6 @@ Rails templates API is very self explanatory and easy to understand. Here's an e ```ruby # template.rb -run "rm public/index.html" generate(:scaffold, "person name:string") route "root to: 'people#index'" rake("db:migrate") @@ -158,10 +157,10 @@ generate(:scaffold, "person", "name:string", "address:text", "age:number") ### run(command) -Executes an arbitrary command. Just like the backticks. Let's say you want to remove the `public/index.html` file: +Executes an arbitrary command. Just like the backticks. Let's say you want to remove the `README.rdoc` file: ```ruby -run "rm public/index.html" +run "rm README.rdoc" ``` ### rake(command, options = {}) @@ -180,7 +179,7 @@ rake "db:migrate", env: 'production' ### route(routing_code) -Adds a routing entry to the `config/routes.rb` file. In above steps, we generated a person scaffold and also removed `public/index.html`. Now to make `PeopleController#index` as the default page for the application: +Adds a routing entry to the `config/routes.rb` file. In above steps, we generated a person scaffold and also removed `README.rdoc`. Now to make `PeopleController#index` as the default page for the application: ```ruby route "root to: 'person#index'" diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 1fac6d9883..a6119eb433 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -37,11 +37,11 @@ Rails on Rack Here's how `rails server` creates an instance of `Rack::Server` ```ruby -Rails::Server.new.tap { |server| +Rails::Server.new.tap do |server| require APP_PATH Dir.chdir(Rails.application.root) server.start -} +end ``` The `Rails::Server` inherits from `Rack::Server` and calls the `Rack::Server#start` method this way: @@ -229,7 +229,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol **`Rack::Lock`** -* Sets `env["rack.multithread"]` flag to `true` and wraps the application within a Mutex. +* Sets `env["rack.multithread"]` flag to `false` and wraps the application within a Mutex. **`ActiveSupport::Cache::Strategy::LocalCache::Middleware`** diff --git a/guides/source/security.md b/guides/source/security.md index 0b0cfe69c4..3706a61431 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -210,7 +210,7 @@ The HTTP protocol basically provides two main types of requests - GET and POST ( * The interaction _changes the state_ of the resource in a way that the user would perceive (e.g., a subscription to a service), or * The user is _held accountable for the results_ of the interaction. -If your web application is RESTful, you might be used to additional HTTP verbs, such as PUT or DELETE. Most of today's web browsers, however do not support them - only GET and POST. Rails uses a hidden `_method` field to handle this barrier. +If your web application is RESTful, you might be used to additional HTTP verbs, such as PATCH, PUT or DELETE. Most of today's web browsers, however do not support them - only GET and POST. Rails uses a hidden `_method` field to handle this barrier. _POST requests can be sent automatically, too_. Here is an example for a link which displays www.harmless.com as destination in the browser's status bar. In fact it dynamically creates a new form that sends a POST request. diff --git a/guides/source/testing.md b/guides/source/testing.md index f178ca87e9..09d6d2d5ee 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -39,10 +39,10 @@ Rails creates a `test` folder for you as soon as you create a Rails project usin ```bash $ ls -F test -fixtures/ functional/ integration/ performance/ test_helper.rb unit/ +fixtures/ functional/ integration/ test_helper.rb unit/ ``` -The `unit` directory is meant to hold tests for your models, the `functional` directory is meant to hold tests for your controllers, the `integration` directory is meant to hold tests that involve any number of controllers interacting, and the `performance` directory is meant for performance tests. +The `unit` directory is meant to hold tests for your models, the `functional` directory is meant to hold tests for your controllers and the `integration` directory is meant to hold tests that involve any number of controllers interacting. Fixtures are a way of organizing test data; they reside in the `fixtures` folder. @@ -760,14 +760,12 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai | Tasks | Description | | ------------------------------- | ----------- | | `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as the _test_ target is the default.| -| `rake test:benchmark` | Benchmark the performance tests| | `rake test:controllers` | Runs all the controller tests from `test/controllers`| | `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`| | `rake test:helpers` | Runs all the helper tests from `test/helpers`| | `rake test:integration` | Runs all the integration tests from `test/integration`| | `rake test:mailers` | Runs all the mailer tests from `test/mailers`| | `rake test:models` | Runs all the model tests from `test/models`| -| `rake test:profile` | Profile the performance tests| | `rake test:recent` | Tests recent changes| | `rake test:uncommitted` | Runs all the tests which are uncommitted. Supports Subversion and Git| | `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`| @@ -830,7 +828,7 @@ Above, the `setup` method is called before each test and so `@post` is available Let's see the earlier example by specifying `setup` callback by specifying a method name as a symbol: ```ruby -require '../test_helper' +require 'test_helper' class PostsControllerTest < ActionController::TestCase diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 0f5acfba3c..a7ca531123 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -66,37 +66,38 @@ Here's the simplest way to write JavaScript. You may see it referred to as 'inline JavaScript': ```html -<a href="#" onclick="alert('Hello, world.')">Here</a> +<a href="#" onclick="this.style.backgroundColor='#990000'">Paint it red</a> ``` - -When clicked, the alert will trigger. Here's the problem: what happens when -we have lots of JavaScript we want to execute on a click? +When clicked, the link background will become red. Here's the problem: what +happens when we have lots of JavaScript we want to execute on a click? ```html -<a href="#" onclick="function fib(n){return n<2?n:fib(n-1)+fib(n-2);};alert('fib of 15 is: ' + fib(15) + '.');">Calculate</a> +<a href="#" onclick="this.style.backgroundColor='#009900';this.style.color='#FFFFFF';">Paint it green</a> ``` Awkward, right? We could pull the function definition out of the click handler, and turn it into CoffeeScript: ```coffeescript -fib = (n) -> - (if n < 2 then n else fib(n - 1) + fib(n - 2)) +paintIt = (element, backgroundColor, textColor) -> + element.style.backgroundColor = backgroundColor + if textColor? + element.style.color = textColor ``` And then on our page: ```html -<a href="#" onclick="alert('fib of 15 is: ' + fib(15) + '.');">Calculate</a> +<a href="#" onclick="paintIt(this, '#990000')">Paint it red</a> ``` That's a little bit better, but what about multiple links that have the same effect? ```html -<a href="#" onclick="alert('fib of 16 is: ' + fib(16) + '.');">Calculate</a> -<a href="#" onclick="alert('fib of 17 is: ' + fib(17) + '.');">Calculate</a> -<a href="#" onclick="alert('fib of 18 is: ' + fib(18) + '.');">Calculate</a> +<a href="#" onclick="paintIt(this, '#990000')">Paint it red</a> +<a href="#" onclick="paintIt(this, '#009900', '#FFFFFF')">Paint it green</a> +<a href="#" onclick="paintIt(this, '#000099', '#FFFFFF')">Paint it blue</a> ``` Not very DRY, eh? We can fix this by using events instead. We'll add a `data-*` @@ -104,19 +105,21 @@ attribute to our link, and then bind a handler to the click event of every link that has that attribute: ```coffeescript -fib = (n) -> - (if n < 2 then n else fib(n - 1) + fib(n - 2)) - -$(document).ready -> - $("a[data-fib]").click (e) -> - count = $(this).data("fib") - alert "fib of #{count} is: #{fib(count)}." - -... later ... - -<a href="#" data-fib="15">Calculate</a> -<a href="#" data-fib="16">Calculate</a> -<a href="#" data-fib="17">Calculate</a> +paintIt = (element, backgroundColor, textColor) -> + element.style.backgroundColor = backgroundColor + if textColor? + element.style.color = textColor + +$ -> + $("a[data-color]").click -> + backgroundColor = $(this).data("background-color") + textColor = $(this).data("text-color") + paintIt(this, backgroundColor, textColor) +``` +```html +<a href="#" data-background-color="#990000">Paint it red</a> +<a href="#" data-background-color="#009900" data-text-color="#FFFFFF">Paint it green</a> +<a href="#" data-background-color="#000099" data-text-color="#FFFFFF">Paint it blue</a> ``` We call this 'unobtrusive' JavaScript because we're no longer mixing our @@ -204,7 +207,7 @@ is a helper that assists with generating links. It has a `:remote` option you can use like this: ```erb -<%= link_to "first post", @post, remote: true %> +<%= link_to "a post", @post, remote: true %> ``` which generates @@ -214,20 +217,19 @@ which generates ``` You can bind to the same Ajax events as `form_for`. Here's an example. Let's -assume that we have a resource `/fib/:n` that calculates the `n`th Fibonacci -number. We would generate some HTML like this: +assume that we have a list of posts that can be deleted with just one +click. We would generate some HTML like this: ```erb -<%= link_to "Calculate", "/fib/15", remote: true, data: { fib: 15 } %> +<%= link_to "Delete post", @post, remote: true, method: :delete %> ``` and write some CoffeeScript like this: ```coffeescript -$(document).ready -> - $("a[data-fib]").on "ajax:success", (e, data, status, xhr) -> - count = $(this).data("fib") - alert "fib of #{count} is: #{data}." +$ -> + $("a[data-remote]").on "ajax:success", (e, data, status, xhr) -> + alert "The post was deleted." ``` ### button_to diff --git a/install.rb b/install.rb index b87b008c2e..3967624a3f 100644 --- a/install.rb +++ b/install.rb @@ -1,11 +1,16 @@ version = ARGV.pop +if version.nil? + puts "Usage: ruby install.rb version" + exit(64) +end + %w( activesupport activemodel activerecord actionpack actionmailer railties ).each do |framework| puts "Installing #{framework}..." - `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-ri --no-rdoc && rm #{framework}-#{version}.gem` + `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --local --no-ri --no-rdoc && rm #{framework}-#{version}.gem` end puts "Installing Rails..." `gem build rails.gemspec` -`gem install rails-#{version}.gem --no-ri --no-rdoc ` +`gem install rails-#{version}.gem --local --no-ri --no-rdoc ` `rm rails-#{version}.gem` diff --git a/rails.gemspec b/rails.gemspec index ba94354bf1..34957c9455 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |s| s.bindir = 'bin' s.executables = [] - s.files = Dir['guides/**/*'] + s.files = ['README.rdoc'] + Dir['guides/**/*'] s.add_dependency 'activesupport', version s.add_dependency 'actionpack', version diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 953133d8ef..5dbca2e9b4 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,8 +1,86 @@ ## Rails 4.0.0 (unreleased) ## +* Specify which logs to clear when using the `rake log:clear` task. + (e.g. rake log:clear LOGS=test,staging) + + *Matt Bridges* + +* Allow a `:dirs` key in the `SourceAnnotationExtractor.enumerate` options + to explicitly set the directories to be traversed so it's easier to define + custom rake tasks. + + *Brian D. Burns* + +* Deprecate `Rails::Generators::ActiveModel#update_attributes` in favor of `#update`. + + ORMs that implement `Generators::ActiveModel#update_attributes` should change + to `#update`. Scaffold controller generators should change calls like: + + @orm_instance.update_attributes(...) + + to: + + @orm_instance.update(...) + + This goes along with the addition of `ActiveRecord::Base#update`. + + *Carlos Antonio da Silva* + +* Include `jbuilder` by default and rely on its scaffold generator to show json API. + Check https://github.com/rails/jbuilder for more info and examples. + + *DHH* + +* Scaffold now generates HTML-only controller by default. + + *DHH + Pavel Pravosud* + +* The generated `README.rdoc` for new applications invites the user to + document the necessary steps to get the application up and running. + + *Xavier Noria* + +* Generated applications no longer get `doc/README_FOR_APP`. In consequence, + the `doc` directory is created on demand by documentation tasks rather than + generated by default. + + *Xavier Noria* + +* App executables now live in the `bin/` directory: `bin/bundle`, + `bin/rails`, `bin/rake`. Run `rake rails:update:bin` to add these + executables to your own app. `script/rails` is gone from new apps. + + Running executables within your app ensures they use your app's Ruby + version and its bundled gems, and it ensures your production deployment + tools only need to execute a single script. No more having to carefully + `cd` to the app dir and run `bundle exec ...`. + + Rather than treating `bin/` as a junk drawer for generated "binstubs", + bundler 1.3 adds support for generating stubs for just the executables + you actually use: `bundle binstubs unicorn` generates `bin/unicorn`. + Add that executable to git and version it just like any other app code. + + *Jeremy Kemper* + +* `config.assets.enabled` is now true by default. If you're upgrading from a Rails 3.x app + that does not use the asset pipeline, you'll be required to add `config.assets.enabled = false` + to your application.rb. If you don't want the asset pipeline on a new app use `--skip-sprockets` + + *DHH* + +* Environment name can be a start substring of the default environment names + (production, development, test). For example: tes, pro, prod, dev, devel. + Fix #8628. + + *Mykola Kyryk* + +* Add `-B` alias for `--skip-bundle` option in the rails new generators. + + *Jiri Pospisil* + * Quote column names in generates fixture files. This prevents conflicts with reserved YAML keywords such as 'yes' and 'no' - Fix #8612 + Fix #8612. *Yves Senn* @@ -151,8 +229,6 @@ * Add convenience `hide!` method to Rails generators to hide current generator namespace from showing when running `rails generate`. *Carlos Antonio da Silva* -* Scaffold now uses `content_tag_for` in index.html.erb *José Valim* - * Rails::Plugin has gone. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. *Santiago Pastorino* * Set config.action_mailer.async = true to turn on asynchronous diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb new file mode 100644 index 0000000000..8937e10db3 --- /dev/null +++ b/railties/lib/rails/app_rails_loader.rb @@ -0,0 +1,29 @@ +require 'pathname' + +module Rails + module AppRailsLoader + RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"] + EXECUTABLE = 'bin/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? + 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. + exec_app_rails unless cwd == Dir.pwd + end + rescue SystemCallError + # could not chdir, no problem just return + end + + def self.in_rails_application_or_engine? + File.exists?(EXECUTABLE) && File.read(EXECUTABLE) =~ /(APP|ENGINE)_PATH/ + 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) + end + end +end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 086fd5ed95..2c7ddd86e7 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -48,12 +48,12 @@ module Rails @secret_key_base = nil @assets = ActiveSupport::OrderedOptions.new - @assets.enabled = false + @assets.enabled = true @assets.paths = [] @assets.precompile = [ Proc.new { |path, fn| fn =~ /app\/assets/ && !%w(.js .css).include?(File.extname(path)) }, /(?:\/|\\|\A)application\.(css|js)$/ ] @assets.prefix = "/assets" - @assets.version = '' + @assets.version = '1.0' @assets.debug = false @assets.compile = true @assets.digest = false diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb index 443d6f47ad..b717b026de 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -1,9 +1,12 @@ require 'rbconfig' -require 'rails/script_rails_loader' +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. -Rails::ScriptRailsLoader.exec_script_rails! +# +# 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' Signal.trap("INT") { puts; exit(1) } diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 3ab2a3809e..aacde52cfc 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -24,8 +24,6 @@ The most common rails commands are: In addition to those, there are: application Generate the Rails application code destroy Undo code generated with "generate" (short-cut alias: "d") - benchmarker See how fast a piece of code runs - profiler Get profile information from a piece of code plugin new Generates skeleton for developing a Rails plugin runner Run a piece of code in the application environment (short-cut alias: "r") @@ -51,11 +49,6 @@ when 'generate', 'destroy', 'plugin' require "rails/commands/#{command}" end -when 'benchmarker', 'profiler' - require APP_PATH - Rails.application.require_environment! - require "rails/commands/#{command}" - when 'console' require 'rails/commands/console' options = Rails::Console.parse_arguments(ARGV) @@ -72,18 +65,18 @@ when 'console' when 'server' # Change to the application's path if there is no config.ru file in current dir. - # This allows us to run script/rails server from other directories, but still get + # This allows us to run `rails server` from other directories, but still get # the main config.ru and properly set the tmp directory. Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru")) require 'rails/commands/server' - Rails::Server.new.tap { |server| + Rails::Server.new.tap do |server| # We need to require application after the server sets environment, # otherwise the --environment option given to the server won't propagate. require APP_PATH Dir.chdir(Rails.application.root) server.start - } + end when 'dbconsole' require 'rails/commands/dbconsole' diff --git a/railties/lib/rails/commands/benchmarker.rb b/railties/lib/rails/commands/benchmarker.rb deleted file mode 100644 index b745b45e17..0000000000 --- a/railties/lib/rails/commands/benchmarker.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'optparse' -require 'rails/test_help' -require 'rails/performance_test_help' - -ARGV.push('--benchmark') # HAX -require 'active_support/testing/performance' -ARGV.pop - -def options - options = {} - defaults = ActiveSupport::Testing::Performance::DEFAULTS - - OptionParser.new do |opt| - opt.banner = "Usage: rails benchmarker 'Ruby.code' 'Ruby.more_code' ... [OPTS]" - opt.on('-r', '--runs N', Numeric, 'Number of runs.', "Default: #{defaults[:runs]}") { |r| options[:runs] = r } - opt.on('-o', '--output PATH', String, 'Directory to use when writing the results.', "Default: #{defaults[:output]}") { |o| options[:output] = o } - opt.on('-m', '--metrics a,b,c', Array, 'Metrics to use.', "Default: #{defaults[:metrics].join(",")}") { |m| options[:metrics] = m.map(&:to_sym) } - opt.parse!(ARGV) - end - - options -end - -class BenchmarkerTest < ActionDispatch::PerformanceTest #:nodoc: - self.profile_options = options - - ARGV.each do |expression| - eval <<-RUBY - def test_#{expression.parameterize('_')} - #{expression} - end - RUBY - end -end diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index aef7600fbd..86ab1aabbf 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -24,11 +24,21 @@ module Rails if arguments.first && arguments.first[0] != '-' env = arguments.first - options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env + if available_environments.include? env + options[:environment] = env + else + options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env + end end options end + + private + + def available_environments + Dir['config/environments/*.rb'].map { |fname| File.basename(fname, '.*') } + end end attr_reader :options, :app, :console @@ -79,13 +89,11 @@ module Rails end def require_debugger - begin - require 'debugger' - puts "=> Debugger enabled" - rescue Exception - puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again." - exit - end + require 'debugger' + puts "=> Debugger enabled" + rescue LoadError + puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again." + exit end end end diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index 4a5674236d..5914c9e4ae 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -136,12 +136,20 @@ module Rails if arguments.first && arguments.first[0] != '-' env = arguments.first - options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env + if available_environments.include? env + options[:environment] = env + else + options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env + end end options end + def available_environments + Dir['config/environments/*.rb'].map { |fname| File.basename(fname, '.*') } + end + def find_cmd_and_exec(commands, *args) commands = Array(commands) diff --git a/railties/lib/rails/commands/profiler.rb b/railties/lib/rails/commands/profiler.rb deleted file mode 100644 index 315bcccf61..0000000000 --- a/railties/lib/rails/commands/profiler.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'optparse' -require 'rails/test_help' -require 'rails/performance_test_help' -require 'active_support/testing/performance' - -def options - options = {} - defaults = ActiveSupport::Testing::Performance::DEFAULTS - - OptionParser.new do |opt| - opt.banner = "Usage: rails profiler 'Ruby.code' 'Ruby.more_code' ... [OPTS]" - opt.on('-r', '--runs N', Numeric, 'Number of runs.', "Default: #{defaults[:runs]}") { |r| options[:runs] = r } - opt.on('-o', '--output PATH', String, 'Directory to use when writing the results.', "Default: #{defaults[:output]}") { |o| options[:output] = o } - opt.on('-m', '--metrics a,b,c', Array, 'Metrics to use.', "Default: #{defaults[:metrics].join(",")}") { |m| options[:metrics] = m.map(&:to_sym) } - opt.on('-f', '--formats x,y,z', Array, 'Formats to output to.', "Default: #{defaults[:formats].join(",")}") { |m| options[:formats] = m.map(&:to_sym) } - opt.parse!(ARGV) - end - - options -end - -class ProfilerTest < ActionDispatch::PerformanceTest #:nodoc: - self.profile_options = options - - ARGV.each do |expression| - eval <<-RUBY - def test_#{expression.parameterize('_')} - #{expression} - end - RUBY - end -end diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index 6adbdc6e0b..c4622d6a2d 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -24,7 +24,7 @@ ARGV.clone.options do |opts| if RbConfig::CONFIG['host_os'] !~ /mswin|mingw/ opts.separator "" - opts.separator "You can also use runner as a shebang line for your scripts like this:" + opts.separator "You can also use runner as a shebang line for your executables:" opts.separator "-------------------------------------------------------------" opts.separator "#!/usr/bin/env #{File.expand_path($0)} runner" opts.separator "" diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 725295004f..0ae2f16aba 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -175,7 +175,7 @@ module Rails # There are some places where an Engine's name is used: # # * routes: when you mount an Engine with <tt>mount(MyEngine::Engine => '/my_engine')</tt>, - # it's used as default :as option + # it's used as default <tt>:as</tt> option # * rake task for installing migrations <tt>my_engine:install:migrations</tt> # # Engine name is set by default based on class name. For <tt>MyEngine::Engine</tt> it will be @@ -407,10 +407,8 @@ module Rails end end - self.isolated = false - delegate :middleware, :root, :paths, to: :config - delegate :engine_name, :isolated?, to: "self.class" + delegate :engine_name, :isolated?, to: :class def initialize @_all_autoload_paths = nil diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index d9a91b74d1..4b767ea0c6 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -50,7 +50,6 @@ module Rails javascripts: true, javascript_engine: :js, orm: false, - performance_tool: nil, resource_controller: :controller, resource_route: true, scaffold_controller: :scaffold_controller, @@ -179,7 +178,6 @@ module Rails "#{test}:model", "#{test}:scaffold", "#{test}:view", - "#{test}:performance", "#{template}:controller", "#{template}:scaffold", "#{template}:mailer", diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index b96ee9295e..cb3aca5811 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -4,6 +4,10 @@ require 'rbconfig' module Rails module Generators module Actions + def initialize(*) # :nodoc: + super + @in_group = nil + end # Adds an entry into Gemfile for the supplied gem. # @@ -186,7 +190,7 @@ module Rails log :generate, what argument = args.map {|arg| arg.to_s }.flatten.join(" ") - in_root { run_ruby_script("script/rails generate #{what} #{argument}", verbose: false) } + in_root { run_ruby_script("bin/rails generate #{what} #{argument}", verbose: false) } end # Runs the supplied rake task diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb index 0e51b9c568..e5373704d7 100644 --- a/railties/lib/rails/generators/active_model.rb +++ b/railties/lib/rails/generators/active_model.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation' + module Rails module Generators # ActiveModel is a class to be implemented by each ORM to allow Rails to @@ -59,8 +61,14 @@ module Rails end # PATCH/PUT update - def update_attributes(params=nil) - "#{name}.update_attributes(#{params})" + def update(params=nil) + "#{name}.update(#{params})" + end + + def update_attributes(*args) # :nodoc: + ActiveSupport::Deprecation.warn("Calling '@orm_instance.update_attributes' " \ + "is deprecated, please use '@orm_instance.update' instead.") + update(*args) end # POST create diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index edd1e8cbdf..ca3652c703 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -28,7 +28,7 @@ module Rails class_option :skip_gemfile, type: :boolean, default: false, desc: "Don't create a Gemfile" - class_option :skip_bundle, type: :boolean, default: false, + class_option :skip_bundle, type: :boolean, aliases: '-B', default: false, desc: "Don't run bundle install" class_option :skip_git, type: :boolean, aliases: '-G', default: false, diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb index f5182bcc50..90d8db1df5 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb @@ -3,9 +3,9 @@ <table> <thead> <tr> - <% attributes.each do |attribute| -%> +<% attributes.each do |attribute| -%> <th><%= attribute.human_name %></th> - <% end -%> +<% end -%> <th></th> <th></th> <th></th> @@ -13,13 +13,15 @@ </thead> <tbody> - <%%= content_tag_for(:tr, @<%= plural_table_name %>) do |<%= singular_table_name %>| %> - <% attributes.each do |attribute| -%> + <%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %> + <tr> +<% attributes.each do |attribute| -%> <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td> - <% end -%> +<% end -%> <td><%%= link_to 'Show', <%= singular_table_name %> %></td> <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td> <td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td> + </tr> <%% end %> </tbody> </table> diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 372790df59..e22be40381 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -38,7 +38,7 @@ module Rails end def readme - copy_file "README", "README.rdoc" + copy_file "README.rdoc", "README.rdoc" end def gemfile @@ -63,6 +63,13 @@ module Rails keep_file 'app/models/concerns' end + def bin + directory "bin" do |content| + "#{shebang}\n" + content + end + chmod "bin", 0755, verbose: false + end + def config empty_directory "config" @@ -85,10 +92,6 @@ module Rails directory "db" end - def doc - directory "doc" - end - def lib empty_directory 'lib' empty_directory_with_keep_file 'lib/tasks' @@ -103,13 +106,6 @@ module Rails directory "public", "public", recursive: false end - def script - directory "script" do |content| - "#{shebang}\n" + content - end - chmod "script", 0755, verbose: false - end - def test empty_directory_with_keep_file 'test/fixtures' empty_directory_with_keep_file 'test/controllers' @@ -118,7 +114,6 @@ module Rails empty_directory_with_keep_file 'test/helpers' empty_directory_with_keep_file 'test/integration' - template 'test/performance/browsing_test.rb' template 'test/test_helper.rb' end @@ -145,7 +140,7 @@ module Rails # We need to store the RAILS_DEV_PATH in a constant, otherwise the path # can change in Ruby 1.8.7 when we FileUtils.cd. RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__)) - RESERVED_NAMES = %w[application destroy benchmarker profiler plugin runner test] + RESERVED_NAMES = %w[application destroy plugin runner test] class AppGenerator < AppBase # :nodoc: add_shared_options_for "application" @@ -178,6 +173,10 @@ module Rails build(:app) end + def create_bin_files + build(:bin) + end + def create_config_files build(:config) end @@ -195,10 +194,6 @@ module Rails build(:db) end - def create_doc_files - build(:doc) - end - def create_lib_files build(:lib) end @@ -211,10 +206,6 @@ module Rails build(:public_directory) end - def create_script_files - build(:script) - end - def create_test_files build(:test) unless options[:skip_test_unit] end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index c4846b2c11..b5db3d2187 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -9,12 +9,12 @@ source 'https://rubygems.org' <%= assets_gemfile_entry %> <%= javascript_gemfile_entry -%> +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 1.0.1' + # To use ActiveModel has_secure_password # gem 'bcrypt-ruby', '~> 3.0.0' -# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -# gem 'jbuilder' - # Use unicorn as the app server # gem 'unicorn' diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README deleted file mode 100644 index b5d7b6436b..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/README +++ /dev/null @@ -1,259 +0,0 @@ -== Welcome to Rails - -Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Control pattern. - -This pattern splits the view (also called the presentation) into "dumb" -templates that are primarily responsible for inserting pre-built data in between -HTML tags. The model contains the "smart" domain objects (such as Account, -Product, Person, Post) that holds all the business logic and knows how to -persist themselves to a database. The controller handles the incoming requests -(such as Save New Account, Update Product, Show Post) by manipulating the model -and directing data to the view. - -In Rails, the model is handled by what's called an object-relational mapping -layer entitled Active Record. This layer allows you to present the data from -database rows as objects and embellish these data objects with business logic -methods. You can read more about Active Record in -link:files/vendor/rails/activerecord/README.html. - -The controller and view are handled by the Action Pack, which handles both -layers by its two parts: Action View and Action Controller. These two layers -are bundled in a single package due to their heavy interdependence. This is -unlike the relationship between the Active Record and Action Pack that is much -more separate. Each of these packages can be used independently outside of -Rails. You can read more about Action Pack in -link:files/vendor/rails/actionpack/README.html. - - -== Getting Started - -1. At the command prompt, create a new Rails application: - <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name) - -2. Change directory to <tt>myapp</tt> and start the web server: - <tt>cd myapp; rails server</tt> (run with --help for options) - -3. Go to http://localhost:3000/ and you'll see: - "Welcome aboard: You're riding Ruby on Rails!" - -4. Follow the guidelines to start developing your application. You can find -the following resources handy: - -* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html -* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ - - -== Debugging Rails - -Sometimes your application goes wrong. Fortunately there are a lot of tools that -will help you debug it and get it back on the rails. - -First area to check is the application log files. Have "tail -f" commands -running on the server.log and development.log. Rails will automatically display -debugging and runtime information to these files. Debugging info will also be -shown in the browser on requests from 127.0.0.1. - -You can also log your own messages directly into the log file from your code -using the Ruby logger class from inside your controllers. Example: - - class WeblogController < ActionController::Base - def destroy - @weblog = Weblog.find(params[:id]) - @weblog.destroy - logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") - end - end - -The result will be a message in your log file along the lines of: - - Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! - -More information on how to use the logger is at http://www.ruby-doc.org/core/ - -Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are -several books available online as well: - -* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) -* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) - -These two books will bring you up to speed on the Ruby language and also on -programming in general. - - -== Debugger - -Debugger support is available through the debugger command when you start your -Mongrel or WEBrick server with --debugger. This means that you can break out of -execution at any point in the code, investigate and change the model, and then, -resume execution! You need to install the 'debugger' gem to run the server in debugging -mode. Add gem 'debugger' to your Gemfile and run <tt>bundle</tt> to install it. Example: - - class WeblogController < ActionController::Base - def index - @posts = Post.all - debugger - end - end - -So the controller will accept the action, run the first line, then present you -with a IRB prompt in the server window. Here you can do things like: - - >> @posts.inspect - => "[#<Post:0x14a6be8 - @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>, - #<Post:0x14a6620 - @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]" - >> @posts.first.title = "hello from a debugger" - => "hello from a debugger" - -...and even better, you can examine how your runtime objects actually work: - - >> f = @posts.first - => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}> - >> f. - Display all 152 possibilities? (y or n) - -Finally, when you're ready to resume execution, you can enter "cont". - - -== Console - -The console is a Ruby shell, which allows you to interact with your -application's domain model. Here you'll have all parts of the application -configured, just like it is when the application is running. You can inspect -domain models, change values, and save to the database. Starting the script -without arguments will launch it in the development environment. - -To start the console, run <tt>rails console</tt> from the application -directory. - -Options: - -* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications - made to the database. -* Passing an environment name as an argument will load the corresponding - environment. Example: <tt>rails console production</tt>. - -To reload your controllers and models after launching the console run -<tt>reload!</tt> - -More information about irb can be found at: -link:http://www.rubycentral.org/pickaxe/irb.html - - -== dbconsole - -You can go to the command line of your database directly through <tt>rails -dbconsole</tt>. You would be connected to the database with the credentials -defined in database.yml. Starting the script without arguments will connect you -to the development database. Passing an argument will connect you to a different -database, like <tt>rails dbconsole production</tt>. Currently works for MySQL, -PostgreSQL and SQLite 3. - -== Description of Contents - -The default directory structure of a generated Ruby on Rails application: - - |-- app - | |-- assets - | |-- images - | |-- javascripts - | `-- stylesheets - | |-- controllers - | |-- helpers - | |-- mailers - | |-- models - | `-- views - | `-- layouts - |-- config - | |-- environments - | |-- initializers - | `-- locales - |-- db - |-- doc - |-- lib - | `-- tasks - |-- log - |-- public - |-- script - |-- test - | |-- fixtures - | |-- functional - | |-- integration - | |-- performance - | `-- unit - |-- tmp - | |-- cache - | |-- pids - | |-- sessions - | `-- sockets - `-- vendor - |-- assets - `-- stylesheets - -app - Holds all the code that's specific to this particular application. - -app/assets - Contains subdirectories for images, stylesheets, and JavaScript files. - -app/controllers - Holds controllers that should be named like weblogs_controller.rb for - automated URL mapping. All controllers should descend from - ApplicationController which itself descends from ActionController::Base. - -app/models - Holds models that should be named like post.rb. Models descend from - ActiveRecord::Base by default. - -app/views - Holds the template files for the view that should be named like - weblogs/index.html.erb for the WeblogsController#index action. All views use - eRuby syntax by default. - -app/views/layouts - Holds the template files for layouts to be used with views. This models the - common header/footer method of wrapping views. In your views, define a layout - using the <tt>layout :default</tt> and create a file named default.html.erb. - Inside default.html.erb, call <% yield %> to render the view using this - layout. - -app/helpers - Holds view helpers that should be named like weblogs_helper.rb. These are - generated for you automatically when using generators for controllers. - Helpers can be used to wrap functionality for your views into methods. - -config - Configuration files for the Rails environment, the routing map, the database, - and other dependencies. - -db - Contains the database schema in schema.rb. db/migrate contains all the - sequence of Migrations for your schema. - -doc - This directory is where your application documentation will be stored when - generated using <tt>rake doc:app</tt> - -lib - Application specific libraries. Basically, any kind of custom code that - doesn't belong under controllers, models, or helpers. This directory is in - the load path. - -public - The directory available for the web server. Also contains the dispatchers and the - default HTML files. This should be set as the DOCUMENT_ROOT of your web - server. - -script - Helper scripts for automation and generation. - -test - Unit and functional tests along with fixtures. When using the rails generate - command, template test files will be generated for you and placed in this - directory. - -vendor - External libraries that the application depends on. If the app has frozen rails, - those gems also go here, under vendor/rails/. This directory is in the load path. diff --git a/railties/lib/rails/generators/rails/app/templates/README.rdoc b/railties/lib/rails/generators/rails/app/templates/README.rdoc new file mode 100644 index 0000000000..dd4e97e22e --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/README.rdoc @@ -0,0 +1,28 @@ +== README + +This README would normally document whatever steps are necessary to get the +application up and running. + +Things you may want to cover: + +* Ruby version + +* System dependencies + +* Configuration + +* Database creation + +* Database initialization + +* How to run the test suite + +* Services (job queues, cache servers, search engines, etc.) + +* Deployment instructions + +* ... + + +Please feel free to use a different markup language if you do not plan to run +<tt>rake doc:app</tt>. diff --git a/railties/lib/rails/generators/rails/app/templates/app/mailers/.empty_directory b/railties/lib/rails/generators/rails/app/templates/app/mailers/.empty_directory deleted file mode 100644 index e69de29bb2..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/app/mailers/.empty_directory +++ /dev/null diff --git a/railties/lib/rails/generators/rails/app/templates/app/models/.empty_directory b/railties/lib/rails/generators/rails/app/templates/app/models/.empty_directory deleted file mode 100644 index e69de29bb2..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/app/models/.empty_directory +++ /dev/null diff --git a/railties/lib/rails/generators/rails/app/templates/bin/bundle b/railties/lib/rails/generators/rails/app/templates/bin/bundle new file mode 100644 index 0000000000..e0df7f4440 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/bin/bundle @@ -0,0 +1,3 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +require 'rubygems' +load Gem.bin_path('bundler', 'bundle') diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rails b/railties/lib/rails/generators/rails/app/templates/bin/rails new file mode 100644 index 0000000000..6a128b95e5 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/bin/rails @@ -0,0 +1,3 @@ +APP_PATH = File.expand_path('../../config/application', __FILE__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rake b/railties/lib/rails/generators/rails/app/templates/bin/rake new file mode 100644 index 0000000000..d14fc8395b --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/bin/rake @@ -0,0 +1,3 @@ +require_relative '../config/boot' +require 'rake' +Rake.application.run 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 5f15c973c6..f5d7d698a3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -22,21 +22,10 @@ module <%= app_const_base %> # Custom directories with classes and modules you want to be autoloadable. # config.autoload_paths += %W(#{config.root}/extras) +<% if options.skip_sprockets? -%> - # Configure sensitive parameters which will be filtered from the log file. - config.filter_parameters += [:password] - - # Use SQL instead of Active Record's schema dumper when creating the database. - # This is necessary if your schema can't be completely dumped by the schema dumper, - # like if you have constraints or database-specific column types. - # config.active_record.schema_format = :sql - -<% unless options.skip_sprockets? -%> - # Enable the asset pipeline. - config.assets.enabled = true - - # Version of your assets, change this if you want to expire all your assets. - config.assets.version = '1.0' + # Disable the asset pipeline. + config.assets.enabled = false <% end -%> end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml index 22c9194fad..eb569b7dab 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml @@ -26,7 +26,7 @@ development: # domain socket that doesn't need configuration. Windows does not have # domain sockets, so uncomment these lines. #host: localhost - + # The TCP port the server listens on. Defaults to 5432. # If your server runs on a different port number, change accordingly. #port: 5432 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 b52b733c56..53620dc8e2 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 @@ -1,5 +1,5 @@ # SQL Server (2005 or higher recommended) -# +# # Install the adapters and driver # gem install tiny_tds # gem install activerecord-sqlserver-adapter @@ -8,8 +8,8 @@ # gem 'tiny_tds' # gem 'activerecord-sqlserver-adapter' # -# You should make sure freetds is configured correctly first. -# freetds.conf contains host/port/protocol_versions settings. +# 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 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 68df1d1ec8..0ab91d9864 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -32,6 +32,9 @@ # Generate digests for assets URLs. config.assets.digest = true + + # Version of your assets, change this if you want to expire all your assets. + config.assets.version = '1.0' <%- end -%> # Specifies the header that your server uses for sending files. diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000..4a994e1e7b --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb index 9262c3379f..ac033bf9dc 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb @@ -9,7 +9,7 @@ # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end -# + # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym 'RESTful' diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb index a8285f88ca..d89dac7c6a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb @@ -1,3 +1,5 @@ +# Be sure to restart your server when you modify this file. + # 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. # Rails.application.config.time_zone = 'Central Time (US & Canada)' diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt index 280f777cc0..4f1d56cd2f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -1,5 +1,5 @@ # Be sure to restart your server when you modify this file. -# + # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. diff --git a/railties/lib/rails/generators/rails/app/templates/doc/README_FOR_APP b/railties/lib/rails/generators/rails/app/templates/doc/README_FOR_APP deleted file mode 100644 index fe41f5cc24..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/doc/README_FOR_APP +++ /dev/null @@ -1,2 +0,0 @@ -Use this README file to introduce your application and point to useful places in the API for learning more. -Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. diff --git a/railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory b/railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory deleted file mode 100644 index e69de29bb2..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory +++ /dev/null diff --git a/railties/lib/rails/generators/rails/app/templates/script/rails b/railties/lib/rails/generators/rails/app/templates/script/rails deleted file mode 100644 index 11bc1edde9..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/script/rails +++ /dev/null @@ -1,5 +0,0 @@ -# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. - -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands' diff --git a/railties/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory deleted file mode 100644 index e69de29bb2..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory +++ /dev/null diff --git a/railties/lib/rails/generators/rails/app/templates/test/functional/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/functional/.empty_directory deleted file mode 100644 index e69de29bb2..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/test/functional/.empty_directory +++ /dev/null diff --git a/railties/lib/rails/generators/rails/app/templates/test/integration/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/integration/.empty_directory deleted file mode 100644 index e69de29bb2..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/test/integration/.empty_directory +++ /dev/null diff --git a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb deleted file mode 100644 index d09ce5ad34..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'test_helper' -require 'rails/performance_test_help' - -class BrowsingTest < ActionDispatch::PerformanceTest - # Refer to the documentation for all available options - # self.profile_options = { runs: 5, metrics: [:wall_time, :memory], - # output: 'tmp/performance', formats: [:flat] } - - test "homepage" do - get '/' - end -end diff --git a/railties/lib/rails/generators/rails/app/templates/test/unit/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/unit/.empty_directory deleted file mode 100644 index e69de29bb2..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/test/unit/.empty_directory +++ /dev/null diff --git a/railties/lib/rails/generators/rails/controller/USAGE b/railties/lib/rails/generators/rails/controller/USAGE index 9def4af65c..64239ad599 100644 --- a/railties/lib/rails/generators/rails/controller/USAGE +++ b/railties/lib/rails/generators/rails/controller/USAGE @@ -6,7 +6,7 @@ Description: path like 'parent_module/controller_name'. This generates a controller class in app/controllers and invokes helper, - template engine and test framework generators. + template engine, assets, and test framework generators. Example: `rails generate controller CreditCards open debit credit close` diff --git a/railties/lib/rails/generators/rails/migration/USAGE b/railties/lib/rails/generators/rails/migration/USAGE index f340ed97f2..baf3d9894f 100644 --- a/railties/lib/rails/generators/rails/migration/USAGE +++ b/railties/lib/rails/generators/rails/migration/USAGE @@ -20,3 +20,16 @@ Example: add_column :posts, :title, :string add_column :posts, :body, :text add_column :posts, :published, :boolean + +Migration names containing JoinTable will generate join tables for use with +has_and_belongs_to_many associations. + +Example: + `rails g migration CreateMediaJoinTable artists musics:uniq` + + will create the migration + + create_join_table :artists, :musics do |t| + # t.index [:artist_id, :music_id] + t.index [:music_id, :artist_id], unique: true + end diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE index e29e19490e..6574200fbf 100644 --- a/railties/lib/rails/generators/rails/model/USAGE +++ b/railties/lib/rails/generators/rails/model/USAGE @@ -21,12 +21,12 @@ Description: Available field types: - Just after the field name you can specify a type like text or boolean. + Just after the field name you can specify a type like text or boolean. It will generate the column with the associated SQL type. For instance: `rails generate model post title:string body:text` - will generate a title column with a varchar type and a body column with a text + will generate a title column with a varchar type and a body column with a text type. You can use the following types: integer @@ -57,16 +57,16 @@ Available field types: limit Set the maximum size of the field giving a number between curly braces default Set a default value for the field - precision Defines the precision for the decimal fields + precision Defines the precision for the decimal fields scale Defines the scale for the decimal fields - uniq Defines the field values as unique + uniq Defines the field values as unique index Will add an index on the field Examples: `rails generate model user pseudo:string{30}` `rails generate model user pseudo:string:uniq` - + Examples: `rails generate model account` @@ -76,7 +76,7 @@ Examples: Model: app/models/account.rb Test: test/models/account_test.rb Fixtures: test/fixtures/accounts.yml - Migration: db/migrate/XXX_add_accounts.rb + Migration: db/migrate/XXX_create_accounts.rb `rails generate model post title:string body:text published:boolean` @@ -90,5 +90,5 @@ Examples: Model: app/models/admin/account.rb Test: test/models/admin/account_test.rb Fixtures: test/fixtures/admin/accounts.yml - Migration: db/migrate/XXX_add_admin_accounts.rb + Migration: db/migrate/XXX_create_admin_accounts.rb diff --git a/railties/lib/rails/generators/rails/performance_test/USAGE b/railties/lib/rails/generators/rails/performance_test/USAGE deleted file mode 100644 index 9dc799559c..0000000000 --- a/railties/lib/rails/generators/rails/performance_test/USAGE +++ /dev/null @@ -1,10 +0,0 @@ -Description: - Stubs out a new performance test. Pass the name of the test, either - CamelCased or under_scored, as an argument. - - This generator invokes the current performance tool, which defaults to - TestUnit. - -Example: - `rails generate performance_test GeneralStories` creates a GeneralStories - performance test in test/performance/general_stories_test.rb diff --git a/railties/lib/rails/generators/rails/performance_test/performance_test_generator.rb b/railties/lib/rails/generators/rails/performance_test/performance_test_generator.rb deleted file mode 100644 index 56cd562f3d..0000000000 --- a/railties/lib/rails/generators/rails/performance_test/performance_test_generator.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Rails - module Generators - class PerformanceTestGenerator < NamedBase # :nodoc: - hook_for :performance_tool, as: :performance - end - end -end diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 48ce3e86a1..af00748037 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -53,13 +53,11 @@ module Rails template "lib/%name%.rb" template "lib/tasks/%name%_tasks.rake" template "lib/%name%/version.rb" - if full? - template "lib/%name%/engine.rb" - end + template "lib/%name%/engine.rb" if engine? end def config - template "config/routes.rb" if full? + template "config/routes.rb" if engine? end def test @@ -70,7 +68,7 @@ module Rails task default: :test EOF - if full? + if engine? template "test/integration/navigation_test.rb" end end @@ -132,13 +130,13 @@ task default: :test end end - def script(force = false) - return unless full? + def bin(force = false) + return unless engine? - directory "script", force: force do |content| + directory "bin", force: force do |content| "#{shebang}\n" + content end - chmod "script", 0755, verbose: false + chmod "bin", 0755, verbose: false end def gemfile_entry @@ -175,7 +173,7 @@ task default: :test "skip adding entry to Gemfile" def initialize(*args) - raise Error, "Options should be given after the plugin name. For details run: rails plugin --help" if args[0].blank? + raise Error, "Options should be given after the plugin name. For details run: rails plugin new --help" if args[0].blank? @dummy_path = nil super @@ -216,8 +214,8 @@ task default: :test build(:images) end - def create_script_files - build(:script) + def create_bin_files + build(:bin) end def create_test_files @@ -266,13 +264,17 @@ task default: :test store_application_definition! build(:test_dummy_config) build(:test_dummy_clean) - # ensure that script/rails has proper dummy_path - build(:script, true) + # ensure that bin/rails has proper dummy_path + build(:bin, true) end end + def engine? + full? || mountable? + end + def full? - options[:full] || options[:mountable] + options[:full] end def mountable? diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec index 568ed653b7..e956d13d8a 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |s| <% end -%> <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>" -<% if full? && !options[:skip_javascript] -%> +<% if engine? && !options[:skip_javascript] -%> # s.add_dependency "<%= "#{options[:javascript]}-rails" %>" <% end -%> <% unless options[:skip_active_record] -%> diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile index d69f943a72..a8b5bfaf3f 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" <% if options[:skip_gemspec] -%> <%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails::VERSION::STRING %>" -<% if full? && !options[:skip_javascript] -%> +<% if engine? && !options[:skip_javascript] -%> # gem "<%= "#{options[:javascript]}-rails" %>" <% end -%> <% else -%> diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile index 65a5bae712..0ba899176c 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile @@ -14,7 +14,7 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -<% if full? && !options[:skip_active_record] && with_dummy_app? -%> +<% if engine? && !options[:skip_active_record] && with_dummy_app? -%> APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__) load 'rails/tasks/engine.rake' <% end %> diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt b/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt index aa87d1b50c..aa87d1b50c 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt +++ b/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb index 2d3bdc510c..40c074cced 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb +++ b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb @@ -1,4 +1,4 @@ -<% if full? -%> +<% if engine? -%> require "<%= name %>/engine" <% end -%> diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb index dd636ed3cf..36589d65e2 100644 --- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb @@ -9,13 +9,8 @@ module Rails class_option :stylesheets, type: :boolean, desc: "Generate Stylesheets" class_option :stylesheet_engine, desc: "Engine for Stylesheets" - class_option :html, type: :boolean, default: true, - desc: "Generate a scaffold with HTML output" - def handle_skip - if !options[:html] || !options[:stylesheets] - @options = @options.merge(stylesheet_engine: false) - end + @options = @options.merge(stylesheet_engine: false) unless options[:stylesheets] end hook_for :scaffold_controller, required: true diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb index 32fa54a362..4f36b612ae 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb @@ -10,17 +10,8 @@ module Rails class_option :orm, banner: "NAME", type: :string, required: true, desc: "ORM to generate the controller for" - class_option :html, type: :boolean, default: true, - desc: "Generate a scaffold with HTML output" - argument :attributes, type: :array, default: [], banner: "field:type field:type" - def handle_skip - unless options[:html] - @options = @options.merge(template_engine: false, helper: false) - end - end - def create_controller_files template "controller.rb", File.join('app/controllers', class_path, "#{controller_file_name}_controller.rb") end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index 4baad85e9e..e813437d75 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -7,95 +7,47 @@ class <%= controller_class_name %>Controller < ApplicationController before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy] # GET <%= route_url %> - # GET <%= route_url %>.json def index @<%= plural_table_name %> = <%= orm_class.all(class_name) %> - - respond_to do |format| - <%- if options[:html] -%> - format.html # index.html.erb - <%- end -%> - format.json { render json: <%= "@#{plural_table_name}" %> } - end end # GET <%= route_url %>/1 - # GET <%= route_url %>/1.json def show - respond_to do |format| - <%- if options[:html] -%> - format.html # show.html.erb - <%- end -%> - format.json { render json: <%= "@#{singular_table_name}" %> } - end end - <%- if options[:html] -%> # GET <%= route_url %>/new - # GET <%= route_url %>/new.json def new @<%= singular_table_name %> = <%= orm_class.build(class_name) %> - - respond_to do |format| - format.html # new.html.erb - format.json { render json: <%= "@#{singular_table_name}" %> } - end end # GET <%= route_url %>/1/edit def edit end - <%- end -%> # POST <%= route_url %> - # POST <%= route_url %>.json def create @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %> - respond_to do |format| - if @<%= orm_instance.save %> - <%- if options[:html] -%> - format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> } - <%- end -%> - format.json { render json: <%= "@#{singular_table_name}" %>, status: :created, location: <%= "@#{singular_table_name}" %> } - else - <%- if options[:html] -%> - format.html { render action: "new" } - <%- end -%> - format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity } - end + if @<%= orm_instance.save %> + redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> + else + render action: 'new' end end # PATCH/PUT <%= route_url %>/1 - # PATCH/PUT <%= route_url %>/1.json def update - respond_to do |format| - if @<%= orm_instance.update_attributes("#{singular_table_name}_params") %> - <%- if options[:html] -%> - format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> } - <%- end -%> - format.json { head :no_content } - else - <%- if options[:html] -%> - format.html { render action: "edit" } - <%- end -%> - format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity } - end + if @<%= orm_instance.update("#{singular_table_name}_params") %> + redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> + else + render action: 'edit' end end # DELETE <%= route_url %>/1 - # DELETE <%= route_url %>/1.json def destroy @<%= orm_instance.destroy %> - - respond_to do |format| - <%- if options[:html] -%> - format.html { redirect_to <%= index_helper %>_url } - <%- end -%> - format.json { head :no_content } - end + redirect_to <%= index_helper %>_url end private @@ -104,11 +56,7 @@ class <%= controller_class_name %>Controller < ApplicationController @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> end - # Use this method to whitelist the permissible parameters. Example: - # params.require(:person).permit(:name, :age) - # - # Also, you can specialize this method with per-user checking of permissible - # attributes. + # Never trust parameters from the scary internet, only allow the white list through. def <%= "#{singular_table_name}_params" %> <%- if attributes_names.empty? -%> params[<%= ":#{singular_table_name}" %>] diff --git a/railties/lib/rails/generators/test_unit/performance/performance_generator.rb b/railties/lib/rails/generators/test_unit/performance/performance_generator.rb deleted file mode 100644 index 5552edeee4..0000000000 --- a/railties/lib/rails/generators/test_unit/performance/performance_generator.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'rails/generators/test_unit' - -module TestUnit # :nodoc: - module Generators # :nodoc: - class PerformanceGenerator < Base # :nodoc: - check_class_collision suffix: "Test" - - def create_test_files - template 'performance_test.rb', File.join('test/performance', class_path, "#{file_name}_test.rb") - end - end - end -end diff --git a/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb b/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb deleted file mode 100644 index 2bcb482d68..0000000000 --- a/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'test_helper' -require 'rails/performance_test_help' - -class <%= class_name %>Test < ActionDispatch::PerformanceTest - # Refer to the documentation for all available options - # self.profile_options = { runs: 5, metrics: [:wall_time, :memory], - # output: 'tmp/performance', formats: [:flat] } - - test "homepage" do - get '/' - end -end diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb index 30e1650555..18bd1ece9d 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb @@ -36,7 +36,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase end test "should update <%= singular_table_name %>" do - put :update, id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> + patch :update, id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>)) end diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index aacc1be2fc..592e74726e 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -46,7 +46,7 @@ module Rails alias inspect to_s def to_html - (table = '<table>').tap do + '<table>'.tap do |table| properties.each do |(name, value)| table << %(<tr><td class="name">#{CGI.escapeHTML(name.to_s)}</td>) formatted_value = if value.kind_of?(Array) diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index e650f58d20..fa5668a5b5 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -3,12 +3,12 @@ require 'action_dispatch/routing/inspector' class Rails::InfoController < ActionController::Base # :nodoc: self.view_paths = File.expand_path('../templates', __FILE__) prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH - layout 'application' + layout -> { request.xhr? ? nil : 'application' } before_filter :require_local! def index - redirect_to '/rails/info/routes' + redirect_to action: :routes end def properties @@ -16,7 +16,7 @@ class Rails::InfoController < ActionController::Base # :nodoc: end def routes - @routes = ActionDispatch::Routing::RoutesInspector.new.collect_routes(_routes.routes) + @routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes) end protected diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 8af4130e87..de6795eda2 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -55,9 +55,9 @@ module Rails add(path, with: value, glob: glob) end - def add(path, options={}) - with = options[:with] || path - @root[path] = Path.new(self, path, [with].flatten, options) + def add(path, options = {}) + with = Array(options.fetch(:with, path)) + @root[path] = Path.new(self, path, with, options) end def [](path) @@ -189,9 +189,9 @@ module Rails path = File.expand_path(p, @root.path) if @glob && File.directory?(path) - result.concat Dir.chdir(path) { - Dir.glob(@glob).map { |file| File.join path, file }.sort - } + Dir.chdir(path) do + result.concat(Dir.glob(@glob).map { |file| File.join path, file }.sort) + end else result << path end diff --git a/railties/lib/rails/performance_test_help.rb b/railties/lib/rails/performance_test_help.rb deleted file mode 100644 index b1285efde2..0000000000 --- a/railties/lib/rails/performance_test_help.rb +++ /dev/null @@ -1,3 +0,0 @@ -ActionController::Base.perform_caching = true -ActiveSupport::Dependencies.mechanism = :require -Rails.logger.level = ActiveSupport::Logger::INFO diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index 7be2333981..6ed6722c44 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -1,19 +1,24 @@ require 'active_support/core_ext/time/conversions' require 'active_support/core_ext/object/blank' +require 'active_support/log_subscriber' +require 'action_dispatch/http/request' +require 'rack/body_proxy' module Rails module Rack # Sets log tags, logs the request, calls the app, and flushes the logs. class Logger < ActiveSupport::LogSubscriber def initialize(app, taggers = nil) - @app, @taggers = app, taggers || [] + @app = app + @taggers = taggers || [] + @instrumenter = ActiveSupport::Notifications.instrumenter end def call(env) request = ActionDispatch::Request.new(env) - if Rails.logger.respond_to?(:tagged) - Rails.logger.tagged(compute_tags(request)) { call_app(request, env) } + if logger.respond_to?(:tagged) + logger.tagged(compute_tags(request)) { call_app(request, env) } else call_app(request, env) end @@ -23,13 +28,19 @@ module Rails def call_app(request, env) # Put some space between requests in development logs. - if Rails.env.development? - Rails.logger.debug '' - Rails.logger.debug '' + if development? + logger.debug '' + logger.debug '' end - Rails.logger.info started_request_message(request) - @app.call(env) + @instrumenter.start 'action_dispatch.request', request: request + logger.info started_request_message(request) + resp = @app.call(env) + resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) } + resp + rescue + finish(request) + raise ensure ActiveSupport::LogSubscriber.flush_all! end @@ -55,6 +66,20 @@ module Rails end end end + + private + + def finish(request) + @instrumenter.finish 'action_dispatch.request', request: request + end + + def development? + Rails.env.development? + end + + def logger + Rails.logger + end end end end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 5b454e7f20..9437e9c406 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -172,7 +172,7 @@ module Rails end end - delegate :railtie_name, to: "self.class" + delegate :railtie_name, to: :class def config @config ||= Railtie::Configuration.new diff --git a/railties/lib/rails/script_rails_loader.rb b/railties/lib/rails/script_rails_loader.rb deleted file mode 100644 index 7054089614..0000000000 --- a/railties/lib/rails/script_rails_loader.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'pathname' - -module Rails - module ScriptRailsLoader - RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"] - SCRIPT_RAILS = File.join('script', 'rails') - - def self.exec_script_rails! - cwd = Dir.pwd - return unless in_rails_application? || in_rails_application_subdirectory? - exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? - 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. - exec_script_rails! unless cwd == Dir.pwd - end - rescue SystemCallError - # could not chdir, no problem just return - end - - def self.in_rails_application? - File.exists?(SCRIPT_RAILS) - end - - def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd)) - File.exists?(File.join(path, SCRIPT_RAILS)) || !path.root? && in_rails_application_subdirectory?(path.parent) - end - end -end
\ No newline at end of file diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index ac806e8006..2cbb0a435c 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -15,7 +15,7 @@ class SourceAnnotationExtractor class Annotation < Struct.new(:line, :tag, :text) def self.directories - @@directories ||= %w(app config db lib script test) + (ENV['SOURCE_ANNOTATION_DIRECTORIES'] || '').split(',') + @@directories ||= %w(app config db lib test) + (ENV['SOURCE_ANNOTATION_DIRECTORIES'] || '').split(',') end # Returns a representation of the annotation that looks like this: @@ -31,16 +31,25 @@ class SourceAnnotationExtractor end end - # Prints all annotations with tag +tag+ under the root directories +app+, +config+, +lib+, - # +script+, and +test+ (recursively). Filenames with extension - # +.builder+, +.rb+, +.erb+, +.haml+, +.slim+, +.css+, +.scss+, +.js+, - # +.coffee+, and +.rake+ are taken into account. The +options+ hash is passed to each - # annotation's +to_s+. + # Prints all annotations with tag +tag+ under the root directories +app+, + # +config+, +db+, +lib+, and +test+ (recursively). + # + # Additional directories may be added using a comma-delimited list set using + # <tt>ENV['SOURCE_ANNOTATION_DIRECTORIES']</tt>. + # + # Directories may also be explicitly set using the <tt>:dirs</tt> key in +options+. + # + # SourceAnnotationExtractor.enumerate 'TODO|FIXME', dirs: %w(app lib), tag: true + # + # If +options+ has a <tt>:tag</tt> flag, it will be passed to each annotation's +to_s+. + # + # See <tt>#find_in</tt> for a list of file extensions that will be taken into account. # # This class method is the single entry point for the rake tasks. def self.enumerate(tag, options={}) extractor = new(tag) - extractor.display(extractor.find, options) + dirs = options.delete(:dirs) || Annotation.directories + extractor.display(extractor.find(dirs), options) end attr_reader :tag @@ -51,7 +60,7 @@ class SourceAnnotationExtractor # Returns a hash that maps filenames under +dirs+ (recursively) to arrays # with their annotations. - def find(dirs = Annotation.directories) + def find(dirs) dirs.inject({}) { |h, dir| h.update(find_in(dir)) } end @@ -68,16 +77,22 @@ class SourceAnnotationExtractor if File.directory?(item) results.update(find_in(item)) - elsif item =~ /\.(builder|rb|coffee|rake)$/ - results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/)) - elsif item =~ /\.(css|scss|js)$/ - results.update(extract_annotations_from(item, /\/\/\s*(#{tag}):?\s*(.*)$/)) - elsif item =~ /\.erb$/ - results.update(extract_annotations_from(item, /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/)) - elsif item =~ /\.haml$/ - results.update(extract_annotations_from(item, /-\s*#\s*(#{tag}):?\s*(.*)$/)) - elsif item =~ /\.slim$/ - results.update(extract_annotations_from(item, /\/\s*\s*(#{tag}):?\s*(.*)$/)) + else + pattern = + case item + when /\.(builder|rb|coffee|rake)$/ + /#\s*(#{tag}):?\s*(.*)$/ + when /\.(css|scss|js)$/ + /\/\/\s*(#{tag}):?\s*(.*)$/ + when /\.erb$/ + /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/ + when /\.haml$/ + /-\s*#\s*(#{tag}):?\s*(.*)$/ + when /\.slim$/ + /\/\s*\s*(#{tag}):?\s*(.*)$/ + else nil + end + results.update(extract_annotations_from(item, pattern)) if pattern end end diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index 2851ca4189..ea6c074bdc 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -45,7 +45,7 @@ namespace :doc do rdoc.title = ENV['title'] || "Rails Application Documentation" rdoc.options << '--line-numbers' rdoc.options << '--charset' << 'utf-8' - rdoc.rdoc_files.include('doc/README_FOR_APP') + rdoc.rdoc_files.include('README.rdoc') rdoc.rdoc_files.include('app/**/*.rb') rdoc.rdoc_files.include('lib/**/*.rb') } @@ -57,7 +57,10 @@ namespace :doc do rdoc.template = "#{ENV['template']}.rb" if ENV['template'] rdoc.title = "Rails Framework Documentation" rdoc.options << '--line-numbers' - rdoc.rdoc_files.include('README.rdoc') + + gem_path('rails') do |rails| + rdoc.options << '-m' << "#{rails}/README.rdoc" + end gem_path('actionmailer') do |actionmailer| %w(README.rdoc CHANGELOG.md MIT-LICENSE lib/action_mailer/base.rb).each do |file| diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 50499304cb..2116330b45 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -1,6 +1,6 @@ namespace :rails do - desc "Update configs and some other initially generated files (or use just update:configs, update:scripts, or update:application_controller)" - task update: [ "update:configs", "update:scripts", "update:application_controller" ] + desc "Update configs and some other initially generated files (or use just update:configs, update:bin, or update:application_controller)" + task update: [ "update:configs", "update:bin", "update:application_controller" ] desc "Applies the template supplied by LOCATION=(/path/to/template) or URL" task :template do @@ -58,9 +58,9 @@ namespace :rails do invoke_from_app_generator :create_config_files end - # desc "Adds new scripts to the application script/ directory" - task :scripts do - invoke_from_app_generator :create_script_files + # desc "Adds new executables to the application bin/ directory" + task :bin do + invoke_from_app_generator :create_bin_files end # desc "Rename application.rb to application_controller.rb" diff --git a/railties/lib/rails/tasks/log.rake b/railties/lib/rails/tasks/log.rake index 6e1334692e..6c3f02eb0c 100644 --- a/railties/lib/rails/tasks/log.rake +++ b/railties/lib/rails/tasks/log.rake @@ -1,9 +1,23 @@ namespace :log do - desc "Truncates all *.log files in log/ to zero bytes" + desc "Truncates all *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)" task :clear do - FileList["log/*.log"].each do |log_file| - f = File.open(log_file, "w") - f.close + log_files.each do |file| + clear_log_file(file) end end + + def log_files + if ENV['LOGS'] + ENV['LOGS'].split(',') + .map { |file| "log/#{file.strip}.log" } + .select { |file| File.exists?(file) } + else + FileList["log/*.log"] + end + end + + def clear_log_file(file) + f = File.open(file, "w") + f.close + end end diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index 676b475640..1815c2fdc7 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -2,6 +2,6 @@ desc 'Print out all defined routes in match order, with names. Target specific c task routes: :environment do all_routes = Rails.application.routes.routes require 'action_dispatch/routing/inspector' - inspector = ActionDispatch::Routing::RoutesInspector.new - puts inspector.format(all_routes, ENV['CONTROLLER']).join "\n" + inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes) + puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, ENV['CONTROLLER']) end diff --git a/railties/lib/rails/templates/layouts/application.html.erb b/railties/lib/rails/templates/layouts/application.html.erb index 53276d3e7c..7352d48e7b 100644 --- a/railties/lib/rails/templates/layouts/application.html.erb +++ b/railties/lib/rails/templates/layouts/application.html.erb @@ -22,6 +22,10 @@ a { color: #000; } a:visited { color: #666; } a:hover { color: #fff; background-color:#000; } + + h2 { padding-left: 10px; } + + <%= yield :style %> </style> </head> <body> diff --git a/railties/lib/rails/templates/rails/info/routes.html.erb b/railties/lib/rails/templates/rails/info/routes.html.erb index 1ea387c63f..2d8a190986 100644 --- a/railties/lib/rails/templates/rails/info/routes.html.erb +++ b/railties/lib/rails/templates/rails/info/routes.html.erb @@ -6,7 +6,4 @@ Routes match in priority from top to bottom </p> -<%# actionpack/lib/action_dispatch/middleware/templates %> -<%= render layout: "routes/route_wrapper" do %> - <%= render partial: "routes/route", collection: @routes %> -<% end %> +<%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %> diff --git a/railties/lib/rails/templates/rails/welcome/index.html.erb b/railties/lib/rails/templates/rails/welcome/index.html.erb index 9a62d206dc..e239e1695e 100644 --- a/railties/lib/rails/templates/rails/welcome/index.html.erb +++ b/railties/lib/rails/templates/rails/welcome/index.html.erb @@ -59,7 +59,7 @@ #header { - background-image: url("assets/rails.png"); + background-image: url("/assets/rails.png"); background-repeat: no-repeat; background-position: top left; height: 64px; @@ -173,15 +173,18 @@ </style> <script> function about() { - info = document.getElementById('about-content'); - if (window.XMLHttpRequest) - { xhr = new XMLHttpRequest(); } - else - { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } - xhr.open("GET","rails/info/properties",false); - xhr.send(""); - info.innerHTML = xhr.responseText; - info.style.display = 'block' + var info = document.getElementById('about-content'), + xhr; + + if (info.innerHTML === '') { + xhr = new XMLHttpRequest(); + xhr.open("GET", "/rails/info/properties", false); + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + xhr.send(""); + info.innerHTML = xhr.responseText; + } + + info.style.display = info.style.display === 'none' ? 'block' : 'none'; } </script> </head> @@ -208,7 +211,7 @@ </div> <div id="about"> - <h3><a href="rails/info/properties" onclick="about(); return false">About your application’s environment</a></h3> + <h3><a href="/rails/info/properties" onclick="about(); return false">About your application’s environment</a></h3> <div id="about-content" style="display: none"></div> </div> diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 616206dd0b..739e4ad992 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -11,16 +11,6 @@ require 'action_dispatch/testing/integration' require 'rails/backtrace_cleaner' MiniTest.backtrace_filter = Rails.backtrace_cleaner -# Enable turn if it is available -begin - require 'turn' - - Turn.config do |c| - c.natural = true - end -rescue LoadError -end - if defined?(ActiveRecord::Base) class ActiveSupport::TestCase include ActiveRecord::TestFixtures diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb index ed89ce4128..f52c4c44b7 100644 --- a/railties/lib/rails/test_unit/railtie.rb +++ b/railties/lib/rails/test_unit/railtie.rb @@ -5,7 +5,6 @@ module Rails fixture_replacement: nil c.integration_tool :test_unit - c.performance_tool :test_unit end rake_tasks do diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 9ad3a4e6d6..f0d46fd959 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -45,7 +45,7 @@ end task default: :test -desc 'Runs test:units, test:functionals, test:integration together (also available: test:benchmark, test:profile)' +desc 'Runs test:units, test:functionals, test:integration together' task :test do Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke end @@ -146,15 +146,4 @@ namespace :test do t.libs << "test" t.pattern = 'test/integration/**/*_test.rb' end - - Rails::SubTestTask.new(benchmark: 'test:prepare') do |t| - t.libs << 'test' - t.pattern = 'test/performance/**/*_test.rb' - t.options = '-- --benchmark' - end - - Rails::SubTestTask.new(profile: 'test:prepare') do |t| - t.libs << 'test' - t.pattern = 'test/performance/**/*_test.rb' - end end diff --git a/railties/test/app_rails_loader_test.rb b/railties/test/app_rails_loader_test.rb new file mode 100644 index 0000000000..87e0ad7bd7 --- /dev/null +++ b/railties/test/app_rails_loader_test.rb @@ -0,0 +1,41 @@ +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? + 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 + + 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 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 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? + end +end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 53109cb041..78aefa1437 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -192,6 +192,16 @@ module ApplicationTests end end + test "filter_parameters should be able to set via config.filter_parameters in an initializer" do + app_file 'config/initializers/filter_parameters_logging.rb', <<-RUBY + Rails.application.config.filter_parameters += [ :password, :foo, 'bar' ] + RUBY + + require "#{app_path}/config/environment" + + assert_equal [:password, :foo, 'bar'], Rails.application.env_config['action_dispatch.parameter_filter'] + end + test "config.to_prepare is forwarded to ActionDispatch" do $prepared = false @@ -407,7 +417,17 @@ module ApplicationTests require "#{app_path}/config/environment" - assert_equal "Wellington", Rails.application.config.time_zone + assert_equal Time.find_zone!("Wellington"), Time.zone_default + end + + test "timezone can be set on initializers" do + app_file "config/initializers/locale.rb", <<-RUBY + Rails.application.config.time_zone = "Central Time (US & Canada)" + RUBY + + require "#{app_path}/config/environment" + + assert_equal Time.find_zone!("Central Time (US & Canada)"), Time.zone_default end test "raises when an invalid timezone is defined in the config" do @@ -557,6 +577,54 @@ module ApplicationTests assert_equal 'permitted', last_response.body end + test "config.action_controller.action_on_unpermitted_parameters = :raise" do + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ActionController::Base + def create + render text: params.require(:post).permit(:name) + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + config.action_controller.action_on_unpermitted_parameters = :raise + RUBY + + require "#{app_path}/config/environment" + + assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters + + post "/posts", {post: {"title" =>"zomg"}} + assert_match "We're sorry, but something went wrong", last_response.body + end + + test "config.action_controller.action_on_unpermitted_parameters is :log by default on development" do + ENV["RAILS_ENV"] = "development" + + require "#{app_path}/config/environment" + + assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters + end + + test "config.action_controller.action_on_unpermitted_parameters is :log by defaul on test" do + ENV["RAILS_ENV"] = "test" + + require "#{app_path}/config/environment" + + assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters + end + + test "config.action_controller.action_on_unpermitted_parameters is false by default on production" do + ENV["RAILS_ENV"] = "production" + + require "#{app_path}/config/environment" + + assert_equal false, ActionController::Parameters.action_on_unpermitted_parameters + end + test "config.action_dispatch.ignore_accept_header" do make_basic_app do |app| app.config.action_dispatch.ignore_accept_header = true diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index bc0af499c1..78ada58ec8 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -30,7 +30,7 @@ module ApplicationTests end test "allow running plugin new generator inside Rails app directory" do - FileUtils.cd(rails_root){ `ruby script/rails plugin new vendor/plugins/bukkits` } + FileUtils.cd(rails_root){ `ruby bin/rails plugin new vendor/plugins/bukkits` } assert File.exist?(File.join(rails_root, "vendor/plugins/bukkits/test/dummy/config/application.rb")) end diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb index c99666d7e4..b8e0c9be60 100644 --- a/railties/test/application/middleware/cache_test.rb +++ b/railties/test/application/middleware/cache_test.rb @@ -81,8 +81,8 @@ module ApplicationTests add_to_config "config.action_dispatch.rack_cache = true" get "/expires/expires_header" - assert_equal "miss, store", last_response.headers["X-Rack-Cache"] - assert_equal "max-age=10, public", last_response.headers["Cache-Control"] + assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"] + assert_equal "max-age=10, public", last_response.headers["Cache-Control"] body = last_response.body @@ -115,8 +115,8 @@ module ApplicationTests add_to_config "config.action_dispatch.rack_cache = true" get "/expires/expires_etag" - assert_equal "miss, store", last_response.headers["X-Rack-Cache"] - assert_equal "public", last_response.headers["Cache-Control"] + assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"] + assert_equal "public", last_response.headers["Cache-Control"] body = last_response.body etag = last_response.headers["ETag"] @@ -149,8 +149,8 @@ module ApplicationTests add_to_config "config.action_dispatch.rack_cache = true" get "/expires/expires_last_modified" - assert_equal "miss, store", last_response.headers["X-Rack-Cache"] - assert_equal "public", last_response.headers["Cache-Control"] + assert_equal "miss, ignore, store", last_response.headers["X-Rack-Cache"] + assert_equal "public", last_response.headers["Cache-Control"] body = last_response.body last = last_response.headers["Last-Modified"] diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb index fde13eeb94..f0d3438aa4 100644 --- a/railties/test/application/middleware/remote_ip_test.rb +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -40,7 +40,7 @@ module ApplicationTests end assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do - assert_equal "1.1.1.2", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") + assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") end end diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb new file mode 100644 index 0000000000..0a793f8f60 --- /dev/null +++ b/railties/test/application/middleware/static_test.rb @@ -0,0 +1,31 @@ +# encoding: utf-8 +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class MiddlewareStaticTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + # Regression test to #8907 + # See https://github.com/rails/rails/commit/9cc82b77196d21a5c7021f6dca59ab9b2b158a45#commitcomment-2416514 + test "doesn't set Cache-Control header when it is nil" do + app_file "public/foo.html", 'static' + + require "#{app_path}/config/environment" + + get 'foo' + + assert_not last_response.headers.has_key?('Cache-Control'), "Cache-Control should not be set" + end + end +end diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb index 744bb93671..3508f4225a 100644 --- a/railties/test/application/rake/notes_test.rb +++ b/railties/test/application/rake/notes_test.rb @@ -62,7 +62,6 @@ module ApplicationTests app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory" app_file "db/some_seeds.rb", "# TODO: note in db directory" app_file "lib/some_file.rb", "# TODO: note in lib directory" - app_file "script/run_something.rb", "# TODO: note in script directory" app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory" app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory" @@ -83,11 +82,10 @@ module ApplicationTests assert_match(/note in config directory/, output) assert_match(/note in db directory/, output) assert_match(/note in lib directory/, output) - assert_match(/note in script directory/, output) assert_match(/note in test directory/, output) assert_no_match(/note in some_other directory/, output) - assert_equal 6, lines.size + assert_equal 5, lines.size lines.each do |line_number| assert_equal 4, line_number.size @@ -100,7 +98,6 @@ module ApplicationTests app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory" app_file "db/some_seeds.rb", "# TODO: note in db directory" app_file "lib/some_file.rb", "# TODO: note in lib directory" - app_file "script/run_something.rb", "# TODO: note in script directory" app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory" app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory" @@ -121,12 +118,50 @@ module ApplicationTests assert_match(/note in config directory/, output) assert_match(/note in db directory/, output) assert_match(/note in lib directory/, output) - assert_match(/note in script directory/, output) assert_match(/note in test directory/, output) assert_match(/note in some_other directory/, output) - assert_equal 7, lines.size + assert_equal 6, lines.size + + lines.each do |line_number| + assert_equal 4, line_number.size + end + end + end + + test 'custom rake task finds specific notes in specific directories' do + app_file "app/controllers/some_controller.rb", "# TODO: note in app directory" + app_file "lib/some_file.rb", "# OPTIMIZE: note in lib directory\n" << "# FIXME: note in lib directory" + app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory" + + app_file "lib/tasks/notes_custom.rake", <<-EOS + require 'rails/source_annotation_extractor' + task :notes_custom do + tags = 'TODO|FIXME' + opts = { dirs: %w(lib test), tag: true } + SourceAnnotationExtractor.enumerate(tags, opts) + end + EOS + + boot_rails + + require 'rake' + require 'rdoc/task' + require 'rake/testtask' + + Rails.application.load_tasks + + Dir.chdir(app_path) do + output = `bundle exec rake notes_custom` + lines = output.scan(/\[([0-9\s]+)\]/).flatten + + assert_match(/\[FIXME\] note in lib directory/, output) + assert_match(/\[TODO\] note in test directory/, output) + assert_no_match(/OPTIMIZE/, output) + assert_no_match(/note in app directory/, output) + + assert_equal 2, lines.size lines.each do |line_number| assert_equal 4, line_number.size diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index f65b5e2f2d..6595c40f8b 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -37,27 +37,27 @@ module ApplicationTests end def test_should_run_file - app_file "script/count_users.rb", <<-SCRIPT + app_file "bin/count_users.rb", <<-SCRIPT puts User.count SCRIPT - assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "script/count_users.rb"` } + assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "bin/count_users.rb"` } end def test_should_set_dollar_0_to_file - app_file "script/dollar0.rb", <<-SCRIPT + app_file "bin/dollar0.rb", <<-SCRIPT puts $0 SCRIPT - assert_match "script/dollar0.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/dollar0.rb"` } + assert_match "bin/dollar0.rb", Dir.chdir(app_path) { `bundle exec rails runner "bin/dollar0.rb"` } end def test_should_set_dollar_program_name_to_file - app_file "script/program_name.rb", <<-SCRIPT + app_file "bin/program_name.rb", <<-SCRIPT puts $PROGRAM_NAME SCRIPT - assert_match "script/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/program_name.rb"` } + assert_match "bin/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "bin/program_name.rb"` } end def test_with_hook diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb index f6bcaa466a..c7ad2fba8f 100644 --- a/railties/test/application/test_test.rb +++ b/railties/test/application/test_test.rb @@ -52,31 +52,6 @@ module ApplicationTests run_test_file 'integration/posts_test.rb' end - test "performance test" do - controller 'posts', <<-RUBY - class PostsController < ActionController::Base - end - RUBY - - app_file 'app/views/posts/index.html.erb', <<-HTML - Posts#index - HTML - - app_file 'test/performance/posts_test.rb', <<-RUBY - require 'test_helper' - require 'rails/performance_test_help' - - class PostsTest < ActionDispatch::PerformanceTest - def test_index - get '/posts' - assert_response :success - end - end - RUBY - - run_test_file 'performance/posts_test.rb' - end - private def run_test_file(name) result = ruby '-Itest', "#{app_path}/test/#{name}" diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb index 9e449856f4..6be4a5fe89 100644 --- a/railties/test/commands/console_test.rb +++ b/railties/test/commands/console_test.rb @@ -111,6 +111,12 @@ class Rails::ConsoleTest < ActiveSupport::TestCase assert_match(/\sdevelopment\s/, output) end + def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present + Rails::Console.stubs(:available_environments).returns(['dev']) + options = Rails::Console.parse_arguments(['dev']) + assert_match('dev', options[:environment]) + end + private attr_reader :output diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb index 6316584825..38fe8ca544 100644 --- a/railties/test/commands/dbconsole_test.rb +++ b/railties/test/commands/dbconsole_test.rb @@ -45,6 +45,18 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase ENV['RAILS_ENV'] = "test" end + def test_rails_env_is_development_when_argument_is_dev + Rails::DBConsole.stubs(:available_environments).returns(['development', 'test']) + options = Rails::DBConsole.new.send(:parse_arguments, ['dev']) + assert_match('development', options[:environment]) + end + + def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present + Rails::DBConsole.stubs(:available_environments).returns(['dev']) + options = Rails::DBConsole.new.send(:parse_arguments, ['dev']) + assert_match('dev', options[:environment]) + end + def test_mysql dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], 'db') start(adapter: 'mysql', database: 'db') diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 54734ed260..f8fa8ee153 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -151,7 +151,7 @@ class ActionsTest < Rails::Generators::TestCase end def test_generate_should_run_script_generate_with_argument_and_options - generator.expects(:run_ruby_script).once.with('script/rails generate model MyModel', verbose: false) + generator.expects(:run_ruby_script).once.with('bin/rails generate model MyModel', verbose: false) action :generate, 'model', 'MyModel' end @@ -203,14 +203,14 @@ class ActionsTest < Rails::Generators::TestCase def test_readme run_generator Rails::Generators::AppGenerator.expects(:source_root).times(2).returns(destination_root) - assert_match(/Welcome to Rails/, action(:readme, "README.rdoc")) + assert_match "application up and running", action(:readme, "README.rdoc") end def test_readme_with_quiet generator(default_arguments, quiet: true) run_generator Rails::Generators::AppGenerator.expects(:source_root).times(2).returns(destination_root) - assert_no_match(/Welcome to Rails/, action(:readme, "README.rdoc")) + assert_no_match "application up and running", action(:readme, "README.rdoc") end def test_log diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index bd0f9d6a32..ee93dc49cd 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -17,23 +17,23 @@ DEFAULT_APP_FILES = %w( app/models app/models/concerns app/views/layouts + bin/bundle + bin/rails + bin/rake config/environments config/initializers config/locales db - doc lib lib/tasks lib/assets log - script/rails test/fixtures test/controllers test/models test/helpers test/mailers test/integration - test/performance vendor vendor/assets tmp/cache @@ -56,7 +56,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag\s+"application"/ assert_file "app/views/layouts/application.html.erb", /javascript_include_tag\s+"application"/ assert_file "app/assets/stylesheets/application.css" - assert_file "config/application.rb", /config\.assets\.enabled = true/ end def test_invalid_application_name_raises_an_error @@ -218,14 +217,13 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "test/test_helper.rb" do |helper_content| assert_no_match(/fixtures :all/, helper_content) end - assert_file "test/performance/browsing_test.rb" end def test_generator_if_skip_sprockets_is_given run_generator [destination_root, "--skip-sprockets"] assert_file "config/application.rb" do |content| assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content) - assert_no_match(/config\.assets\.enabled = true/, content) + assert_match(/config\.assets\.enabled = false/, content) end assert_file "Gemfile" do |content| assert_no_match(/sass-rails/, content) @@ -239,8 +237,8 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_match(/config\.assets\.digest = true/, content) assert_no_match(/config\.assets\.js_compressor = :uglifier/, content) assert_no_match(/config\.assets\.css_compressor = :sass/, content) + assert_no_match(/config\.assets\.version = '1\.0'/, content) end - assert_file "test/performance/browsing_test.rb" end def test_inclusion_of_javascript_runtime diff --git a/railties/test/generators/performance_test_generator_test.rb b/railties/test/generators/performance_test_generator_test.rb deleted file mode 100644 index 37f9857193..0000000000 --- a/railties/test/generators/performance_test_generator_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'generators/generators_test_helper' -require 'rails/generators/rails/performance_test/performance_test_generator' - -class PerformanceTestGeneratorTest < Rails::Generators::TestCase - include GeneratorsTestHelper - arguments %w(performance) - - def test_performance_test_skeleton_is_created - run_generator - assert_file "test/performance/performance_test.rb", /class PerformanceTest < ActionDispatch::PerformanceTest/ - end -end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index ab78800a4e..4bf5a2f08b 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -209,10 +209,10 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "app/views" assert_file "app/helpers" assert_file "app/mailers" + assert_file "bin/rails" assert_file "config/routes.rb", /Rails.application.routes.draw do/ assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < ::Rails::Engine\n end\nend/ assert_file "lib/bukkits.rb", /require "bukkits\/engine"/ - assert_file "script/rails" end def test_being_quiet_while_creating_dummy_application @@ -246,15 +246,15 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_usage_of_engine_commands run_generator [destination_root, "--full"] - assert_file "script/rails", /ENGINE_PATH = File.expand_path\('..\/..\/lib\/bukkits\/engine', __FILE__\)/ - assert_file "script/rails", /ENGINE_ROOT = File.expand_path\('..\/..', __FILE__\)/ - assert_file "script/rails", /require 'rails\/all'/ - assert_file "script/rails", /require 'rails\/engine\/commands'/ + assert_file "bin/rails", /ENGINE_PATH = File.expand_path\('..\/..\/lib\/bukkits\/engine', __FILE__\)/ + assert_file "bin/rails", /ENGINE_ROOT = File.expand_path\('..\/..', __FILE__\)/ + assert_file "bin/rails", /require 'rails\/all'/ + assert_file "bin/rails", /require 'rails\/engine\/commands'/ end def test_shebang run_generator [destination_root, "--full"] - assert_file "script/rails", /#!\/usr\/bin\/env ruby/ + assert_file "bin/rails", /#!\/usr\/bin\/env ruby/ end def test_passing_dummy_path_as_a_parameter diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index f13305adb0..c34ce285e3 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -31,12 +31,10 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_instance_method :create, content do |m| assert_match(/@user = User\.new\(user_params\)/, m) assert_match(/@user\.save/, m) - assert_match(/@user\.errors/, m) end assert_instance_method :update, content do |m| - assert_match(/@user\.update_attributes\(user_params\)/, m) - assert_match(/@user\.errors/, m) + assert_match(/@user\.update\(user_params\)/, m) end assert_instance_method :destroy, content do |m| @@ -101,7 +99,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_match(/class UsersControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) assert_match(/post :create, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) - assert_match(/put :update, id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) + assert_match(/patch :update, id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) end end @@ -112,7 +110,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_match(/class UsersControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) assert_match(/post :create, user: \{ \}/, content) - assert_match(/put :update, id: @user, user: \{ \}/, content) + assert_match(/patch :update, id: @user, user: \{ \}/, content) end end @@ -127,18 +125,6 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_no_file "app/views/layouts/users.html.erb" end - def test_skip_html_if_required - run_generator [ "User", "name:string", "age:integer", "--no-html" ] - assert_no_file "app/helpers/users_helper.rb" - assert_no_file "app/views/users" - - assert_file "app/controllers/users_controller.rb" do |content| - assert_no_match(/format\.html/, content) - assert_no_match(/def edit/, content) - assert_no_match(/def new/, content) - end - end - def test_default_orm_is_used run_generator ["User", "--orm=unknown"] @@ -176,7 +162,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase def test_new_hash_style run_generator assert_file "app/controllers/users_controller.rb" do |content| - assert_match(/\{ render action: "new" \}/, content) + assert_match(/render action: 'new'/, content) end end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index f3f2c170ee..357f472a3f 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -41,12 +41,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_instance_method :create, content do |m| assert_match(/@product_line = ProductLine\.new\(product_line_params\)/, m) assert_match(/@product_line\.save/, m) - assert_match(/@product_line\.errors/, m) end assert_instance_method :update, content do |m| - assert_match(/@product_line\.update_attributes\(product_line_params\)/, m) - assert_match(/@product_line\.errors/, m) + assert_match(/@product_line\.update\(product_line_params\)/, m) end assert_instance_method :destroy, content do |m| @@ -61,7 +59,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "test/controllers/product_lines_controller_test.rb" do |test| assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test) assert_match(/post :create, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) - assert_match(/put :update, id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) + assert_match(/patch :update, id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) end # Views @@ -87,7 +85,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) assert_match(/post :create, product_line: \{ \}/, content) - assert_match(/put :update, id: @product_line, product_line: \{ \}/, content) + assert_match(/patch :update, id: @product_line, product_line: \{ \}/, content) end end @@ -158,12 +156,10 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_instance_method :create, content do |m| assert_match(/@admin_role = Admin::Role\.new\(admin_role_params\)/, m) assert_match(/@admin_role\.save/, m) - assert_match(/@admin_role\.errors/, m) end assert_instance_method :update, content do |m| - assert_match(/@admin_role\.update_attributes\(admin_role_params\)/, m) - assert_match(/@admin_role\.errors/, m) + assert_match(/@admin_role\.update\(admin_role_params\)/, m) end assert_instance_method :destroy, content do |m| @@ -257,11 +253,6 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_no_file "app/assets/stylesheets/posts.css" end - def test_scaffold_generator_no_html - run_generator [ "posts", "--no-html" ] - assert_no_file "app/assets/stylesheets/scaffold.css" - end - def test_scaffold_generator_no_javascripts run_generator [ "posts", "--no-javascripts" ] assert_file "app/assets/stylesheets/scaffold.css" diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 1e5a4545a1..d203afed5c 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -48,7 +48,7 @@ module SharedGeneratorTests def test_options_before_application_name_raises_an_error content = capture(:stderr){ run_generator(["--pretend", destination_root]) } - assert_match(/Options should be given after the \w+ name. For details run: rails( plugin)? --help\n/, content) + assert_match(/Options should be given after the \w+ name. For details run: rails( plugin new)? --help\n/, content) end def test_name_collision_raises_an_error @@ -68,12 +68,12 @@ module SharedGeneratorTests def test_shebang_is_added_to_rails_file run_generator [destination_root, "--ruby", "foo/bar/baz", "--full"] - assert_file "script/rails", /#!foo\/bar\/baz/ + assert_file "bin/rails", /#!foo\/bar\/baz/ end def test_shebang_when_is_the_same_as_default_use_env run_generator [destination_root, "--ruby", Thor::Util.ruby_command, "--full"] - assert_file "script/rails", /#!\/usr\/bin\/env/ + assert_file "bin/rails", /#!\/usr\/bin\/env/ end def test_template_raises_an_error_with_invalid_path diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 172a42a549..7ae1b5ccfd 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -206,7 +206,7 @@ module TestHelpers def script(script) Dir.chdir(app_path) do - `#{Gem.ruby} #{app_path}/script/rails #{script}` + `#{Gem.ruby} #{app_path}/bin/rails #{script}` end end diff --git a/railties/test/rack_logger_test.rb b/railties/test/rack_logger_test.rb new file mode 100644 index 0000000000..3a9392fd26 --- /dev/null +++ b/railties/test/rack_logger_test.rb @@ -0,0 +1,71 @@ +require 'active_support/testing/autorun' +require 'active_support/test_case' +require 'rails/rack/logger' +require 'logger' + +module Rails + module Rack + class LoggerTest < ActiveSupport::TestCase + class TestLogger < Rails::Rack::Logger + NULL = ::Logger.new File::NULL + + attr_reader :logger + + def initialize(logger = NULL, taggers = nil, &block) + super(->(_) { block.call; [200, {}, []] }, taggers) + @logger = logger + end + + def development?; false; end + end + + class Subscriber < Struct.new(:starts, :finishes) + def initialize(starts = [], finishes = []) + super + end + + def start(name, id, payload) + starts << [name, id, payload] + end + + def finish(name, id, payload) + finishes << [name, id, payload] + end + end + + attr_reader :subscriber, :notifier + + def setup + @subscriber = Subscriber.new + @notifier = ActiveSupport::Notifications.notifier + notifier.subscribe 'action_dispatch.request', subscriber + end + + def teardown + notifier.unsubscribe subscriber + end + + def test_notification + logger = TestLogger.new { } + + assert_difference('subscriber.starts.length') do + assert_difference('subscriber.finishes.length') do + logger.call('REQUEST_METHOD' => 'GET').last.close + end + end + end + + def test_notification_on_raise + logger = TestLogger.new { raise } + + assert_difference('subscriber.starts.length') do + assert_difference('subscriber.finishes.length') do + assert_raises(RuntimeError) do + logger.call 'REQUEST_METHOD' => 'GET' + end + end + end + end + end + end +end diff --git a/railties/test/script_rails_loader_test.rb b/railties/test/script_rails_loader_test.rb deleted file mode 100644 index 3ccc147749..0000000000 --- a/railties/test/script_rails_loader_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'abstract_unit' -require 'rails/script_rails_loader' - -class ScriptRailsLoaderTest < ActiveSupport::TestCase - - test "is in a rails application if script/rails exists" do - File.stubs(:exists?).returns(true) - assert Rails::ScriptRailsLoader.in_rails_application? - end - - test "is in a rails application if parent directory has script/rails" do - File.stubs(:exists?).with("/foo/bar/script/rails").returns(false) - File.stubs(:exists?).with("/foo/script/rails").returns(true) - assert Rails::ScriptRailsLoader.in_rails_application_subdirectory?(Pathname.new("/foo/bar")) - end - - test "is not in a rails application if at the root directory and doesn't have script/rails" do - Pathname.any_instance.stubs(:root?).returns true - assert !Rails::ScriptRailsLoader.in_rails_application? - end - -end
\ No newline at end of file |