diff options
151 files changed, 1885 insertions, 4986 deletions
@@ -7,11 +7,11 @@ gem 'rack-cache', '~> 1.2' gem 'bcrypt-ruby', '~> 3.0.0' gem 'jquery-rails', '~> 2.2.0' gem 'turbolinks' -gem 'coffee-rails', '~> 4.0.0.beta1' +gem 'coffee-rails', '~> 4.0.0' # This needs to be with require false to avoid # it being automatically loaded by sprockets -gem 'uglifier', '~> 1.3', require: false +gem 'uglifier', '>= 1.3.0', require: false group :doc do gem 'sdoc' diff --git a/RAILS_VERSION b/RAILS_VERSION index 14e1b44259..14b6456d31 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -4.0.0.beta1 +4.1.0.beta diff --git a/README.md b/README.md new file mode 100644 index 0000000000..31ee4c1086 --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +## Welcome to Rails + +Rails is a web-application framework that includes everything needed to +create database-backed web applications according to the +[Model-View-Controller (MVC)](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) +pattern. + +Understanding the MVC pattern is key to understanding Rails. MVC divides your +application into three layers, each with a specific responsibility. + +The _View layer_ is composed of "templates" that are responsible for providing +appropriate representations of your application's resources. Templates can +come in a variety of formats, but most view templates are HTML with embedded +Ruby code (ERB files). + +The _Model layer_ represents your domain model (such as Account, Product, +Person, Post, etc.) and encapsulates the business logic that is specific to +your application. In Rails, database-backed model classes are derived from +`ActiveRecord::Base`. Active Record allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. Although most Rails models are backed by a database, models can also +be ordinary Ruby classes, or Ruby classes that implement a set of interfaces +as provided by the Active Model module. You can read more about Active Record +in its [README](activerecord/README.rdoc). + +The _Controller layer_ is responsible for handling incoming HTTP requests and +providing a suitable response. Usually this means returning HTML, but Rails +controllers can also generate XML, JSON, PDFs, mobile-specific views, and +more. Controllers manipulate models and render view templates in order to +generate the appropriate HTTP response. + +In Rails, the Controller and View layers are handled together by Action Pack. +These two layers are bundled in a single package due to their heavy interdependence. +This is unlike the relationship between Active Record and Action Pack, which are +independent. Each of these packages can be used independently outside of Rails. You +can read more about Action Pack in its [README](actionpack/README.rdoc). + +## Getting Started + +1. Install Rails at the command prompt if you haven't yet: + + gem install rails + +2. At the command prompt, create a new Rails application: + + rails new myapp + + where "myapp" is the application name. + +3. Change directory to `myapp` and start the web server: + + cd myapp + rails server + + Run with `--help` or `-h` for options. + +4. Go to http://localhost:3000 and you'll see: "Welcome aboard: You're riding Ruby on Rails!" + +5. Follow the guidelines to start developing your application. You may find + the following resources handy: + * [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html) + * [Ruby on Rails Guides](http://guides.rubyonrails.org) + * [The API Documentation](http://api.rubyonrails.org) + * [Ruby on Rails Tutorial](http://ruby.railstutorial.org/ruby-on-rails-tutorial-book) + +## Contributing + +We encourage you to contribute to Ruby on Rails! Please check out the +[Contributing to Ruby on Rails guide](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) for guidelines about how to proceed. [Join us!](http://contributors.rubyonrails.org) + +## Code Status + +* [![Build Status](https://api.travis-ci.org/rails/rails.png)](https://travis-ci.org/rails/rails) +* [![Dependencies](https://gemnasium.com/rails/rails.png?travis)](https://gemnasium.com/rails/rails) + +## License + +Ruby on Rails is released under the [MIT License](http://www.opensource.org/licenses/MIT). diff --git a/README.rdoc b/README.rdoc deleted file mode 100644 index 55b5efc916..0000000000 --- a/README.rdoc +++ /dev/null @@ -1,77 +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-Controller (MVC)}[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller] pattern. - -Understanding the MVC pattern is key to understanding Rails. MVC divides your application -into three layers, each with a specific responsibility. - -The View layer is composed of "templates" that are responsible for providing -appropriate representations of your application's resources. Templates -can come in a variety of formats, but most view templates are \HTML with embedded Ruby -code (.erb files). - -The Model layer represents your domain model (such as Account, Product, Person, Post) -and encapsulates the business logic that is specific to your application. In Rails, -database-backed model classes are derived from ActiveRecord::Base. Active Record allows -you to present the data from database rows as objects and embellish these data objects -with business logic methods. Although most Rails models are backed by a database, models -can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as -provided by the ActiveModel module. You can read more about Active Record in its -{README}[link:/activerecord/README.rdoc]. - -The Controller layer is responsible for handling incoming HTTP requests and providing a -suitable response. Usually this means returning \HTML, but Rails controllers can also -generate XML, JSON, PDFs, mobile-specific views, and more. Controllers manipulate models -and render view templates in order to generate the appropriate HTTP response. - -In Rails, the Controller and View layers are handled together by Action Pack. -These two layers are bundled in a single package due to their heavy interdependence. -This is unlike the relationship between Active Record and Action Pack, which are -independent. Each of these packages can be used independently outside of Rails. You -can read more about Action Pack in its {README}[link:/actionpack/README.rdoc]. - -== Getting Started - -1. Install Rails at the command prompt if you haven't yet: - - gem install rails - -2. At the command prompt, create a new Rails application: - - rails new myapp - - where "myapp" is the application name. - -3. Change directory to +myapp+ and start the web server: - - cd myapp; rails server - - Run with <tt>--help</tt> or <tt>-h</tt> for options. - -4. Go to http://localhost:3000 and you'll see: - - "Welcome aboard: You're riding Ruby on Rails!" - -5. Follow the guidelines to start developing your application. You may find the following resources handy: - -* The README file created within your application. -* {Getting Started with Rails}[http://guides.rubyonrails.org/getting_started.html]. -* {Ruby on Rails Tutorial}[http://ruby.railstutorial.org/ruby-on-rails-tutorial-book]. -* {Ruby on Rails Guides}[http://guides.rubyonrails.org]. -* {The API Documentation}[http://api.rubyonrails.org]. - -== Contributing - -We encourage you to contribute to Ruby on Rails! Please check out the {Contributing to Rails -guide}[http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how -to proceed. {Join us}[http://contributors.rubyonrails.org]! - -== Code Status - -* {<img src="https://secure.travis-ci.org/rails/rails.png"/>}[http://travis-ci.org/rails/rails] -* {<img src="https://gemnasium.com/rails/rails.png?travis"/>}[https://gemnasium.com/rails/rails] - -== License - -Ruby on Rails is released under the {MIT License}[http://www.opensource.org/licenses/MIT]. diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 975f61fa58..9e9d07b386 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,62 +1,3 @@ -## Rails 4.0.0 (unreleased) ## +* No changes. - -## Rails 4.0.0.beta1 (February 25, 2013) ## - -* Allow passing interpolations to `#default_i18n_subject`, e.g.: - - # config/locales/en.yml - en: - user_mailer: - welcome: - subject: 'Hello, %{username}' - - # app/mailers/user_mailer.rb - class UserMailer < ActionMailer::Base - def welcome(user) - mail(subject: default_i18n_subject(username: user.name)) - end - end - - *Olek Janiszewski* - -* Eager loading made to use relation's `in_clause_length` instead of host's one. - Fixes #8474. - - *Boris Staal* - -* Explicit multipart messages no longer set the order of the MIME parts. - - *Nate Berkopec* - -* Do not render views when `mail` isn't called. Fixes #7761. - - *Yves Senn* - -* Allow delivery method options to be set per mail instance. - - If your SMTP delivery settings are dynamic, you can now override settings - per mail instance for e.g. - - def my_mailer(user, company) - mail to: user.email, subject: "Welcome!", - delivery_method_options: { user_name: company.smtp_user, - password: company.smtp_password } - end - - This will ensure that your default SMTP settings will be overridden - by the company specific ones. You only have to override the settings - that are dynamic and leave the static setting in your environment - configuration file (e.g. `config/environments/production.rb`). - - *Aditya Sanghi* - -* Allow to set default Action Mailer options via `config.action_mailer.default_options=`. *Robert Pankowecki* - -* Raise an `ActionView::MissingTemplate` exception when no implicit template could be found. *Damien Mathieu* - -* Allow callbacks to be defined in mailers similar to `ActionController::Base`. You can configure default - settings, headers, attachments, delivery settings or change delivery using - `before_filter`, `after_filter`, etc. *Justin S. Leitgeb* - -Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/actionmailer/CHANGELOG.md) for previous changes. +Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/actionmailer/CHANGELOG.md) for previous changes. diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 89e31c4be6..9d00091972 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -1,7 +1,7 @@ module ActionMailer # Returns the version of the currently loaded ActionMailer as a Gem::Version def self.version - Gem::Version.new "4.0.0.beta1" + Gem::Version.new "4.1.0.beta" end module VERSION #:nodoc: diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 88cdd53336..ffafa7412d 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,1159 +1,3 @@ -## Rails 4.0.0 (unreleased) ## +* No changes. -* Fix explicit names on multiple file fields. If a file field tag has - the multiple option, it is turned into an array field (appending `[]`), - but if an explicit name is passed to `file_field` the `[]` is not - appended. - Fixes #9830. - - *Ryan McGeary* - -* Add block support for the `mail_to` helper, similar to the `link_to` helper. - - *Sam Pohlenz* - -* Automatically configure cookie-based sessions to be encrypted if - `secret_key_base` is set, falling back to signed if only `secret_token` - is set. Automatically upgrade existing signed cookie-based sessions from - Rails 3.x to be encrypted if both `secret_key_base` and `secret_token` - are set, or signed with the new key generator if only `secret_token` is - set. This leaves only the `config.session_store :cookie_store` option and - removes the two new options introduced in 4.0.0.beta1: - `encrypted_cookie_store` and `upgrade_signature_to_encryption_cookie_store`. - - *Trevor Turk* - -* Ensure consistent fallback to the default layout lookup for layouts set - using symbols or procs that return `nil`. - - All of the following layouts will result in the default layout lookup: - - layout nil - - layout proc { nil } - - layout :returns_nil - def returns_nil - nil - end - - Previously symbols and procs which returned `nil` resulted in no layout which - differed from the `layout nil` behavior. To get the "no layout" behavior just - return `false` instead of `nil` for `layout`. - - *Chris Nicola* - -* Create `UpgradeLegacySignedCookieJar` to transparently upgrade existing signed - cookies generated by Rails 3.x to avoid invalidating them when upgrading to Rails 4.x. - - *Trevor Turk + Neeraj Singh* - -* Raise an `ArgumentError` when a clashing named route is defined. - - *Trevor Turk* - -* Allow default url options to accept host with protocol such as `http://` - - config.action_mailer.default_url_options = { host: "http://mydomain.com" } - - *Richard Schneeman* - -* Ensure that digest authentication responds with a 401 status when a basic - header is received. - - *Brad Dunbar* - -* Include I18n locale fallbacks in view lookup. - Fixes #3512. - - *Juan Barreneche* - -* Integration and functional tests allow headers and rack env - variables to be passed when performing requests. - Fixes #6513. - - Example: - - # integration test - get "/success", {}, "HTTP_REFERER" => "http://test.com/", - "Accepts" => "text/plain, text/html" - - # functional test - @request.headers["Accepts"] = "text/plain, text/html" - - *Yves Senn* - -* Http::Headers respects headers that are not prefixed with HTTP_ - - *Yves Senn* - -* Fix incorrectly appended square brackets to a multiple select box - if an explicit name has been given and it already ends with "[]" - - Before: - - select(:category, [], {}, multiple: true, name: "post[category][]") - # => <select name="post[category][][]" ...> - - After: - - select(:category, [], {}, multiple: true, name: "post[category][]") - # => <select name="post[category][]" ...> - - *Olek Janiszewski* - -* Fixed regression when using `assert_template` to verify files sent using - `render file: 'README.md'`. - Fixes #9464. - - *Justin Coyne* - -* Fixed `ActionView::Helpers::CaptureHelper#content_for` regression when trying to use it in - a boolean statement. - Fixes #9360. - - *Nikolay Shebanov* - -* `format: true` does not override existing format constraints. - Fixes #9466. - - Example: - - # This will force the .json extension. - get '/json_only', to: ok, format: true, constraints: { format: /json/ } - - *Yves Senn* - -* Skip valid encoding checks for non-String parameters that come - from the matched route's defaults. - Fixes #9435. - - Example: - - root to: 'main#posts', page: 1 - - *Yves Senn* - -* Don't verify Regexp requirements for non-Regexp `:constraints`. - Fixes #9432. - - Example: - - get '/photos.:format' => 'feeds#photos', constraints: {format: 'xml'} - - *Yves Senn* - -* Make `ActionDispatch::Journey::Path::Pattern#new` raise more meaningful exception message. - - *Thierry Zires* - - -## Rails 4.0.0.beta1 (February 25, 2013) ## - -* Fix `respond_to` not using formats that have no block if all is present. *Michael Grosser* - -* New applications use an encrypted session store by default. - - *Santiago Pastorino* - -* Determine the controller#action from only the matched path when using the - shorthand syntax. Previously the complete path was used, which led - to problems with nesting (scopes and namespaces). - Fixes #7554. - - Example: - - # This will route to questions#new. - scope ':locale' do - get 'questions/new' - end - - *Yves Senn* - -* Remove support for parsing XML parameters from request. If you still want to parse XML - parameters, please install `actionpack-xml_parser' gem. - - *Prem Sichanugrist* - -* Remove support for parsing YAML parameters from request. - - *Aaron Patterson* - -* Add a message when you have no routes defined to both `rake routes` and - GET "/rails/info/routes" that lets you know you have none defined and links - to the Rails guide on the topic. - - *Steve Klabnik* - -* Change `image_alt` method to replace underscores/hyphens to spaces in filenames. - - Previously, underscored filenames became `alt="A_long_file_name_with_underscores"` - in HTML, which is poor for accessibility. For instance, Apple's VoiceOver Utility - pronounces each underscore. `A_long_file_name` thus would be read as `A underscore - long underscore file underscore name.` Now underscored or hyphenated filenames - (both of which are very popular naming conventions) read more naturally in - screen readers by converting both hyphens and underscores to spaces. - - Before: - - image_tag('underscored_file_name.png') - # => <img alt="Underscored_file_name" src="/assets/underscored_file_name.png" /> - - After: - - image_tag('underscored_file_name.png') - # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" /> - - *Nick Cox* - -* We don't support Ruby constant notation in the `:controller` option for route - definitions. So, this raises an `ArgumentError` now: - - resources :posts, controller: "Admin::Posts" # WRONG - - Use path notation instead: - - resources :posts, controller: "admin/posts" # RIGHT - - *Yves Senn* - -* `assert_template` can be used to verify the locals of partials, - which live inside a directory. - - # Prefixed partials inside directories worked and still work. - assert_template partial: 'directory/_partial', locals: {name: 'John'} - - # This did not work but does now. - assert_template partial: 'directory/partial', locals: {name: 'John'} - - Fixes #8516. - - *Yves Senn* - -* Fix `content_tag_for` with array HTML option. - It would embed array as string instead of joining it like `content_tag` does: - - content_tag(:td, class: ["foo", "bar"]){} - # => <td class="foo bar"></td> - - Before: - - content_tag_for(:td, item, class: ["foo", "bar"]) - # => <td class="item ["foo", "bar"]" id="item_1"></td> - - After: - - content_tag_for(:td, item, class: ["foo", "bar"]) - # => <td class="item foo bar" id="item_1"></td> - - *Semyon Perepelitsa* - -* Remove `BestStandardsSupport` middleware, !DOCTYPE html already triggers - standards mode per http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx - and ChromeFrame header has been moved to `config.action_dispatch.default_headers` - - *Guillermo Iguaran* - -* Fix CSRF protection and `current_url?` helper to work with HEAD requests - now that `ActionDispatch::Head` has been removed in favor of `Rack::Head`. - - *Michiel Sikkes* - -* Change `asset_path` to not include `SCRIPT_NAME` when it's used - from a mounted engine. Fixes #8119. - - *Piotr Sarnacki* - -* 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* - -* 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. - Fixes #7979. - - *André Arko*, *Steve Klabnik*, *Alexey Gaziev* - -* Do not append second slash to `root_url` when using `trailing_slash: true` - Fixes #8700. - - Before: - - root_url(trailing_slash: true) # => http://test.host// - - After: - - root_url(trailing_slash: true) # => 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. - Fixes #8661. - - *Yves Senn* - -* Added `Mime::NullType` class. This allows to use `html?`, `xml?`, `json?`, etc. - when the format of the request is unknown, without raising an exception. - - *Angelo Capilleri* - -* Integrate the Journey gem into Action Dispatch so that the global namespace - is not polluted with names that may be used as models. - - *Andrew White* - -* Extract support for email address obfuscation via `:encode`, `:replace_at`, and `replace_dot` - options from the `mail_to` helper into the `actionview-encoded_mail_to` gem. - - *Nick Reed + DHH* - -* Handle `:protocol` option in `stylesheet_link_tag` and `javascript_include_tag` - - *Vasiliy Ermolovich* - -* Clear url helper methods when routes are reloaded. *Andrew White* - -* Fix a bug in `ActionDispatch::Request#raw_post` that caused `env['rack.input']` - to be read but not rewound. - - *Matt Venables* - -* Prevent raising `EOFError` on multipart GET request (IE issue). *Adam Stankiewicz* - -* Rename all action callbacks from *_filter to *_action to avoid the misconception that these - callbacks are only suited for transforming or halting the response. With the new style, - it's more inviting to use them as they were intended, like setting shared ivars for views. - - Example: - - class PeopleController < ActionController::Base - before_action :set_person, except: [:index, :new, :create] - before_action :ensure_permission, only: [:edit, :update] - - ... - - private - def set_person - @person = current_account.people.find(params[:id]) - end - - def ensure_permission - current_person.can_change?(@person) - end - end - - The old *_filter methods still work with no deprecation notice. - - *DHH* - -* Add `cache_if` and `cache_unless` for conditional fragment caching: - - Example: - - <%= cache_if condition, project do %> - <b>All the topics on this project</b> - <%= render project.topics %> - <% end %> - - # and - - <%= cache_unless condition, project do %> - <b>All the topics on this project</b> - <%= render project.topics %> - <% end %> - - *Stephen Ausman + Fabrizio Regini + Angelo Capilleri* - -* Add logging filter capability for redirect URLs: - - config.filter_redirect << 'http://please.hide.it/' - - *Fabrizio Regini* - -* Fixed a bug that ignores constraints on a glob route. This was caused because the constraint - regular expression is overwritten when the `routes.rb` file is processed. Fixes #7924 - - *Maura Fitzgerald* - -* More descriptive error messages when calling `render :partial` with - an invalid `:layout` argument. - - Fixes #8376. - - render partial: 'partial', layout: true - - # results in ActionView::MissingTemplate: Missing partial /true - - *Yves Senn* - -* Sweepers was extracted from Action Controller as `rails-observers` gem. - - *Rafael Mendonça França* - -* Add option flag to `CacheHelper#cache` to manually bypass automatic template digests: - - <% cache project, skip_digest: true do %> - ... - <% end %> - - *Drew Ulmer* - -* Do not sort Hash options in `grouped_options_for_select`. *Sergey Kojin* - -* Accept symbols as `send_data :disposition` value *Elia Schito* - -* Add i18n scope to `distance_of_time_in_words`. *Steve Klabnik* - -* `assert_template`: - - is no more passing with empty string. - - is now validating option keys. It accepts: `:layout`, `:partial`, `:locals` and `:count`. - - *Roberto Soares* - -* Allow setting a symbol as path in scope on routes. This is now allowed: - - scope :api do - resources :users - end - - It is also possible to pass multiple symbols to scope to shorten multiple nested scopes: - - scope :api do - scope :v1 do - resources :users - end - end - - can be rewritten as: - - scope :api, :v1 do - resources :users - end - - *Guillermo Iguaran + Amparo Luna* - -* Fix error when using a non-hash query argument named "params" in `url_for`. - - Before: - - url_for(params: "") # => undefined method `reject!' for "":String - - After: - - url_for(params: "") # => http://www.example.com?params= - - *tumayun + Carlos Antonio da Silva* - -* Render every partial with a new `ActionView::PartialRenderer`. This resolves - issues when rendering nested partials. - Fixes #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`. - Fixes #7976. - - *Joost Baaij* - -* Fix input name when `multiple: true` and `:index` are set. - - Before: - - check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1) - # => <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" /> - - After: - - check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1) - # => <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" /> - - Fixes #8108. - - *Daniel Fox, Grant Hutchins & Trace Wax* - -* `date_select` helper accepts `with_css_classes: true` to add css classes similar with type - of generated select tags. - - *Pavel Nikitin* - -* 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 - (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). - - *Josh Peek* - -* `assert_template` can be used to assert on the same template with different locals - Fixes #3675. - - *Yves Senn* - -* Remove old asset tag concatenation (no longer needed now that we have the asset pipeline). - - *Josh Peek* - -* 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 - Fixes #3415. - - *Yves Senn* - -* The `Rack::Cache` middleware is now disabled by default. To enable it, - set `config.action_dispatch.rack_cache = true` and add `gem rack-cache` to your Gemfile. - - *Guillermo Iguaran* - -* `ActionController::Base.page_cache_extension` option is deprecated - in favour of `ActionController::Base.default_static_extension`. - - *Francesco Rodriguez* - -* Action and Page caching has been extracted from Action Dispatch - as `actionpack-action_caching` and `actionpack-page_caching` gems. - Please read the `README.md` file on both gems for the usage. - - *Francesco Rodriguez* - -* Failsafe exception returns `text/plain`. *Steve Klabnik* - -* Rename internal variables on `ActionController::TemplateAssertions` to prevent - naming collisions. `@partials`, `@templates` and `@layouts` are now prefixed with an underscore. - Fixes #7459. - - *Yves Senn* - -* `resource` and `resources` don't modify the passed options hash. - Fixes #7777. - - *Yves Senn* - -* 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' ] - - # Precompiles phone/index-<digest>.css and aliases phone.css to phone/index.css. - config.assets.precompile = [ 'phone/index.css' ] - - # Both of these work with either precompile thanks to their aliases. - <%= stylesheet_link_tag 'phone', media: 'all' %> - <%= stylesheet_link_tag 'phone/index', media: 'all' %> - - *Jeremy Kemper* - -* `assert_template` is no more passing with what ever string that matches - with the template name. - - Before when we have a template `/layout/hello.html.erb`, `assert_template` - was passing with any string that matches. This behavior allowed false - positive like: - - assert_template "layout" - assert_template "out/hello" - - Now it only passes with: - - assert_template "layout/hello" - assert_template "hello" - - Fixes #3849. - - *Hugolnx* - -* `image_tag` will set the same width and height for image if numerical value - passed to `size` option. - - *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. - - *Michael Grosser* - -* `ActionDispatch::Http::UploadedFile` now delegates `close` to its tempfile. *Sergio Gil* - -* Add `ActionController::StrongParameters`, this module converts `params` hash into - an instance of ActionController::Parameters that allows whitelisting of permitted - parameters. Non-permitted parameters are forbidden to be used in Active Model by default - For more details check the documentation of the module or the - [strong_parameters gem](https://github.com/rails/strong_parameters) - - *DHH + Guillermo Iguaran* - -* Remove Integration between `attr_accessible`/`attr_protected` and - `ActionController::ParamsWrapper`. ParamWrapper now wraps all the parameters returned - by the class method `attribute_names`. - - *Guillermo Iguaran* - -* Log now displays the correct status code when an exception is raised. - Fixes #7646. - - *Yves Senn* - -* Allow pass couple extensions to `ActionView::Template.register_template_handler` call. - - *Tima Maslyuchenko* - -* Sprockets integration has been extracted from Action Pack to the `sprockets-rails` - gem. `rails` gem is depending on `sprockets-rails` by default. - - *Guillermo Iguaran* - -* `ActionDispatch::Session::MemCacheStore` now uses `dalli` instead of the deprecated - `memcache-client` gem. - - *Arun Agrawal + Guillermo Iguaran* - -* Support multiple etags in If-None-Match header. *Travis Warlick* - -* Allow to configure how unverified request will be handled using `:with` - option in `protect_from_forgery` method. - - Valid unverified request handling methods are: - - - `:exception` - Raises ActionController::InvalidAuthenticityToken exception. - - `:reset_session` - Resets the session. - - `:null_session` - Provides an empty session during request but doesn't - reset it completely. Used as default if `:with` option is not specified. - - New applications are generated with: - - protect_from_forgery with: :exception - - *Sergey Nartimov* - -* Add `.ruby` template handler, this handler simply allows arbitrary Ruby code as a template. *Guillermo Iguaran* - -* Add `separator` option for `ActionView::Helpers::TextHelper#excerpt`: - - excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1) - # => ...a very beautiful... - - *Guirec Corbel* - -* Added controller-level etag additions that will be part of the action etag computation *Jeremy Kemper/DHH* - - class InvoicesController < ApplicationController - etag { current_user.try :id } - - def show - # Etag will differ even for the same invoice when it's viewed by a different current_user - @invoice = Invoice.find(params[:id]) - fresh_when(@invoice) - end - end - -* 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 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* - -* Deprecate `Template#mime_type`, it will be removed in Rails 4.1 in favor of `#type`. - *Piotr Sarnacki* - -* Move vendored html-scanner from `action_controller` to `action_view` directory. If you - require it directly, please use 'action_view/vendor/html-scanner', reference to - 'action_controller/vendor/html-scanner' will be removed in Rails 4.1. *Piot Sarnacki* - -* Fix handling of date selects when using both disabled and discard options. - Fixes #7431. - - *Vasiliy Ermolovich* - -* `ActiveRecord::SessionStore` is extracted out of Rails into a gem `activerecord-session_store`. - Setting `config.session_store` to `:active_record_store` will no longer work and will break - if the `activerecord-session_store` gem isn't available. *Prem Sichanugrist* - -* Fix `select_tag` when `option_tags` is nil. - Fixes #7404. - - *Sandeep Ravichandran* - -* Add `Request#formats=(extensions)` that lets you set multiple formats directly in a prioritized order. - - Example of using this for custom iphone views with an HTML fallback: - - class ApplicationController < ActionController::Base - before_filter :adjust_format_for_iphone_with_html_fallback - - private - def adjust_format_for_iphone_with_html_fallback - request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/] - end - end - - *DHH* - -* Add Routing Concerns to declare common routes that can be reused inside - others resources and routes. - - Code before: - - resources :messages do - resources :comments - end - - resources :posts do - resources :comments - resources :images, only: :index - end - - Code after: - - concern :commentable do - resources :comments - end - - concern :image_attachable do - resources :images, only: :index - end - - resources :messages, concerns: :commentable - - resources :posts, concerns: [:commentable, :image_attachable] - - *DHH + Rafael Mendonça França* - -* Add `start_hour` and `end_hour` options to the `select_hour` helper. *Evan Tann* - -* Raises an `ArgumentError` when the first argument in `form_for` contain `nil` - or is empty. - - *Richard Schneeman* - -* Add 'X-Frame-Options' => 'SAMEORIGIN' - 'X-XSS-Protection' => '1; mode=block' and - 'X-Content-Type-Options' => 'nosniff' - as default headers. - - *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* - -* Deprecate `button_to_function` and `link_to_function` helpers. - - We recommend the use of Unobtrusive JavaScript instead. For example: - - link_to "Greeting", "#", class: "nav_link" - - $(function() { - $('.nav_link').click(function() { - // Some complex code - - return false; - }); - }); - - or - - link_to "Greeting", '#', onclick: "alert('Hello world!'); return false", class: "nav_link" - - for simple cases. - - *Rafael Mendonça França* - -* `javascript_include_tag :all` will now not include `application.js` if the file does not exists. *Prem Sichanugrist* - -* Send an empty response body when call `head` with status between 100 and 199, 204, 205 or 304. - - *Armand du Plessis* - -* Fixed issue with where digest authentication would not work behind a proxy. *Arthur Smith* - -* Added `ActionController::Live`. Mix it in to your controller and you can - stream data to the client live. For example: - - class FooController < ActionController::Base - include ActionController::Live - - def index - 100.times { - # Client will see this as it's written - response.stream.write "hello world\n" - sleep 1 - } - response.stream.close - end - end - - *Aaron Patterson* - -* Remove `ActionDispatch::Head` middleware in favor of `Rack::Head`. *Santiago Pastorino* - -* Deprecate `:confirm` in favor of `data: { confirm: "Text" }` option for `button_to`, `button_tag`, `image_submit_tag`, `link_to` and `submit_tag` helpers. - - *Carlos Galdino + Rafael Mendonça França* - -* 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.: - - class ApplicationController - add_flash_types :error, :warning - end - - If you add the above code, you can use `<%= error %>` in an erb, and `redirect_to /foo, error: 'message'` in a controller. - - *kennyj* - -* Remove Active Model dependency from Action Pack. *Guillermo Iguaran* - -* Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping: - - get Rack::Utils.escape('こんにちは') => 'home#index' - - You just have to write the unicode route: - - get 'こんにちは' => 'home#index' - - *kennyj* - -* Return proper format on exceptions. *Santiago Pastorino* - -* Allow to use `mounted_helpers` (helpers for accessing mounted engines) in `ActionView::TestCase`. *Piotr Sarnacki* - -* Include `mounted_helpers` (helpers for accessing mounted engines) in `ActionDispatch::IntegrationTest` by default. *Piotr Sarnacki* - -* Extracted redirect logic from `ActionController::ForceSSL::ClassMethods.force_ssl` into `ActionController::ForceSSL#force_ssl_redirect` - - *Jeremy Friesen* - -* Make possible to use a block in `button_to` if the button text is hard - to fit into the name parameter, e.g.: - - <%= button_to [:make_happy, @user] do %> - Make happy <strong><%= @user.name %></strong> - <% end %> - # => "<form method="post" action="/users/1/make_happy" class="button_to"> - # <div> - # <button type="submit"> - # Make happy <strong>Name</strong> - # </button> - # </div> - # </form>" - - *Sergey Nartimov* - -* 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 - predictable. The most common use case for such behavior is loading helpers - from engines. When you load helpers from application and engine Foo, in - that order, first rails will load all of the helpers from application, - sorted alphabetically and then it will do the same for Foo engine. - - *Piotr Sarnacki* - -* `truncate` now always returns an escaped HTML-safe string. The option `:escape` can be used as - false to not escape the result. - - *Li Ellis Gallardo + Rafael Mendonça França* - -* `truncate` now accepts a block to show extra content when the text is truncated. *Li Ellis Gallardo* - -* Add `week_field`, `week_field_tag`, `month_field`, `month_field_tag`, `datetime_local_field`, - `datetime_local_field_tag`, `datetime_field` and `datetime_field_tag` helpers. *Carlos Galdino* - -* Add `color_field` and `color_field_tag` helpers. *Carlos Galdino* - -* `assert_generates`, `assert_recognizes`, and `assert_routing` all raise - `Assertion` instead of `RoutingError` *David Chelimsky* - -* URL path parameters with invalid encoding now raise `ActionController::BadRequest`. *Andrew White* - -* Malformed query and request parameter hashes now raise `ActionController::BadRequest`. *Andrew White* - -* Add `divider` option to `grouped_options_for_select` to generate a separator - `optgroup` automatically, and deprecate `prompt` as third argument, in favor - of using an options hash. *Nicholas Greenfield* - -* Add `time_field` and `time_field_tag` helpers which render an `input[type="time"]` tag. *Alex Soulim* - -* 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* - -* Deprecate `:disable_with` in favor of `data: { disable_with: "Text" }` option from `submit_tag`, `button_tag` and `button_to` helpers. - - *Carlos Galdino + Rafael Mendonça França* - -* 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 - `display size` is one and `multiple` is not true. *Angelo Capilleri* - -* Copy literal route constraints to defaults so that url generation know about them. - The copied constraints are `:protocol`, `:subdomain`, `:domain`, `:host` and `:port`. - - *Andrew White* - -* `respond_to` and `respond_with` now raise `ActionController::UnknownFormat` instead - of directly returning head 406. The exception is rescued and converted to 406 - in the exception handling middleware. *Steven Soroka* - -* Allows `assert_redirected_to` to match against a regular expression. *Andy Lindeman* - -* Add backtrace to development routing error page. *Richard Schneeman* - -* Replace `include_seconds` boolean argument with `include_seconds: true` option - in `distance_of_time_in_words` and `time_ago_in_words` signature. *Dmitriy Kiriyenko* - -* 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* - -* Allow to lazy load `default_form_builder` by passing a `String` instead of a constant. *Piotr Sarnacki* - -* Session arguments passed to `process` calls in functional tests are now merged into - the existing session, whereas previously they would replace the existing session. - This change may break some existing tests if they are asserting the exact contents of - the session but should not break existing tests that only assert individual keys. - - *Andrew White* - -* Add `index` method to FormBuilder class. *Jorge Bejar* - -* Remove the leading \n added by textarea on `assert_select`. *Santiago Pastorino* - -* 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. - -* 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* - -* Removed default `size` option from the `text_field`, `search_field`, `telephone_field`, `url_field`, `email_field` helpers. *Philip Arndt* - -* Removed default `cols` and `rows` options from the `text_area` helper. *Philip Arndt* - -* Adds support for layouts when rendering a partial with a given collection. *serabe* - -* Allows the route helper `root` to take a string argument. For example, `root 'pages#main'`. *bcardarella* - -* Forms of persisted records use always PATCH (via the `_method` hack). *fxn* - -* For resources, both PATCH and PUT are routed to the `update` action. *fxn* - -* Don't ignore `force_ssl` in development. This is a change of behavior - use a `:if` condition to recreate the old behavior. - - class AccountsController < ApplicationController - force_ssl if: :ssl_configured? - - def ssl_configured? - !Rails.env.development? - end - end - - *Pat Allan* - -* Adds support for the PATCH verb: - * Request objects respond to `patch?`. - * Routes have a new `patch` method, and understand `:patch` in the - existing places where a verb is configured, like `:via`. - * New method `patch` available in functional tests. - * If `:patch` is the default verb for updates, edits are - tunneled as PATCH rather than as PUT, and routing acts accordingly. - * New method `patch_via_redirect` available in integration tests. - - *dlee* - -* Integration tests support the `OPTIONS` method. *Jeremy Kemper* - -* `expires_in` accepts a `must_revalidate` flag. If true, "must-revalidate" - is added to the Cache-Control header. *fxn* - -* Add `date_field` and `date_field_tag` helpers which render an `input[type="date"]` tag *Olek Janiszewski* - -* Adds `image_url`, `javascript_url`, `stylesheet_url`, `audio_url`, `video_url`, and `font_url` - to assets tag helper. These URL helpers will return the full path to your assets. This is useful - when you are going to reference this asset from external host. *Prem Sichanugrist* - -* Default responder will now always use your overridden block in `respond_with` to render your response. *Prem Sichanugrist* - -* Allow `value_method` and `text_method` arguments from `collection_select` and - `options_from_collection_for_select` to receive an object that responds to `:call`, - such as a `proc`, to evaluate the option in the current element context. This works - the same way with `collection_radio_buttons` and `collection_check_boxes`. - - *Carlos Antonio da Silva + Rafael Mendonça França* - -* Add `collection_check_boxes` form helper, similar to `collection_select`: - Example: - - collection_check_boxes :post, :author_ids, Author.all, :id, :name - # Outputs something like: - <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" /> - <label for="post_author_ids_1">D. Heinemeier Hansson</label> - <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" /> - <label for="post_author_ids_2">D. Thomas</label> - <input name="post[author_ids][]" type="hidden" value="" /> - - The label/check_box pairs can be customized with a block. - - *Carlos Antonio da Silva + Rafael Mendonça França* - -* Add `collection_radio_buttons` form helper, similar to `collection_select`: - Example: - - collection_radio_buttons :post, :author_id, Author.all, :id, :name - # Outputs something like: - <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" /> - <label for="post_author_id_1">D. Heinemeier Hansson</label> - <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" /> - <label for="post_author_id_2">D. Thomas</label> - - The label/radio_button pairs can be customized with a block. - - *Carlos Antonio da Silva + Rafael Mendonça França* - -* `check_box` with `:form` html5 attribute will now replicate the `:form` - attribute to the hidden field as well. *Carlos Antonio da Silva* - -* `label` form helper accepts `for: nil` to not generate the attribute. *Carlos Antonio da Silva* - -* Add `:format` option to `number_to_percentage`. *Rodrigo Flores* - -* Add `config.action_view.logger` to configure logger for Action View. *Rafael Mendonça França* - -* Deprecated `ActionController::Integration` in favour of `ActionDispatch::Integration`. - -* Deprecated `ActionController::IntegrationTest` in favour of `ActionDispatch::IntegrationTest`. - -* Deprecated `ActionController::PerformanceTest` in favour of `ActionDispatch::PerformanceTest`. - -* Deprecated `ActionController::AbstractRequest` in favour of `ActionDispatch::Request`. - -* Deprecated `ActionController::Request` in favour of `ActionDispatch::Request`. - -* Deprecated `ActionController::AbstractResponse` in favour of `ActionDispatch::Response`. - -* Deprecated `ActionController::Response` in favour of `ActionDispatch::Response`. - -* Deprecated `ActionController::Routing` in favour of `ActionDispatch::Routing`. - -* `check_box helper` with `disabled: true` will generate a disabled - hidden field to conform with the HTML convention where disabled fields are - not submitted with the form. This is a behavior change, previously the hidden - tag had a value of the disabled checkbox. *Tadas Tamosauskas* - -* `favicon_link_tag` helper will now use the favicon in app/assets by default. *Lucas Caton* - -* `ActionView::Helpers::TextHelper#highlight` now defaults to the - HTML5 `mark` element. *Brian Cardarella* - -Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/actionpack/CHANGELOG.md) for previous changes. +Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for previous changes. diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index bac994496e..8e7bdf620e 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -309,6 +309,7 @@ module AbstractController RUBY when Proc define_method :_layout_from_proc, &_layout + protected :_layout_from_proc <<-RUBY result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'}) return #{default_behavior} if result.nil? diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 143b3e0cbd..b84c9e78c3 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -36,8 +36,7 @@ module ActionController raise "MiddlewareStack#build requires an app" unless app middlewares.reverse.inject(app) do |a, middleware| - middleware.valid?(action) ? - middleware.build(a) : a + middleware.valid?(action) ? middleware.build(a) : a end end end @@ -57,7 +56,7 @@ module ActionController # And then to route requests to your metal controller, you would add # something like this to <tt>config/routes.rb</tt>: # - # match 'hello', to: HelloController.action(:index) + # get 'hello', to: HelloController.action(:index) # # The +action+ method returns a valid Rack application for the \Rails # router to dispatch to. diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index f1e8714a86..b8afce42c9 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -1,3 +1,6 @@ +require 'active_support/core_ext/hash/except' +require 'active_support/core_ext/hash/slice' + module ActionController # This module provides a method which will redirect browser to use HTTPS # protocol. This will ensure that user's sensitive information will be @@ -14,6 +17,10 @@ module ActionController extend ActiveSupport::Concern include AbstractController::Callbacks + ACTION_OPTIONS = [:only, :except, :if, :unless] + URL_OPTIONS = [:protocol, :host, :domain, :subdomain, :port, :path] + REDIRECT_OPTIONS = [:status, :flash, :alert, :notice] + module ClassMethods # Force the request to this particular controller or specified actions to be # under HTTPS protocol. @@ -29,18 +36,34 @@ module ActionController # end # end # - # ==== Options - # * <tt>host</tt> - Redirect to a different host name - # * <tt>only</tt> - The callback should be run only for this action - # * <tt>except</tt> - The callback should be run for all actions except this action - # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback - # will be called only when it returns a true value. - # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback - # will be called only when it returns a false value. + # ==== URL Options + # You can pass any of the following options to affect the redirect url + # * <tt>host</tt> - Redirect to a different host name + # * <tt>subdomain</tt> - Redirect to a different subdomain + # * <tt>domain</tt> - Redirect to a different domain + # * <tt>port</tt> - Redirect to a non-standard port + # * <tt>path</tt> - Redirect to a different path + # + # ==== Redirect Options + # You can pass any of the following options to affect the redirect status and response + # * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently) + # * <tt>flash</tt> - Set a flash message when redirecting + # * <tt>alert</tt> - Set a alert message when redirecting + # * <tt>notice</tt> - Set a notice message when redirecting + # + # ==== Action Options + # You can pass any of the following options to affect the before_action callback + # * <tt>only</tt> - The callback should be run only for this action + # * <tt>except</tt> - The callback should be run for all actions except this action + # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback + # will be called only when it returns a true value. + # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback + # will be called only when it returns a false value. def force_ssl(options = {}) - host = options.delete(:host) - before_action(options) do - force_ssl_redirect(host) + action_options = options.slice(*ACTION_OPTIONS) + redirect_options = options.except(*ACTION_OPTIONS) + before_action(action_options) do + force_ssl_redirect(redirect_options) end end end @@ -48,14 +71,26 @@ module ActionController # Redirect the existing request to use the HTTPS protocol. # # ==== Parameters - # * <tt>host</tt> - Redirect to a different host name - def force_ssl_redirect(host = nil) + # * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options + # available to the <tt>force_ssl</tt> method. + def force_ssl_redirect(host_or_options = nil) unless request.ssl? - redirect_options = {:protocol => 'https://', :status => :moved_permanently} - redirect_options.merge!(:host => host) if host - redirect_options.merge!(:params => request.query_parameters) + options = { + :protocol => 'https://', + :host => request.host, + :path => request.fullpath, + :status => :moved_permanently + } + + if host_or_options.is_a?(Hash) + options.merge!(host_or_options) + elsif host_or_options + options.merge!(:host => host_or_options) + end + + secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS)) flash.keep if respond_to?(:flash) - redirect_to redirect_options + redirect_to secure_url, options.slice(*REDIRECT_OPTIONS) end end end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 23d70c9ea2..44703221f3 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -230,7 +230,7 @@ module ActionController # params = ActionController::Parameters.new({ # person: { # contact: { - # email: 'none@test.com' + # email: 'none@test.com', # phone: '555-1234' # } # } diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index a35a613158..41a286f0ed 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -455,13 +455,14 @@ module ActionController # # - +action+: The controller action to call. # - +parameters+: The HTTP parameters that you want to pass. This may - # be +nil+, a Hash, or a String that is appropriately encoded + # be +nil+, a hash, or a string that is appropriately encoded # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>). - # - +session+: A Hash of parameters to store in the session. This may be +nil+. - # - +flash+: A Hash of parameters to store in the flash. This may be +nil+. + # - +session+: A hash of parameters to store in the session. This may be +nil+. + # - +flash+: A hash of parameters to store in the flash. This may be +nil+. + # + # You can also simulate POST, PATCH, PUT, DELETE, HEAD, and OPTIONS requests with + # +post+, +patch+, +put+, +delete+, +head+, and +options+. # - # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with - # +#post+, +#patch+, +#put+, +#delete+, and +#head+. # Note that the request method is not verified. The different methods are # available to make the tests more expressive. def get(action, *args) @@ -469,37 +470,37 @@ module ActionController end # Simulate a POST request with the given parameters and set/volley the response. - # See +#get+ for more details. + # See +get+ for more details. def post(action, *args) process(action, "POST", *args) end # Simulate a PATCH request with the given parameters and set/volley the response. - # See +#get+ for more details. + # See +get+ for more details. def patch(action, *args) process(action, "PATCH", *args) end # Simulate a PUT request with the given parameters and set/volley the response. - # See +#get+ for more details. + # See +get+ for more details. def put(action, *args) process(action, "PUT", *args) end # Simulate a DELETE request with the given parameters and set/volley the response. - # See +#get+ for more details. + # See +get+ for more details. def delete(action, *args) process(action, "DELETE", *args) end # Simulate a HEAD request with the given parameters and set/volley the response. - # See +#get+ for more details. + # See +get+ for more details. def head(action, *args) process(action, "HEAD", *args) end # Simulate a OPTIONS request with the given parameters and set/volley the response. - # See +#get+ for more details. + # See +get+ for more details. def options(action, *args) process(action, "OPTIONS", *args) end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 06e936cdb0..60a2cccdc5 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -55,6 +55,7 @@ module ActionDispatch # :nodoc: CONTENT_TYPE = "Content-Type".freeze SET_COOKIE = "Set-Cookie".freeze LOCATION = "Location".freeze + NO_CONTENT_CODES = [204, 304] cattr_accessor(:default_charset) { "utf-8" } cattr_accessor(:default_headers) @@ -289,7 +290,7 @@ module ActionDispatch # :nodoc: header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join) - if [204, 304].include?(@status) + if NO_CONTENT_CODES.include?(@status) header.delete CONTENT_TYPE [status, header, []] else diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index ab5399c8ea..6f5a52c568 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -4,7 +4,9 @@ require 'active_support/core_ext/hash/slice' module ActionDispatch module Http module URL - IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ + IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ + HOST_REGEXP = /(^.*:\/\/)?([^:]+)(?::(\d+$))?/ + PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/ mattr_accessor :tld_length self.tld_length = 1 @@ -28,6 +30,7 @@ module ActionDispatch end def url_for(options = {}) + options = options.dup path = options.delete(:script_name).to_s.chomp("/") path << options.delete(:path).to_s @@ -59,15 +62,20 @@ module ActionDispatch result = "" unless options[:only_path] - protocol = extract_protocol(options) - unless options[:protocol] == false - result << protocol - result << ":" unless result.match(%r{:|//}) + if match = options[:host].match(HOST_REGEXP) + options[:protocol] ||= match[1] unless options[:protocol] == false + options[:host] = match[2] + options[:port] = match[3] unless options.key?(:port) end - result << "//" unless result.match("//") + + options[:protocol] = normalize_protocol(options) + options[:host] = normalize_host(options) + options[:port] = normalize_port(options) + + result << options[:protocol] result << rewrite_authentication(options) - result << host_or_subdomain_and_domain(options) - result << ":#{options.delete(:port)}" if options[:port] + result << options[:host] + result << ":#{options[:port]}" if options[:port] end result end @@ -76,6 +84,10 @@ module ActionDispatch host && IP_HOST_REGEXP !~ host end + def same_host?(options) + (options[:subdomain] == true || !options.key?(:subdomain)) && options[:domain].nil? + end + def rewrite_authentication(options) if options[:user] && options[:password] "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@" @@ -84,29 +96,47 @@ module ActionDispatch end end - # Extracts protocol http:// or https:// from options[:host] - # needs to be called whether the :protocol is being used or not - def extract_protocol(options) - if options[:host] && match = options[:host].match(/(^.*:\/\/)(.*)/) - options[:protocol] ||= match[1] - options[:host] = match[2] + def normalize_protocol(options) + case options[:protocol] + when nil + "http://" + when false, "//" + "//" + when PROTOCOL_REGEXP + "#{$1}://" + else + raise ArgumentError, "Invalid :protocol option: #{options[:protocol].inspect}" end - options[:protocol] || "http" end - def host_or_subdomain_and_domain(options) - return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?) + def normalize_host(options) + return options[:host] if !named_host?(options[:host]) || same_host?(options) tld_length = options[:tld_length] || @@tld_length host = "" - unless options[:subdomain] == false - host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param - host << "." + if options[:subdomain] == true || !options.key?(:subdomain) + host << extract_subdomain(options[:host], tld_length).to_param + elsif options[:subdomain].present? + host << options[:subdomain].to_param end + host << "." unless host.empty? host << (options[:domain] || extract_domain(options[:host], tld_length)) host end + + def normalize_port(options) + return nil if options[:port].nil? || options[:port] == false + + case options[:protocol] + when "//" + nil + when "https://" + options[:port].to_i == 443 ? nil : options[:port] + else + options[:port].to_i == 80 ? nil : options[:port] + end + end end def initialize(env) diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y index a2e1afed32..040f8d5922 100644 --- a/actionpack/lib/action_dispatch/journey/parser.y +++ b/actionpack/lib/action_dispatch/journey/parser.y @@ -36,6 +36,7 @@ rule ; literal : LITERAL { result = Literal.new(val.first) } + ; dot : DOT { result = Dot.new(val.first) } ; diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 6fda085681..50e1853094 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -102,6 +102,10 @@ module ActionDispatch value === request.send(method).to_s when Array value.include?(request.send(method)) + when TrueClass + request.send(method).present? + when FalseClass + request.send(method).blank? else value === request.send(method) end diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 31868b1814..419e665d12 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -38,7 +38,9 @@ module ActionDispatch env['REMOTE_ADDR'] end - def [](k); env[k]; end + def [](k) + env[k] + end end attr_reader :request_class, :formatter diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 7489ce8028..1de3d14530 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -9,9 +9,11 @@ module ActionDispatch 'ActionController::RoutingError' => :not_found, 'AbstractController::ActionNotFound' => :not_found, 'ActionController::MethodNotAllowed' => :method_not_allowed, + 'ActionController::UnknownHttpMethod' => :method_not_allowed, 'ActionController::NotImplemented' => :not_implemented, 'ActionController::UnknownFormat' => :not_acceptable, 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, + 'ActionDispatch::ParamsParser::ParseError' => :bad_request, 'ActionController::BadRequest' => :bad_request, 'ActionController::ParameterMissing' => :bad_request ) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 80054c8a40..c3fd0c18ec 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -299,7 +299,7 @@ module ActionDispatch end end - # Invokes Rack::Mount::Utils.normalize path and ensure that + # Invokes Journey::Router::Utils.normalize_path and ensure that # (:locale) becomes (/:locale) instead of /(:locale). Except # for root cases, where the latter is the correct one. def self.normalize_path(path) @@ -945,6 +945,8 @@ module ActionDispatch VALID_ON_OPTIONS = [:new, :collection, :member] RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns] CANONICAL_ACTIONS = %w(index create new show update destroy) + RESOURCE_METHOD_SCOPES = [:collection, :member, :new] + RESOURCE_SCOPES = [:resource, :resources] class Resource #:nodoc: attr_reader :controller, :path, :options, :param @@ -1499,11 +1501,11 @@ module ActionDispatch end def resource_scope? #:nodoc: - [:resource, :resources].include? @scope[:scope_level] + RESOURCE_SCOPES.include? @scope[:scope_level] end def resource_method_scope? #:nodoc: - [:collection, :member, :new].include? @scope[:scope_level] + RESOURCE_METHOD_SCOPES.include? @scope[:scope_level] end def with_exclusive_scope diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index d48a83e6c6..342b6ec23d 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -170,9 +170,10 @@ module ActionDispatch def call(t, args) if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t) - @options.merge!(t.url_options) if t.respond_to?(:url_options) - @options[:path] = optimized_helper(args) - ActionDispatch::Http::URL.url_for(@options) + options = @options.dup + options.merge!(t.url_options) if t.respond_to?(:url_options) + options[:path] = optimized_helper(args) + ActionDispatch::Http::URL.url_for(options) else super end diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index b5e47d78d1..fd08f392aa 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,7 +1,7 @@ module ActionPack # Returns the version of the currently loaded ActionPack as a Gem::Version def self.version - Gem::Version.new "4.0.0.beta1" + Gem::Version.new "4.1.0.beta" end module VERSION #:nodoc: diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index d3953c26b7..8fb5eb1548 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -808,7 +808,7 @@ module ActionView options[:max_years_allowed] = @options[:max_years_allowed] || 1000 if (options[:end] - options[:start]).abs > options[:max_years_allowed] - raise ArgumentError, "There're too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter" + raise ArgumentError, "There are too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter." end build_options_and_select(:year, val, options) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 7e65ebb4e4..719c9c09b5 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -380,7 +380,7 @@ module ActionView # should produce the desired results. def options_from_collection_for_select(collection, value_method, text_method, selected = nil) options = collection.map do |element| - [value_for_collection(element, text_method), value_for_collection(element, value_method)] + [value_for_collection(element, text_method), value_for_collection(element, value_method), option_html_attributes(element)] end selected, disabled = extract_selected_and_disabled(selected) select_deselect = { diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb index d9c76366f8..91ee2ea8f5 100644 --- a/actionpack/lib/action_view/path_set.rb +++ b/actionpack/lib/action_view/path_set.rb @@ -1,5 +1,11 @@ module ActionView #:nodoc: # = Action View PathSet + # + # This class is used to store and access paths in Action View. A number of + # operations are defined so that you can search among the paths in this + # set and also perform operations on other +PathSet+ objects. + # + # A +LookupContext+ will use a +PathSet+ to store the paths in its context. class PathSet #:nodoc: include Enumerable diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb index 6fb8cbb46c..73c19a0ae2 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -1,4 +1,19 @@ module ActionView + # This class defines the interface for a renderer. Each class that + # subclasses +AbstractRenderer+ is used by the base +Renderer+ class to + # render a specific type of object. + # + # The base +Renderer+ class uses its +render+ method to delegate to the + # renderers. These currently consist of + # + # PartialRenderer - Used for rendering partials + # TemplateRenderer - Used for rendering other types of templates + # StreamingTemplateRenderer - Used for streaming + # + # Whenever the +render+ method is called on the base +Renderer+ class, a new + # renderer object of the correct type is created, and the +render+ method on + # that new object is called in turn. This abstracts the setup and rendering + # into a separate classes for partials and templates. class AbstractRenderer #:nodoc: delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 43a88b0623..821026268a 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -313,6 +313,13 @@ module ActionView private + # Sets up instance variables needed for rendering a partial. This method + # finds the options and details and extracts them. The method also contains + # logic that handles the type of object passed in as the partial. + # + # If +options[:partial]+ is a string, then the +@path+ instance variable is + # set to that string. Otherwise, the +options[:partial]+ object must + # respond to +to_partial_path+ in order to setup the path. def setup(context, options, block) @view = context partial = options[:partial] @@ -413,6 +420,13 @@ module ActionView end end + # Obtains the path to where the object's partial is located. If the object + # responds to +to_partial_path+, then +to_partial_path+ will be called and + # will provide the path. If the object does not respond to +to_partial_path+, + # then an +ArgumentError+ is raised. + # + # If +prefix_partial_path_with_controller_namespace+ is true, then this + # method will prefix the partial paths with a namespace. def partial_path(object = @object) object = object.to_model if object.respond_to?(:to_model) diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb index 30a0c4be70..964b18337e 100644 --- a/actionpack/lib/action_view/renderer/renderer.rb +++ b/actionpack/lib/action_view/renderer/renderer.rb @@ -2,6 +2,12 @@ module ActionView # This is the main entry point for rendering. It basically delegates # to other objects like TemplateRenderer and PartialRenderer which # actually renders the template. + # + # The Renderer will parse the options from the +render+ or +render_body+ + # method and render a partial or a template based on the options. The + # +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all + # the setup and logic necessary to render a view and a new object is created + # each time +render+ is called. class Renderer attr_accessor :lookup_context diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb index 92baad4523..4a05c00f8b 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionpack/test/abstract/layouts_test.rb @@ -8,6 +8,8 @@ module AbstractControllerTests include AbstractController::Rendering include AbstractController::Layouts + abstract! + self.view_paths = [ActionView::FixtureResolver.new( "layouts/hello.erb" => "With String <%= yield %>", "layouts/hello_override.erb" => "With Override <%= yield %>", @@ -251,6 +253,10 @@ module AbstractControllerTests assert_equal "Hello nil!", controller.response_body end + test "when layout is specified as a proc, do not leak any methods into controller's action_methods" do + assert_equal Set.new(['index']), WithProc.action_methods + end + test "when layout is specified as a proc, call it and use the layout returned" do controller = WithProc.new controller.process(:index) diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb index 6758668b7a..3655b90e32 100644 --- a/actionpack/test/controller/force_ssl_test.rb +++ b/actionpack/test/controller/force_ssl_test.rb @@ -14,8 +14,42 @@ class ForceSSLControllerLevel < ForceSSLController force_ssl end -class ForceSSLCustomDomain < ForceSSLController - force_ssl :host => "secure.test.host" +class ForceSSLCustomOptions < ForceSSLController + force_ssl :host => "secure.example.com", :only => :redirect_host + force_ssl :port => 8443, :only => :redirect_port + force_ssl :subdomain => 'secure', :only => :redirect_subdomain + force_ssl :domain => 'secure.com', :only => :redirect_domain + force_ssl :path => '/foo', :only => :redirect_path + force_ssl :status => :found, :only => :redirect_status + force_ssl :flash => { :message => 'Foo, Bar!' }, :only => :redirect_flash + force_ssl :alert => 'Foo, Bar!', :only => :redirect_alert + force_ssl :notice => 'Foo, Bar!', :only => :redirect_notice + + def force_ssl_action + render :text => action_name + end + + alias_method :redirect_host, :force_ssl_action + alias_method :redirect_port, :force_ssl_action + alias_method :redirect_subdomain, :force_ssl_action + alias_method :redirect_domain, :force_ssl_action + alias_method :redirect_path, :force_ssl_action + alias_method :redirect_status, :force_ssl_action + alias_method :redirect_flash, :force_ssl_action + alias_method :redirect_alert, :force_ssl_action + alias_method :redirect_notice, :force_ssl_action + + def use_flash + render :text => flash[:message] + end + + def use_alert + render :text => flash[:alert] + end + + def use_notice + render :text => flash[:notice] + end end class ForceSSLOnlyAction < ForceSSLController @@ -80,19 +114,77 @@ class ForceSSLControllerLevelTest < ActionController::TestCase end end -class ForceSSLCustomDomainTest < ActionController::TestCase - tests ForceSSLCustomDomain +class ForceSSLCustomOptionsTest < ActionController::TestCase + tests ForceSSLCustomOptions - def test_banana_redirects_to_https_with_custom_host - get :banana + def setup + @request.env['HTTP_HOST'] = 'www.example.com:80' + end + + def test_redirect_to_custom_host + get :redirect_host assert_response 301 - assert_equal "https://secure.test.host/force_ssl_custom_domain/banana", redirect_to_url + assert_equal "https://secure.example.com/force_ssl_custom_options/redirect_host", redirect_to_url end - def test_cheeseburger_redirects_to_https_with_custom_host - get :cheeseburger + def test_redirect_to_custom_port + get :redirect_port + assert_response 301 + assert_equal "https://www.example.com:8443/force_ssl_custom_options/redirect_port", redirect_to_url + end + + def test_redirect_to_custom_subdomain + get :redirect_subdomain assert_response 301 - assert_equal "https://secure.test.host/force_ssl_custom_domain/cheeseburger", redirect_to_url + assert_equal "https://secure.example.com/force_ssl_custom_options/redirect_subdomain", redirect_to_url + end + + def test_redirect_to_custom_domain + get :redirect_domain + assert_response 301 + assert_equal "https://www.secure.com/force_ssl_custom_options/redirect_domain", redirect_to_url + end + + def test_redirect_to_custom_path + get :redirect_path + assert_response 301 + assert_equal "https://www.example.com/foo", redirect_to_url + end + + def test_redirect_to_custom_status + get :redirect_status + assert_response 302 + assert_equal "https://www.example.com/force_ssl_custom_options/redirect_status", redirect_to_url + end + + def test_redirect_to_custom_flash + get :redirect_flash + assert_response 301 + assert_equal "https://www.example.com/force_ssl_custom_options/redirect_flash", redirect_to_url + + get :use_flash + assert_response 200 + assert_equal "Foo, Bar!", @response.body + end + + def test_redirect_to_custom_alert + get :redirect_alert + assert_response 301 + assert_equal "https://www.example.com/force_ssl_custom_options/redirect_alert", redirect_to_url + + get :use_alert + assert_response 200 + assert_equal "Foo, Bar!", @response.body + end + + def test_redirect_to_custom_notice + get :redirect_notice + assert_response 301 + assert_equal "https://www.example.com/force_ssl_custom_options/redirect_notice", redirect_to_url + + get :use_notice + assert_response 200 + assert_equal "Foo, Bar!", @response.body end end @@ -149,16 +241,79 @@ class ForceSSLFlashTest < ActionController::TestCase assert_response 302 assert_equal "http://test.host/force_ssl_flash/cheeseburger", redirect_to_url + # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists + @request.env.delete('PATH_INFO') + get :cheeseburger assert_response 301 assert_equal "https://test.host/force_ssl_flash/cheeseburger", redirect_to_url + # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists + @request.env.delete('PATH_INFO') + get :use_flash assert_equal "hello", assigns["flash_copy"]["that"] assert_equal "hello", assigns["flashy"] end end +class ForceSSLDuplicateRoutesTest < ActionController::TestCase + tests ForceSSLControllerLevel + + def test_force_ssl_redirects_to_same_path + with_routing do |set| + set.draw do + get '/foo', :to => 'force_ssl_controller_level#banana' + get '/bar', :to => 'force_ssl_controller_level#banana' + end + + @request.env['PATH_INFO'] = '/bar' + + get :banana + assert_response 301 + assert_equal 'https://test.host/bar', redirect_to_url + end + end +end + +class ForceSSLFormatTest < ActionController::TestCase + tests ForceSSLControllerLevel + + def test_force_ssl_redirects_to_same_format + with_routing do |set| + set.draw do + get '/foo', :to => 'force_ssl_controller_level#banana' + end + + get :banana, :format => :json + assert_response 301 + assert_equal 'https://test.host/foo.json', redirect_to_url + end + end +end + +class ForceSSLOptionalSegmentsTest < ActionController::TestCase + tests ForceSSLControllerLevel + + def test_force_ssl_redirects_to_same_format + with_routing do |set| + set.draw do + scope '(:locale)' do + defaults :locale => 'en' do + get '/foo', :to => 'force_ssl_controller_level#banana' + end + end + end + + @request.env['PATH_INFO'] = '/en/foo' + get :banana, :locale => 'en' + assert_equal 'en', @controller.params[:locale] + assert_response 301 + assert_equal 'https://test.host/en/foo', redirect_to_url + end + end +end + class RedirectToSSLTest < ActionController::TestCase tests RedirectToSSL def test_banana_redirects_to_https_if_not_https diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index ba24e7fac5..088ad73f2f 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -89,6 +89,13 @@ module AbstractController ) end + def test_subdomain_may_be_removed_with_blank_string + W.default_url_options[:host] = 'api.basecamphq.com' + assert_equal('http://basecamphq.com/c/a/i', + W.new.url_for(:subdomain => '', :controller => 'c', :action => 'a', :id => 'i') + ) + end + def test_multiple_subdomains_may_be_removed W.default_url_options[:host] = 'mobile.www.api.basecamphq.com' assert_equal('http://basecamphq.com/c/a/i', diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 6035f0361e..ff0baccd76 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -29,6 +29,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest raise RuntimeError when "/method_not_allowed" raise ActionController::MethodNotAllowed + when "/unknown_http_method" + raise ActionController::UnknownHttpMethod when "/not_implemented" raise ActionController::NotImplemented when "/unprocessable_entity" @@ -113,6 +115,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_response 405 assert_match(/ActionController::MethodNotAllowed/, body) + get "/unknown_http_method", {}, {'action_dispatch.show_exceptions' => true} + assert_response 405 + assert_match(/ActionController::UnknownHttpMethod/, body) + get "/bad_request", {}, {'action_dispatch.show_exceptions' => true} assert_response 400 assert_match(/ActionController::BadRequest/, body) diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 29703dd5b1..5b42ca0f21 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1124,6 +1124,35 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal "Not Found", @response.body end + def test_resources_with_format_false_from_scope + draw do + scope format: false do + resources :posts + resource :user + end + end + + get "/posts" + assert_response :success + assert_equal "posts#index", @response.body + assert_equal "/posts", posts_path + + get "/posts.html" + assert_response :not_found + assert_equal "Not Found", @response.body + assert_equal "/posts?format=html", posts_path(format: "html") + + get "/user" + assert_response :success + assert_equal "users#show", @response.body + assert_equal "/user", user_path + + get "/user.html" + assert_response :not_found + assert_equal "Not Found", @response.body + assert_equal "/user?format=html", user_path(format: "html") + end + def test_index draw do get '/info' => 'projects#info', :as => 'info' @@ -3322,6 +3351,10 @@ class TestUrlConstraints < ActionDispatch::IntegrationTest end get '/' => ok, :as => :alternate_root, :constraints => { :port => 8080 } + + get '/search' => ok, :constraints => { :subdomain => false } + + get '/logs' => ok, :constraints => { :subdomain => true } end end @@ -3348,6 +3381,24 @@ class TestUrlConstraints < ActionDispatch::IntegrationTest get 'http://www.example.com:8080/' assert_response :success end + + test "false constraint expressions check for absence of values" do + get 'http://example.com/search' + assert_response :success + assert_equal 'http://example.com/search', search_url + + get 'http://api.example.com/search' + assert_response :not_found + end + + test "true constraint expressions check for presence of values" do + get 'http://api.example.com/logs' + assert_response :success + assert_equal 'http://api.example.com/logs', logs_url + + get 'http://example.com/logs' + assert_response :not_found + end end class TestInvalidUrls < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index 45f8fc11b3..38bd234f37 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -8,8 +8,12 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest case req.path when "/not_found" raise AbstractController::ActionNotFound + when "/bad_params" + raise ActionDispatch::ParamsParser::ParseError.new("", StandardError.new) when "/method_not_allowed" raise ActionController::MethodNotAllowed + when "/unknown_http_method" + raise ActionController::UnknownHttpMethod when "/not_found_original_exception" raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new) else @@ -33,6 +37,10 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest get "/", {}, {'action_dispatch.show_exceptions' => true} assert_response 500 assert_equal "500 error fixture\n", body + + get "/bad_params", {}, {'action_dispatch.show_exceptions' => true} + assert_response 400 + assert_equal "400 error fixture\n", body get "/not_found", {}, {'action_dispatch.show_exceptions' => true} assert_response 404 @@ -41,6 +49,10 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true} assert_response 405 assert_equal "", body + + get "/unknown_http_method", {}, {'action_dispatch.show_exceptions' => true} + assert_response 405 + assert_equal "", body end test "localize rescue error page" do diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb index 4123529092..f919592d24 100644 --- a/actionpack/test/dispatch/url_generation_test.rb +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -56,6 +56,47 @@ module TestUrlGeneration test "formatting host when protocol is present" do assert_equal "http://www.example.com/foo", foo_url(host: "httpz://www.example.com", protocol: "http://") end + + test "default ports are removed from the host" do + assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:80", protocol: "http://") + assert_equal "https://www.example.com/foo", foo_url(host: "www.example.com:443", protocol: "https://") + end + + test "port is extracted from the host" do + assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8080", protocol: "http://") + end + + test "port option overides the host" do + assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: 8080) + end + + test "port option disables the host when set to nil" do + assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: nil) + end + + test "port option disables the host when set to false" do + assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: false) + end + + test "keep subdomain when key is true" do + assert_equal "http://www.example.com/foo", foo_url(subdomain: true) + end + + test "keep subdomain when key is missing" do + assert_equal "http://www.example.com/foo", foo_url + end + + test "omit subdomain when key is nil" do + assert_equal "http://example.com/foo", foo_url(subdomain: nil) + end + + test "omit subdomain when key is false" do + assert_equal "http://example.com/foo", foo_url(subdomain: false) + end + + test "omit subdomain when key is blank" do + assert_equal "http://example.com/foo", foo_url(subdomain: "") + end end end diff --git a/actionpack/test/fixtures/public/400.html b/actionpack/test/fixtures/public/400.html new file mode 100644 index 0000000000..03be6bedaf --- /dev/null +++ b/actionpack/test/fixtures/public/400.html @@ -0,0 +1 @@ +400 error fixture diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 94ae8549f7..1715902927 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -100,6 +100,13 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_collection_options_with_element_attributes + assert_dom_equal( + "<option value=\"USA\" class=\"bold\">USA</option>", + options_from_collection_for_select([[ "USA", "USA", { :class => 'bold' } ]], :first, :second) + ) + end + def test_string_options_for_select options = "<option value=\"Denmark\">Denmark</option><option value=\"USA\">USA</option><option value=\"Sweden\">Sweden</option>" assert_dom_equal( diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 2237d747be..81f3391fcd 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -29,14 +29,6 @@ module RenderTestCases assert_equal "Hello world!", @view.render(:file => "test/hello_world") end - def test_render_file_not_using_full_path - assert_equal "Hello world!", @view.render(:file => "test/hello_world") - end - - def test_render_file_without_specific_extension - assert_equal "Hello world!", @view.render(:file => "test/hello_world") - end - # Test if :formats, :locale etc. options are passed correctly to the resolvers. def test_render_file_with_format assert_match "<h1>No Comment</h1>", @view.render(:file => "comments/empty", :formats => [:html]) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index a1f3d081db..eb54b58888 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,213 +1,3 @@ -## Rails 4.0.0 (unreleased) ## +* No changes. -* Add `ActiveModel::Errors#full_messages_for`, to return all the error messages - for a given attribute. - - Example: - - class Person - include ActiveModel::Validations - - attr_reader :name, :email - validates_presence_of :name, :email - end - - person = Person.new - person.valid? # => false - person.errors.full_messages_for(:name) # => ["Name can't be blank"] - - *Volodymyr Shatsky* - -* Added a method so that validations can be easily cleared on a model. - For example: - - class Person - include ActiveModel::Validations - - validates_uniqueness_of :first_name - validate :cannot_be_robot - - def cannot_be_robot - errors.add(:base, 'A person cannot be a robot') if person_is_robot - end - end - - Now, if someone runs `Person.clear_validators!`, then the following occurs: - - Person.validators # => [] - Person._validate_callbacks.empty? # => true - - *John Wang* - -* `has_secure_password` does not fail the confirmation validation - when assigning empty String to `password` and `password_confirmation`. - Fixes #9535. - - Example: - - # Given User has_secure_password. - @user.password = "" - @user.password_confirmation = "" - @user.valid?(:update) # used to be false - - *Yves Senn* - -* `validates_confirmation_of` does not override writer methods for - the confirmation attribute if no reader is defined. - - Example: - - class Blog - def title=(new_title) - @title = new_title.downcase - end - - # previously this would override the setter above. - validates_confirmation_of :title - end - - *Yves Senn* - -## Rails 4.0.0.beta1 (February 25, 2013) ## - -* Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the - absence of attributes. - - class Person - include ActiveModel::Validations - - attr_accessor :first_name - validates_absence_of :first_name - end - - person = Person.new - person.first_name = "John" - person.valid? - # => false - person.errors.messages - # => {:first_name=>["must be blank"]} - - *Roberto Vasquez Angel* - -* `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!`. - - *Renato Mascarenhas* - -* Observers was extracted from Active Model as `rails-observers` gem. - - *Rafael Mendonça França* - -* Specify type of singular association during serialization. - - *Steve Klabnik* - -* Fixed length validator to correctly handle `nil`. Fixes #7180. - - *Michal Zima* - -* Removed dispensable `require` statements. Make sure to require `active_model` before requiring - individual parts of the framework. - - *Yves Senn* - -* Use BCrypt's `MIN_COST` in the test environment for speedier tests when using `has_secure_password`. - - *Brian Cardarella + Jeremy Kemper + Trevor Turk* - -* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to - protect attributes from mass assignment when non-permitted attributes are passed. - - *DHH + Guillermo Iguaran* - -* `ActiveModel::MassAssignmentSecurity` has been extracted from Active Model and the - `protected_attributes` gem should be added to Gemfile in order to use - `attr_accessible` and `attr_protected` macros in your models. - - *Guillermo Iguaran* - -* Due to a change in builder, `nil` and empty strings now generate - closed tags, so instead of this: - - <pseudonyms nil=\"true\"></pseudonyms> - - it generates this: - - <pseudonyms nil=\"true\"/> - - *Carlos Antonio da Silva* - -* Inclusion/exclusion validators accept a method name passed as a symbol to the - `:in` option. - - This allows to use dynamic inclusion/exclusion values using methods, besides - the current lambda/proc support. - - *Gabriel Sobrinho* - -* `ActiveModel::Validation#validates` ability to pass custom exception to the - `:strict` option. - - *Bogdan Gusiev* - -* Changed `ActiveModel::Serializers::Xml::Serializer#add_associations` to by default - propagate `:skip_types, :dasherize, :camelize` keys to included associations. - It can be overridden on each association by explicitly specifying the option on one - or more associations - - *Anthony Alberto* - -* Changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to false. - Now, AM Serializers and AR objects have the same default behaviour. Fixes #6578. - - class User < ActiveRecord::Base; end - - class Person - include ActiveModel::Model - include ActiveModel::AttributeMethods - include ActiveModel::Serializers::JSON - - attr_accessor :name, :age - - def attributes - instance_values - end - end - - user.as_json - => {"id"=>1, "name"=>"Konata Izumi", "age"=>16, "awesome"=>true} - # root is not included - - person.as_json - => {"name"=>"Francesco", "age"=>22} - # root is not included - - *Francesco Rodriguez* - -* Passing false hash values to `validates` will no longer enable the corresponding validators. - - *Steve Purcell* - -* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute`. - - *Brian Cardarella* - -* Added `ActiveModel::Model`, a mixin to make Ruby objects work with AP out of box. - - *Guillermo Iguaran* - -* `AM::Errors#to_json`: support `:full_messages` parameter. - - *Bogdan Gusiev* - -* Trim down Active Model API by removing `valid?` and `errors.full_messages`. - - *José Valim* - -* When `^` or `$` are used in the regular expression provided to `validates_format_of` - and the `:multiline` option is not set to true, an exception will be raised. This is - to prevent security vulnerabilities when using `validates_format_of`. The problem is - described in detail in the Rails security guide. - - *Jan Berdajs + Egor Homakov* - -Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activemodel/CHANGELOG.md) for previous changes. +Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 6d11c0fbdc..5db898b33a 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -12,19 +12,21 @@ module ActiveModel # # => ActiveModel::MissingAttributeError: missing attribute: user_id class MissingAttributeError < NoMethodError end + # == Active \Model Attribute Methods # # <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and - # suffixes to your methods as well as handling the creation of Active Record - # like class methods such as +table_name+. + # suffixes to your methods as well as handling the creation of + # <tt>ActiveRecord::Base</tt>-like class methods such as +table_name+. # - # The requirements to implement ActiveModel::AttributeMethods are to: + # The requirements to implement <tt>ActiveModel::AttributeMethods</tt> are to: # - # * <tt>include ActiveModel::AttributeMethods</tt> in your object. - # * Call each Attribute Method module method you want to add, such as - # +attribute_method_suffix+ or +attribute_method_prefix+. + # * <tt>include ActiveModel::AttributeMethods</tt> in your class. + # * Call each of its method you want to add, such as +attribute_method_suffix+ + # or +attribute_method_prefix+. # * Call +define_attribute_methods+ after the other methods are called. # * Define the various generic +_attribute+ methods that you have declared. + # * Define an +attributes+ method, see below. # # A minimal implementation could be: # @@ -38,6 +40,10 @@ module ActiveModel # # attr_accessor :name # + # def attributes + # {'name' => @name} + # end + # # private # # def attribute_contrived?(attr) @@ -53,10 +59,10 @@ module ActiveModel # end # end # - # Note that whenever you include ActiveModel::AttributeMethods in your class, - # it requires you to implement an +attributes+ method which returns a hash - # with each attribute name in your model as hash key and the attribute value as - # hash value. + # Note that whenever you include <tt>ActiveModel::AttributeMethods</tt> in + # your class, it requires you to implement an +attributes+ method which + # returns a hash with each attribute name in your model as hash key and the + # attribute value as hash value. # # Hash keys must be strings. module AttributeMethods @@ -179,7 +185,6 @@ module ActiveModel undefine_attribute_methods end - # Allows you to make aliases for attributes. # # class Person @@ -413,17 +418,16 @@ module ActiveModel end end - # Allows access to the object attributes, which are held in the - # <tt>@attributes</tt> hash, as though they were first-class methods. So a - # Person class with a name attribute can use Person#name and Person#name= - # and never directly use the attributes hash -- except for multiple assigns - # with ActiveRecord#attributes=. A Milestone class can also ask - # Milestone#completed? to test that the completed attribute is not +nil+ - # or 0. + # Allows access to the object attributes, which are held in the hash + # returned by <tt>attributes</tt>, as though they were first-class + # methods. So a +Person+ class with a +name+ attribute can for example use + # <tt>Person#name</tt> and <tt>Person#name=</tt> and never directly use + # the attributes hash -- except for multiple assigns with + # <tt>ActiveRecord::Base#attributes=</tt>. # - # It's also possible to instantiate related objects, so a Client class - # belonging to the clients table with a +master_id+ foreign key can - # instantiate master through Client#master. + # It's also possible to instantiate related objects, so a <tt>Client</tt> + # class belonging to the +clients+ table with a +master_id+ foreign key + # can instantiate master through <tt>Client#master</tt>. def method_missing(method, *args, &block) if respond_to_without_attributes?(method, true) super @@ -433,17 +437,17 @@ module ActiveModel end end - # 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 - # customize the behavior. + # +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 tell +attribute_missing+ to dispatch the + # attribute. This method can be overloaded to customize the behavior. def attribute_missing(match, *args, &block) __send__(match.target, match.attr_name, *args, &block) end - # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>, - # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt> - # which will all return +true+. + # A +Person+ instance with a +name+ attribute can ask + # <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>, + # and <tt>person.respond_to?(:name?)</tt> which will all return +true+. alias :respond_to_without_attributes? :respond_to? def respond_to?(method, include_private_methods = false) if super diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index 35c4b30999..86340bba37 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -1,7 +1,7 @@ module ActiveModel # Returns the version of the currently loaded ActiveModel as a Gem::Version def self.version - Gem::Version.new "4.0.0.beta1" + Gem::Version.new "4.1.0.beta" end module VERSION #:nodoc: diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb index 0015b3c196..6cd0f4ed4d 100644 --- a/activemodel/test/cases/validations/callbacks_test.rb +++ b/activemodel/test/cases/validations/callbacks_test.rb @@ -40,8 +40,29 @@ class DogWithMissingName < Dog validates_presence_of :name end +class DogValidatorWithIfCondition < Dog + before_validation :set_before_validation_marker1, if: -> { true } + before_validation :set_before_validation_marker2, if: -> { false } + + after_validation :set_after_validation_marker1, if: -> { true } + after_validation :set_after_validation_marker2, if: -> { false } + + def set_before_validation_marker1; self.history << 'before_validation_marker1'; end + def set_before_validation_marker2; self.history << 'before_validation_marker2' ; end + + def set_after_validation_marker1; self.history << 'after_validation_marker1'; end + def set_after_validation_marker2; self.history << 'after_validation_marker2' ; end +end + + class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase + def test_if_condition_is_respected_for_before_validation + d = DogValidatorWithIfCondition.new + d.valid? + assert_equal ["before_validation_marker1", "after_validation_marker1"], d.history + end + def test_before_validation_and_after_validation_callbacks_should_be_called d = DogWithMethodCallbacks.new d.valid? diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb index 15a49e38dd..b795861f95 100644 --- a/activemodel/test/cases/validations/validations_context_test.rb +++ b/activemodel/test/cases/validations/validations_context_test.rb @@ -18,10 +18,10 @@ class ValidationsContextTest < ActiveModel::TestCase end end - test "with a class that adds errors on update and validating a new model with no arguments" do + test "with a class that adds errors on create and validating a new model with no arguments" do Topic.validates_with(ValidatorThatAddsErrors, :on => :create) topic = Topic.new - assert topic.valid?, "Validation doesn't run on create if 'on' is set to update" + assert topic.valid?, "Validation doesn't run on valid? if 'on' is set to create" end test "with a class that adds errors on update and validating a new model" do diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 062c548f20..9f10a31512 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,2003 +1,15 @@ -## Rails 4.0.0 (unreleased) ## +* Mute `psql` output when running rake db:schema:load. -* Added Statement Cache to allow the caching of a single statement. The cache works by - duping the relation returned from yielding a statement, which allows skipping the AST - building phase for following executes. The cache returns results in array format. + *Godfrey Chan* - Example: +* Trigger a save on `has_one association=(associate)` when the associate contents have changed. - cache = ActiveRecord::StatementCache.new do - Book.where(name: "my book").limit(100) - end + Fix #8856. - books = cache.execute + *Chris Thompson* - The solution attempts to get closer to the speed of `find_by_sql` but still maintaining - the expressiveness of the Active Record queries. - - *Olli Rissanen* - -* Preserve context while merging relations with join information. - - class Comment < ActiveRecord::Base - belongs_to :post - end - - class Author < ActiveRecord::Base - has_many :posts - end - - class Post < ActiveRecord::Base - belongs_to :author - has_many :comments - end - - `Comment.joins(:post).merge(Post.joins(:author).merge(Author.where(:name => "Joe Blogs"))).all` - would fail with - `ActiveRecord::ConfigurationError: Association named 'author' was not found on Comment`. - - It is failing because `all` is being called on relation which looks like this after all - the merging: `{:joins=>[:post, :author], :where=>[#<Arel::Nodes::Equality: ....}`. In this - relation all the context that `Post` was joined with `Author` is lost and hence the error - that `author` was not found on `Comment`. - - Ths solution is to build JoinAssociation when two relations with join information are being - merged. And later while building the arel use the previously built `JoinAssociation` record - in `JoinDependency#graft` to build the right from clause. - Fixes #3002. - - *Jared Armstrong and Neeraj Singh* - -* `default_scopes?` is deprecated. Check for `default_scopes.empty?` instead. - - *Agis Anastasopoulos* - -* Default values for PostgreSQL bigint types now get parsed and dumped to the - schema correctly. - - *Erik Peterson* - -* Fix associations with `:inverse_of` option when building association - with a block. Inside the block the parent object was different then - after the block. - - Example: - - parent.association.build do |child| - child.parent.equal?(parent) # false - end - - # vs - - child = parent.association.build - child.parent.equal?(parent) # true - - *Michal Cichra* - -* `has_many` using `:through` now obeys the order clause mentioned in - through association. - Fixes #10016. - - *Neeraj Singh* - -* `belongs_to :touch` behavior now touches old association when - transitioning to new association. - - class Passenger < ActiveRecord::Base - belongs_to :car, touch: true - end - - car_1 = Car.create - car_2 = Car.create - - passenger = Passenger.create car: car_1 - - passenger.car = car_2 - passenger.save - - Previously only car_2 would be touched. Now both car_1 and car_2 - will be touched. - - *Adam Gamble* - -* Extract and deprecate Firebird / Sqlserver / Oracle database tasks, because - These tasks should be supported by 3rd-party adapter. - - *kennyj* - -* Allow `ActiveRecord::Base.connection_handler` to have thread affinity and be - settable, this effectively allows Active Record to be used in a multi threaded - setup with multiple connections to multiple dbs. - - *Sam Saffron* - -* `rename_column` preserves `auto_increment` in MySQL migrations. - Fixes #3493. - - *Vipul A M* - -* PostgreSQL geometric type point is now supported by Active Record. Fixes #7324. - - *Martin Schuerrer* - -* Add support for concurrent indexing in PostgreSQL adapter via the - `algorithm: :concurrently` option. - - add_index(:people, :last_name, algorithm: :concurrently) - - Also add support for MySQL index algorithms (`COPY`, `INPLACE`, - `DEFAULT`) via the `:algorithm` option. - - add_index(:people, :last_name, algorithm: :copy) # or :inplace/:default - - *Dan McClain* - -* Add support for fulltext and spatial indexes on MySQL tables with MyISAM database - engine via the `type: 'FULLTEXT'` / `type: 'SPATIAL'` option. - - add_index(:people, :last_name, type: 'FULLTEXT') - add_index(:people, :last_name, type: 'SPATIAL') - - *Ken Mazaika* - -* Add an `add_index` override in PostgreSQL adapter and MySQL adapter - to allow custom index type support. - Fixes #6101. - - add_index(:wikis, :body, :using => 'gin') - - *Stefan Huber* and *Doabit* - -* After extraction of mass-assignment attributes (which protects [id, type] - by default) we can pass id to `update_attributes` and it will update - another record because id will be used in where statement. We never have - to change id in where statement because we try to set/replace fields for - already loaded record but we have to try to set new id for that record. - - *Dmitry Vorotilin* - -* Models with multiple counter cache associations now update correctly on destroy. - See #7706. - - *Ian Young* - -* If `:inverse_of` is true on an association, then when one calls `find()` on - the association, Active Record will first look through the in-memory objects - in the association for a particular id. Then, it will go to the DB if it - is not found. This is accomplished by calling `find_by_scan` in - collection associations whenever `options[:inverse_of]` is not nil. - Fixes #9470. - - *John Wang* - -* `rake db:create` does not change permissions of the MySQL root user. - Fixes #8079. - - *Yves Senn* - -* The length of the `version` column in the `schema_migrations` table - created by the `mysql2` adapter is 191 if the encoding is "utf8mb4". - - The "utf8" encoding in MySQL has support for a maximum of 3 bytes per character, - and only contains characters from the BMP. The recently added - [utf8mb4](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html) - encoding extends the support to four bytes. As of this writing, said encoding - is supported in the betas of the `mysql2` gem. - - Setting the encoding to "utf8mb4" has - [a few implications](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-upgrading.html). - This change addresses the max length for indexes, which is 191 instead of 255. - - *Xavier Noria* - -* Counter caches on associations will now stay valid when attributes are - updated (not just when records are created or destroyed), for example, - when calling `update_attributes`. The following code now works: - - class Comment < ActiveRecord::Base - belongs_to :post, counter_cache: true - end - - class Post < ActiveRecord::Base - has_many :comments - end - - post = Post.create - comment = Comment.create - - post.comments << comment - post.save.reload.comments_count # => 1 - comment.update_attributes(post_id: nil) - - post.save.reload.comments_count # => 0 - - Updating the id of a `belongs_to` object with the id of a new object will - also keep the count accurate. - - *John Wang* - -* Referencing join tables implicitly was deprecated. There is a - possibility that these deprecation warnings are shown even if you - don't make use of that feature. You can now disable the feature entirely. - Fixes #9712. - - Example: - - # in your configuration - config.active_record.disable_implicit_join_references = true - - # or directly - ActiveRecord::Base.disable_implicit_join_references = true - - *Yves Senn* - -* The `:distinct` option for `Relation#count` is deprecated. You - should use `Relation#distinct` instead. - - Example: - - # Before - Post.select(:author_name).count(distinct: true) - - # After - Post.select(:author_name).distinct.count - - *Yves Senn* - -* Rename `Relation#uniq` to `Relation#distinct`. `#uniq` is still - available as an alias but we encourage to use `#distinct` instead. - Also `Relation#uniq_value` is aliased to `Relation#distinct_value`, - this is a temporary solution and you should migrate to `distinct_value`. - - *Yves Senn* - -* Fix quoting for sqlite migrations using `copy_table_contents` with binary - columns. - - These would fail with "SQLite3::SQLException: unrecognized token" because - the column was not being passed to `quote` so the data was not quoted - correctly. - - *Matthew M. Boedicker* - -* Promotes `change_column_null` to the migrations API. This macro sets/removes - `NOT NULL` constraints, and accepts an optional argument to replace existing - `NULL`s if needed. The adapters for SQLite, MySQL, PostgreSQL, and (at least) - Oracle, already implement this method. - - *Xavier Noria* - -* Uniqueness validation allows you to pass `:conditions` to limit - the constraint lookup. - - Example: - - validates_uniqueness_of :title, conditions: -> { where('approved = ?', true) } - - *Mattias Pfeiffer + Yves Senn* - -* `connection` is deprecated as an instance method. - This allows end-users to have a `connection` method on their models - without clashing with Active Record internals. - - *Ben Moss* - -* When copying migrations, preserve their magic comments and content encoding. - - *OZAWA Sakuro* - -* Fix `subclass_from_attrs` when `eager_load` is false. It cannot find - subclass because all classes are loaded automatically when it needs. - - *Dmitry Vorotilin* - -* When `:name` option is provided to `remove_index`, use it if there is no - index by the conventional name. - - For example, previously if an index was removed like so - `remove_index :values, column: :value, name: 'a_different_name'` - the generated SQL would not contain the specified index name, - and hence the migration would fail. - Fixes #8858. - - *Ezekiel Smithburg* - -* Created block to by-pass the prepared statement bindings. - This will allow to compose fragments of large SQL statements to - avoid multiple round-trips between Ruby and the DB. - - Example: - - sql = Post.connection.unprepared_statement do - Post.first.comments.to_sql - end - - *Cédric Fabianski* - -* Change the semantics of combining scopes to be the same as combining - class methods which return scopes. For example: - - class User < ActiveRecord::Base - scope :active, -> { where state: 'active' } - scope :inactive, -> { where state: 'inactive' } - end - - class Post < ActiveRecord::Base - def self.active - where state: 'active' - end - - def self.inactive - where state: 'inactive' - end - end - - ### BEFORE ### - - User.where(state: 'active').where(state: 'inactive') - # => SELECT * FROM users WHERE state = 'active' AND state = 'inactive' - - User.active.inactive - # => SELECT * FROM users WHERE state = 'inactive' - - Post.active.inactive - # => SELECT * FROM posts WHERE state = 'active' AND state = 'inactive' - - ### AFTER ### - - User.active.inactive - # => SELECT * FROM posts WHERE state = 'active' AND state = 'inactive' - - Before this change, invoking a scope would merge it into the current - scope and return the result. `Relation#merge` applies "last where - wins" logic to de-duplicate the conditions, but this lead to - confusing and inconsistent behaviour. This fixes that. - - If you really do want the "last where wins" logic, you can opt-in to - it like so: - - User.active.merge(User.inactive) - - Fixes #7365. - - *Neeraj Singh* and *Jon Leighton* - -* Expand `#cache_key` to consult all relevant updated timestamps. - - Previously only `updated_at` column was checked, now it will - consult other columns that received updated timestamps on save, - such as `updated_on`. When multiple columns are present it will - use the most recent timestamp. - Fixes #9033. - - *Brendon Murphy* - -* Throw `NotImplementedError` when trying to instantiate `ActiveRecord::Base` or an abstract class. - - *Aaron Weiner* - -* Warn when `rake db:structure:dump` with a MySQL database and - `mysqldump` is not in the PATH or fails. - Fixes #9518. - - *Yves Senn* - -* Remove `connection#structure_dump`, which is no longer used. *Yves Senn* - -* Make it possible to execute migrations without a transaction even - if the database adapter supports DDL transactions. - Fixes #9483. - - Example: - - class ChangeEnum < ActiveRecord::Migration - disable_ddl_transaction! - - def up - execute "ALTER TYPE model_size ADD VALUE 'new_value'" - end - end - - *Yves Senn* - -* Assigning "0.0" to a nullable numeric column does not make it dirty. - Fixes #9034. - - Example: - - product = Product.create price: 0.0 - product.price = '0.0' - product.changed? # => false (this used to return true) - product.changes # => {} (this used to return { price: [0.0, 0.0] }) - - *Yves Senn* - -* Added functionality to unscope relations in a relations chain. For - instance, if you are passed in a chain of relations as follows: - - User.where(name: "John").order('id DESC') - - but you want to get rid of order, then this feature allows you to do: - - User.where(name: "John").order('id DESC').unscope(:order) - == User.where(name: "John") - - The .unscope() function is more general than the .except() method because - .except() only works on the relation it is acting on. However, .unscope() - works for any relation in the entire relation chain. - - *John Wang* - -* PostgreSQL timestamp with time zone (timestamptz) datatype now returns a - ActiveSupport::TimeWithZone instance instead of a string - - *Troy Kruthoff* - -* The `#append` method for collection associations behaves like`<<`. - `#prepend` is not defined and `<<` or `#append` should be used. - Fixes #7364. - - *Yves Senn* - -* Added support for creating a table via Rails migration generator. - For example, - - rails g migration create_books title:string content:text - - will generate a migration that creates a table called books with - the listed attributes, without creating a model. - - *Sammy Larbi* - -* Fix bug that raises the wrong exception when the exception handled by PostgreSQL adapter - doesn't respond to `#result`. - Fixes #8617. - - *kennyj* - -* Support PostgreSQL specific column types when using `change_table`. - Fixes #9480. - - Example: - - change_table :authors do |t| - t.hstore :books - t.json :metadata - end - - *Yves Senn* - -* Revert 408227d9c5ed7d, 'quote numeric'. This introduced some regressions. - - *Steve Klabnik* - -* Fix calculation of `db_runtime` property in - `ActiveRecord::Railties::ControllerRuntime#cleanup_view_runtime`. - Previously, after raising `ActionView::MissingTemplate`, `db_runtime` was - not populated. - Fixes #9215. - - *Igor Fedoronchuk* - -* Do not try to touch invalid (and thus not persisted) parent record - for a `belongs_to :parent, touch: true` association - - *Olek Janiszewski* - -* Fix when performing an ordered join query. The bug only - affected queries where the order was given with a symbol. - Fixes #9275. - - Example: - - # This will expand the order :name to "authors".name. - Author.joins(:books).where('books.published = 1').order(:name) - - -## Rails 4.0.0.beta1 (February 25, 2013) ## - -* Fix overriding of attributes by `default_scope` on `ActiveRecord::Base#dup`. - - *Hiroshige UMINO* - -* Update queries now use prepared statements. - - *Olli Rissanen* - -* Fixing issue #8345. Now throwing an error when one attempts to touch a - new object that has not yet been persisted. For instance: - - Example: - - ball = Ball.new - ball.touch :updated_at # => raises error - - It is not until the ball object has been persisted that it can be touched. - This follows the behavior of update_column. - - *John Wang* - -* Preloading ordered `has_many :through` associations no longer applies - invalid ordering to the `:through` association. - Fixes #8663. - - *Yves Senn* - -* The auto explain feature has been removed. This feature was - activated by configuring `config.active_record.auto_explain_threshold_in_seconds`. - The configuration option was deprecated and has no more effect. - - You can still use `ActiveRecord::Relation#explain` to see the EXPLAIN output for - any given relation. - - *Yves Senn* - -* The `:on` option for `after_commit` and `after_rollback` now - accepts an Array of actions. - Fixes #988. - - Example: - - after_commit :update_cache on: [:create, :update] - - *Yves Senn* - -* Rename related indexes on `rename_table` and `rename_column`. This - does not affect indexes with custom names. - - *Yves Senn* - -* Prevent the creation of indices with too long names, which cause - internal operations to fail (sqlite3 adapter only). The method - `allowed_index_name_length` defines the length limit enforced by - rails. It's value defaults to `index_name_length` but can vary per adapter. - Fixes #8264. - - *Yves Senn* - -* Fixing issue #776. - - Memory bloat in transactions is handled by having the transaction hold only - the AR objects which it absolutely needs to know about. These are the AR - objects with callbacks (they need to be updated as soon as something in the - transaction occurs). - - All other AR objects can be updated lazily by keeping a reference to a - TransactionState object. If an AR object gets inside a transaction, then - the transaction will add its TransactionState to the AR object. When the - user makes a call to some attribute on an AR object (which has no - callbacks) associated with a transaction, the AR object will call the - sync_with_transaction_state method and make sure it is up to date with the - transaction. After it has synced with the transaction state, the AR object - will return the attribute that was requested. - - Most of the logic in the changes are used to handle multiple transactions, - in which case the AR object has to recursively follow parent pointers of - TransactionState objects. - - *John Wang* - -* Descriptive error message when the necessary AR adapter gem was not found. - Fixes #7313. - - *Yves Senn* - -* Active Record now raises an error when blank arguments are passed to query - methods for which blank arguments do not make sense. - - Example: - - Post.includes() # => raises error - - *John Wang* - -* Simplified type casting code for timezone aware attributes to use the - `in_time_zone` method if it is available. This introduces a subtle change - of behavior when using `Date` instances as they are directly converted to - `ActiveSupport::TimeWithZone` instances without first being converted to - `Time` instances. For example: - - # Rails 3.2 behavior - >> Date.today.to_time.in_time_zone - => Wed, 13 Feb 2013 07:00:00 UTC +00:00 - - # Rails 4.0 behavior - >> Date.today.in_time_zone - => Wed, 13 Feb 2013 00:00:00 UTC +00:00 - - On the plus side it now behaves the same whether you pass a `String` date - or an actual `Date` instance. For example: - - # Rails 3.2 behavior - >> Date.civil(2013, 2, 13).to_time.in_time_zone - => Wed, 13 Feb 2013 07:00:00 UTC +00:00 - >> Time.zone.parse("2013-02-13") - => Wed, 13 Feb 2013 00:00:00 UTC +00:00 - - # Rails 4.0 behavior - >> Date.civil(2013, 2, 13).in_time_zone - => Wed, 13 Feb 2013 00:00:00 UTC +00:00 - >> "2013-02-13".in_time_zone - => Wed, 13 Feb 2013 00:00:00 UTC +00:00 - - If you need the old behavior you can convert the dates to times manually. - For example: - - >> Post.new(created_at: Date.today).created_at - => Wed, 13 Feb 2013 00:00:00 UTC +00:00 - - >> Post.new(created_at: Date.today.to_time).created_at - => Wed, 13 Feb 2013 07:00:00 UTC +00:00 - - *Andrew White* - -* Preloading `has_many :through` associations with conditions won't - cache the `:through` association. This will prevent invalid - subsets to be cached. - Fixes #8423. - - Example: - - class User - has_many :posts - has_many :recent_comments, -> { where('created_at > ?', 1.week.ago) }, :through => :posts - end - - a_user = User.includes(:recent_comments).first - - # This is preloaded. - a_user.recent_comments - - # This is not preloaded, fetched now. - a_user.posts - - *Yves Senn* - -* Don't run `after_commit` callbacks when creating through an association - if saving the record fails. - - *James Miller* - -* Allow store accessors to be overridden like other attribute methods, e.g.: - - class User < ActiveRecord::Base - store :settings, accessors: [ :color, :homepage ], coder: JSON - - def color - super || 'red' - end - end - - *Sergey Nartimov* - -* Quote numeric values being compared to non-numeric columns. Otherwise, - in some database, the string column values will be coerced to a numeric - allowing 0, 0.0 or false to match any string starting with a non-digit. - - Example: - - App.where(apikey: 0) # => SELECT * FROM users WHERE apikey = '0' - - *Dylan Smith* - -* Schema dumper supports dumping the enabled database extensions to `schema.rb` - (currently only supported by PostgreSQL). - - *Justin George* - -* The database adapters now converts the options passed thought `DATABASE_URL` - environment variable to the proper Ruby types before using. For example, SQLite requires - that the timeout value is an integer, and PostgreSQL requires that the - prepared_statements option is a boolean. These now work as expected: - - Example: - - DATABASE_URL=sqlite3://localhost/test_db?timeout=500 - DATABASE_URL=postgresql://localhost/test_db?prepared_statements=false - - *Aaron Stone + Rafael Mendonça França* - -* `Relation#merge` now only overwrites where values on the LHS of the - merge. Consider: - - left = Person.where(age: [13, 14, 15]) - right = Person.where(age: [13, 14]).where(age: [14, 15]) - - `left` results in the following SQL: - - WHERE age IN (13, 14, 15) - - `right` results in the following SQL: - - WHERE age IN (13, 14) AND age IN (14, 15) - - Previously, `left.merge(right)` would result in all but the last - condition being removed: - - WHERE age IN (14, 15) - - Now it results in the LHS condition(s) for `age` being removed, but - the RHS remains as it is: - - WHERE age IN (13, 14) AND age IN (14, 15) - - *Jon Leighton* - -* Fix handling of dirty time zone aware attributes - - Previously, when `time_zone_aware_attributes` were enabled, after - changing a datetime or timestamp attribute and then changing it back - to the original value, `changed_attributes` still tracked the - attribute as changed. This caused `[attribute]_changed?` and - `changed?` methods to return true incorrectly. - - Example: - - in_time_zone 'Paris' do - order = Order.new - original_time = Time.local(2012, 10, 10) - order.shipped_at = original_time - order.save - order.changed? # => false - - # changing value - order.shipped_at = Time.local(2013, 1, 1) - order.changed? # => true - - # reverting to original value - order.shipped_at = original_time - order.changed? # => false, used to return true - end - - *Lilibeth De La Cruz* - -* When `#count` is used in conjunction with `#uniq` we perform `count(:distinct => true)`. - Fixes #6865. - - Example: - - relation.uniq.count # => SELECT COUNT(DISTINCT *) - - *Yves Senn + Kaspar Schiess* - -* PostgreSQL ranges type support. Includes: int4range, int8range, - numrange, tsrange, tstzrange, daterange - - Ranges can be created with inclusive and exclusive bounds. - - Example: - - create_table :Room do |t| - t.daterange :availability - end - - Room.create(availability: (Date.today..Float::INFINITY)) - Room.first.availability # => Wed, 19 Sep 2012..Infinity - - One thing to note: Range class does not support exclusive lower - bound. - - *Alexander Grebennik* - -* 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 built records. - Fixes #8879. - - Example: - - widget = Widget.new - widget.things.build - widget.things.empty? # => false - - *Yves Senn* - -* 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` - - *Pascal Friederich* - -* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary. - - * The methods `drop_table` and `remove_column` are now reversible, as long as the necessary information is given. - The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not reversible). - The method `change_table` is also reversible, as long as its block doesn't call `remove`, `change` or `change_default` - - * New method `reversible` makes it possible to specify code to be run when migrating up or down. - See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#using-the-reversible-method) - - * New method `revert` will revert a whole migration or the given block. - 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. - - *Marc-André Lafortune* - -* Serialized attributes can be serialized in integer columns. - Fixes #8575. - - *Rafael Mendonça França* - -* Keep index names when using `alter_table` with sqlite3. - Fixes #3489. - - *Yves Senn* - -* Add ability for PostgreSQL adapter to disable user triggers in `disable_referential_integrity`. - Fixes #5523. - - *Gary S. Weaver* - -* Added support for `validates_uniqueness_of` in PostgreSQL array columns. - Fixes #8075. - - *Pedro Padron* - -* Allow int4range and int8range columns to be created in PostgreSQL and properly convert to/from database. - - *Alexey Vasiliev aka leopard* - -* Do not log the binding values for binary columns. - - *Matthew M. Boedicker* - -* Fix counter cache columns not updated when replacing `has_many :through` - associations. - - *Matthew Robertson* - -* Recognize migrations placed in directories containing numbers and 'rb'. - Fixes #8492. - - *Yves Senn* - -* Add `ActiveRecord::Base.cache_timestamp_format` class attribute to control - the format of the timestamp value in the cache key. Defaults to `:nsec`. - Fixes #8195. - - *Rafael Mendonça França* - -* Session variables can be set for the `mysql`, `mysql2`, and `postgresql` adapters - in the `variables: <hash>` parameter in `config/database.yml`. The key-value pairs of this - hash will be sent in a `SET key = value` query on new database connections. See also: - http://dev.mysql.com/doc/refman/5.0/en/set-statement.html - http://www.postgresql.org/docs/8.3/static/sql-set.html - - *Aaron Stone* - -* Allow `Relation#where` with no arguments to be chained with new `not` query method. - - Example: - - Developer.where.not(name: 'Aaron') - - *Akira Matsuda* - -* Unscope `update_column(s)` query to ignore default scope. - - When applying `default_scope` to a class with a where clause, using - `update_column(s)` could generate a query that would not properly update - the record due to the where clause from the `default_scope` being applied - to the update query. - - class User < ActiveRecord::Base - default_scope -> { where(active: true) } - end - - user = User.first - user.active = false - user.save! - - user.update_column(:active, true) # => false - - In this situation we want to skip the default_scope clause and just - update the record based on the primary key. With this change: - - user.update_column(:active, true) # => true - - Fixes #8436. - - *Carlos Antonio da Silva* - -* SQLite adapter no longer corrupts binary data if the data contains `%00`. - - *Chris Feist* - -* Fix performance problem with `primary_key` method in PostgreSQL adapter when having many schemas. - Uses `pg_constraint` table instead of `pg_depend` table which has many records in general. - Fixes #8414. - - *kennyj* - -* Do not instantiate intermediate Active Record objects when eager loading. - These records caused `after_find` to run more than expected. - Fixes #3313. - - *Yves Senn* - -* Add STI support to init and building associations. - Allows you to do `BaseClass.new(type: "SubClass")` as well as - `parent.children.build(type: "SubClass")` or `parent.build_child` - to initialize an STI subclass. Ensures that the class name is a - valid class and that it is in the ancestors of the super class - that the association is expecting. - - *Jason Rush* - -* Observers was extracted from Active Record as `rails-observers` gem. - - *Rafael Mendonça França* - -* Ensure that associations take a symbol argument. *Steve Klabnik* - -* Fix dirty attribute checks for `TimeZoneConversion` with nil and blank - datetime attributes. Setting a nil datetime to a blank string should not - result in a change being flagged. - Fixes #8310. - - *Alisdair McDiarmid* - -* Prevent mass assignment to the type column of polymorphic associations when using `build` - Fixes #8265. - - *Yves Senn* - -* Deprecate calling `Relation#sum` with a block. To perform a calculation over - the array result of the relation, use `to_a.sum(&block)`. - - *Carlos Antonio da Silva* - -* Fix PostgreSQL adapter to handle BC timestamps correctly - - HistoryEvent.create!(name: "something", occured_at: Date.new(0) - 5.years) - - *Bogdan Gusiev* - -* When running migrations on PostgreSQL, the `:limit` option for `binary` and `text` columns is silently dropped. - Previously, these migrations caused sql exceptions, because PostgreSQL doesn't support limits on these types. - - *Victor Costan* - -* Don't change STI type when calling `ActiveRecord::Base#becomes`. - Add `ActiveRecord::Base#becomes!` with the previous behavior. - - See #3023 for more information. - - *Thomas Hollstegge* - -* `rename_index` can be used inside a `change_table` block. - - change_table :accounts do |t| - t.rename_index :user_id, :account_id - end - - *Jarek Radosz* - -* `#pluck` can be used on a relation with `select` clause. Fix #7551 - - Example: - - Topic.select([:approved, :id]).order(:id).pluck(:id) - - *Yves Senn* - -* Do not create useless database transaction when building `has_one` association. - - Example: - - User.has_one :profile - User.new.build_profile - - *Bogdan Gusiev* - -* `:counter_cache` option for `has_many` associations to support custom named counter caches. - Fixes #7993. - - *Yves Senn* - -* Deprecate the possibility to pass a string as third argument of `add_index`. - Pass `unique: true` instead. - - add_index(:users, :organization_id, unique: true) - - *Rafael Mendonça França* - -* Raise an `ArgumentError` when passing an invalid option to `add_index`. - - *Rafael Mendonça França* - -* Fix `find_in_batches` crashing when IDs are strings and start option is not specified. - - *Alexis Bernard* - -* `AR::Base#attributes_before_type_cast` now returns unserialized values for serialized attributes. - - *Nikita Afanasenko* - -* Use query cache/uncache when using `DATABASE_URL`. - Fixes #6951. - - *kennyj* - -* Fix bug where `update_columns` and `update_column` would not let you update the primary key column. - - *Henrik Nyh* - -* The `create_table` method raises an `ArgumentError` when the primary key column is redefined. - Fixes #6378. - - *Yves Senn* - -* `ActiveRecord::AttributeMethods#[]` raises `ActiveModel::MissingAttributeError` - error if the given attribute is missing. Fixes #5433. - - class Person < ActiveRecord::Base - belongs_to :company - end - - # Before: - person = Person.select('id').first - person[:name] # => nil - person.name # => ActiveModel::MissingAttributeError: missing_attribute: name - person[:company_id] # => nil - person.company # => nil - - # After: - person = Person.select('id').first - person[:name] # => ActiveModel::MissingAttributeError: missing_attribute: name - person.name # => ActiveModel::MissingAttributeError: missing_attribute: name - person[:company_id] # => ActiveModel::MissingAttributeError: missing_attribute: company_id - person.company # => ActiveModel::MissingAttributeError: missing_attribute: company_id - - *Francesco Rodriguez* - -* Small binary fields use the `VARBINARY` MySQL type, instead of `TINYBLOB`. - - *Victor Costan* - -* Decode URI encoded attributes on database connection URLs. - - *Shawn Veader* - -* Add `find_or_create_by`, `find_or_create_by!` and - `find_or_initialize_by` methods to `Relation`. - - These are similar to the `first_or_create` family of methods, but - the behaviour when a record is created is slightly different: - - User.where(first_name: 'Penélope').first_or_create - - will execute: - - User.where(first_name: 'Penélope').create - - Causing all the `create` callbacks to execute within the context of - the scope. This could affect queries that occur within callbacks. - - User.find_or_create_by(first_name: 'Penélope') - - will execute: - - User.create(first_name: 'Penélope') - - Which obviously does not affect the scoping of queries within - callbacks. - - The `find_or_create_by` version also reads better, frankly. - - If you need to add extra attributes during create, you can do one of: - - User.create_with(active: true).find_or_create_by(first_name: 'Jon') - User.find_or_create_by(first_name: 'Jon') { |u| u.active = true } - - The `first_or_create` family of methods have been nodoc'ed in favour - of this API. They may be deprecated in the future but their - implementation is very small and it's probably not worth putting users - through lots of annoying deprecation warnings. - - *Jon Leighton* - -* Fix bug with presence validation of associations. Would incorrectly add duplicated errors - when the association was blank. Bug introduced in 1fab518c6a75dac5773654646eb724a59741bc13. - - *Scott Willson* - -* Fix bug where sum(expression) returns string '0' for no matching records. - Fixes #7439 - - *Tim Macfarlane* - -* PostgreSQL adapter correctly fetches default values when using multiple schemas and domains in a db. Fixes #7914 - - *Arturo Pie* - -* Learn ActiveRecord::QueryMethods#order work with hash arguments - - When symbol or hash passed we convert it to Arel::Nodes::Ordering. - If we pass invalid direction(like name: :DeSc) ActiveRecord::QueryMethods#order will raise an exception - - User.order(:name, email: :desc) - # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC - - *Tima Maslyuchenko* - -* Rename `ActiveRecord::Fixtures` class to `ActiveRecord::FixtureSet`. - Instances of this class normally hold a collection of fixtures (records) - loaded either from a single YAML file, or from a file and a folder - with the same name. This change make the class name singular and makes - the class easier to distinguish from the modules like - `ActiveRecord::TestFixtures`, which operates on multiple fixture sets, - or `DelegatingFixtures`, `::Fixtures`, etc., - and from the class `ActiveRecord::Fixture`, which corresponds to a single - fixture. - - *Alexey Muranov* - -* The postgres adapter now supports tables with capital letters. - Fixes #5920. - - *Yves Senn* - -* `CollectionAssociation#count` returns `0` without querying if the - parent record is not persisted. - - Before: - - person.pets.count - # SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" IS NULL - # => 0 - - After: - - person.pets.count - # fires without sql query - # => 0 - - *Francesco Rodriguez* - -* Fix `reset_counters` crashing on `has_many :through` associations. - Fixes #7822. - - *lulalala* - -* Support for partial inserts. - - When inserting new records, only the fields which have been changed - from the defaults will actually be included in the INSERT statement. - The other fields will be populated by the database. - - This is more efficient, and also means that it will be safe to - remove database columns without getting subsequent errors in running - app processes (so long as the code in those processes doesn't - contain any references to the removed column). - - The `partial_updates` configuration option is now renamed to - `partial_writes` to reflect the fact that it now impacts both inserts - and updates. - - *Jon Leighton* - -* Allow before and after validations to take an array of lifecycle events - - *John Foley* - -* Support for specifying transaction isolation level - - If your database supports setting the isolation level for a transaction, you can set - it like so: - - Post.transaction(isolation: :serializable) do - # ... - end - - Valid isolation levels are: - - * `:read_uncommitted` - * `:read_committed` - * `:repeatable_read` - * `:serializable` - - You should consult the documentation for your database to understand the - semantics of these different levels: - - * http://www.postgresql.org/docs/9.1/static/transaction-iso.html - * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html - - An `ActiveRecord::TransactionIsolationError` will be raised if: - - * The adapter does not support setting the isolation level - * You are joining an existing open transaction - * You are creating a nested (savepoint) transaction - - The mysql, mysql2 and postgresql adapters support setting the transaction - isolation level. However, support is disabled for mysql versions below 5, - because they are affected by a bug (http://bugs.mysql.com/bug.php?id=39170) - which means the isolation level gets persisted outside the transaction. - - *Jon Leighton* - -* `ActiveModel::ForbiddenAttributesProtection` is included by default - in Active Record models. Check the docs of `ActiveModel::ForbiddenAttributesProtection` - for more details. - - *Guillermo Iguaran* - -* Remove integration between Active Record and - `ActiveModel::MassAssignmentSecurity`, `protected_attributes` gem - should be added to use `attr_accessible`/`attr_protected`. Mass - assignment options has been removed from all the AR methods that - used it (ex. `AR::Base.new`, `AR::Base.create`, `AR::Base#update_attributes`, etc). - - *Guillermo Iguaran* - -* Fix the return of querying with an empty hash. - Fixes #6971. - - User.where(token: {}) - - Before: - - #=> SELECT * FROM users; - - After: - - #=> SELECT * FROM users WHERE 1=0; - - *Damien Mathieu* - -* Fix creation of through association models when using `collection=[]` - on a `has_many :through` association from an unsaved model. - Fixes #7661. - - *Ernie Miller* - -* Explain only normal CRUD sql (select / update / insert / delete). - Fix problem that explains unexplainable sql. - Fixes #7544 #6458. - - *kennyj* - -* You can now override the generated accessor methods for stored attributes - and reuse the original behavior with `read_store_attribute` and `write_store_attribute`, - which are counterparts to `read_attribute` and `write_attribute`. - - *Matt Jones* - -* Accept `belongs_to` (including polymorphic) association keys in queries. - - The following queries are now equivalent: - - Post.where(author: author) - Post.where(author_id: author) - - PriceEstimate.where(estimate_of: treasure) - PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure) - - *Peter Brown* - -* Use native `mysqldump` command instead of `structure_dump` method - when dumping the database structure to a sql file. Fixes #5547. - - *kennyj* - -* PostgreSQL inet and cidr types are converted to `IPAddr` objects. - - *Dan McClain* - -* PostgreSQL array type support. Any datatype can be used to create an - array column, with full migration and schema dumper support. - - To declare an array column, use the following syntax: - - create_table :table_with_arrays do |t| - t.integer :int_array, array: true - # integer[] - t.integer :int_array, array: true, length: 2 - # smallint[] - t.string :string_array, array: true, length: 30 - # char varying(30)[] - end - - This respects any other migration detail (limits, defaults, etc). - Active Record will serialize and deserialize the array columns on - their way to and from the database. - - One thing to note: PostgreSQL does not enforce any limits on the - number of elements, and any array can be multi-dimensional. Any - array that is multi-dimensional must be rectangular (each sub array - must have the same number of elements as its siblings). - - If the `pg_array_parser` gem is available, it will be used when - parsing PostgreSQL's array representation. - - *Dan McClain* - -* Attribute predicate methods, such as `article.title?`, will now raise - `ActiveModel::MissingAttributeError` if the attribute being queried for - truthiness was not read from the database, instead of just returning `false`. - - *Ernie Miller* - -* `ActiveRecord::SchemaDumper` uses Ruby 1.9 style hash, which means that the - schema.rb file will be generated using this new syntax from now on. - - *Konstantin Shabanov* - -* Map interval with precision to string datatype in PostgreSQL. Fixes #7518. - - *Yves Senn* - -* Fix eagerly loading associations without primary keys. Fixes #4976. - - *Kelley Reynolds* - -* Rails now raise an exception when you're trying to run a migration that has an invalid - file name. Only lower case letters, numbers, and '_' are allowed in migration's file name. - Please see #7419 for more details. - - *Jan Bernacki* - -* Fix bug when calling `store_accessor` multiple times. - Fixes #7532. - - *Matt Jones* - -* Fix store attributes that show the changes incorrectly. - Fixes #7532. - - *Matt Jones* - -* Fix `ActiveRecord::Relation#pluck` when columns or tables are reserved words. - - *Ian Lesperance* - -* Allow JSON columns to be created in PostgreSQL and properly encoded/decoded. - to/from database. - - *Dickson S. Guedes* - -* Fix time column type casting for invalid time string values to correctly return `nil`. - - *Adam Meehan* - -* Allow to pass Symbol or Proc into `:limit` option of #accepts_nested_attributes_for. - - *Mikhail Dieterle* - -* ActiveRecord::SessionStore has been extracted from Active Record as `activerecord-session_store` - gem. Please read the `README.md` file on the gem for the usage. - - *Prem Sichanugrist* - -* Fix `reset_counters` when there are multiple `belongs_to` association with the - same foreign key and one of them have a counter cache. - Fixes #5200. - - *Dave Desrochers* - -* `serialized_attributes` and `_attr_readonly` become class method only. Instance reader methods are deprecated. - - *kennyj* - -* Round usec when comparing timestamp attributes in the dirty tracking. - Fixes #6975. - - *kennyj* - -* Use inversed parent for first and last child of `has_many` association. - - *Ravil Bayramgalin* - -* Fix `Column.microseconds` and `Column.fast_string_to_time` to avoid converting - timestamp seconds to a float, since it occasionally results in inaccuracies - with microsecond-precision times. Fixes #7352. - - *Ari Pollak* - -* Fix AR#dup to nullify the validation errors in the dup'ed object. Previously the original - and the dup'ed object shared the same errors. - - *Christian Seiler* - -* Raise `ArgumentError` if list of attributes to change is empty in `update_all`. - - *Roman Shatsov* - -* Fix AR#create to return an unsaved record when AR::RecordInvalid is - raised. Fixes #3217. - - *Dave Yeu* - -* Fixed table name prefix that is generated in engines for namespaced models. - - *Wojciech Wnętrzak* - -* Make sure `:environment` task is executed before `db:schema:load` or `db:structure:load`. - Fixes #4772. - - *Seamus Abshere* - -* Allow Relation#merge to take a proc. - - This was requested by DHH to allow creating of one's own custom - association macros. - - For example: - - module Commentable - def has_many_comments(extra) - has_many :comments, -> { where(:foo).merge(extra) } - end - end - - class Post < ActiveRecord::Base - extend Commentable - has_many_comments -> { where(:bar) } - end - - *Jon Leighton* - -* Add CollectionProxy#scope. - - This can be used to get a Relation from an association. - - Previously we had a #scoped method, but we're deprecating that for - AR::Base, so it doesn't make sense to have it here. - - This was requested by DHH, to facilitate code like this: - - Project.scope.order('created_at DESC').page(current_page).tagged_with(@tag).limit(5).scoping do - @topics = @project.topics.scope - @todolists = @project.todolists.scope - @attachments = @project.attachments.scope - @documents = @project.documents.scope - end - - *Jon Leighton* - -* Add `Relation#load`. - - This method explicitly loads the records and then returns `self`. - - Rather than deciding between "do I want an array or a relation?", - most people are actually asking themselves "do I want to eager load - or lazy load?" Therefore, this method provides a way to explicitly - eager-load without having to switch from a `Relation` to an array. - - Example: - - @posts = Post.where(published: true).load - - *Jon Leighton* - -* `Relation#order`: make new order prepend old one. - - User.order("name asc").order("created_at desc") - # SELECT * FROM users ORDER BY created_at desc, name asc - - This also affects order defined in `default_scope` or any kind of associations. - - *Bogdan Gusiev* - -* `Model.all` now returns an `ActiveRecord::Relation`, rather than an - array of records. Use `Relation#to_a` if you really want an array. - - In some specific cases, this may cause breakage when upgrading. - However in most cases the `ActiveRecord::Relation` will just act as a - lazy-loaded array and there will be no problems. - - Note that calling `Model.all` with options (e.g. - `Model.all(conditions: '...')` was already deprecated, but it will - still return an array in order to make the transition easier. - - `Model.scoped` is deprecated in favour of `Model.all`. - - `Relation#all` still returns an array, but is deprecated (since it - would serve no purpose if we made it return a `Relation`). - - *Jon Leighton* - -* `:finder_sql` and `:counter_sql` options on collection associations - are deprecated. Please transition to using scopes. - - *Jon Leighton* - -* `:insert_sql` and `:delete_sql` options on `has_and_belongs_to_many` - associations are deprecated. Please transition to using `has_many - :through`. - - *Jon Leighton* - -* Added `#update_columns` method which updates the attributes from - the passed-in hash without calling save, hence skipping validations and - callbacks. `ActiveRecordError` will be raised when called on new objects - or when at least one of the attributes is marked as read only. - - post.attributes # => {"id"=>2, "title"=>"My title", "body"=>"My content", "author"=>"Peter"} - post.update_columns(title: 'New title', author: 'Sebastian') # => true - post.attributes # => {"id"=>2, "title"=>"New title", "body"=>"My content", "author"=>"Sebastian"} - - *Sebastian Martinez + Rafael Mendonça França* - -* The migration generator now creates a join table with (commented) indexes every time - the migration name contains the word `join_table`: - - rails g migration create_join_table_for_artists_and_musics artist_id:index music_id - - *Aleksey Magusev* - -* Add `add_reference` and `remove_reference` schema statements. Aliases, `add_belongs_to` - and `remove_belongs_to` are acceptable. References are reversible. - - Examples: - - # Create a user_id column - add_reference(:products, :user) - # Create a supplier_id, supplier_type columns and appropriate index - add_reference(:products, :supplier, polymorphic: true, index: true) - # Remove polymorphic reference - remove_reference(:products, :supplier, polymorphic: true) - - *Aleksey Magusev* - -* Add `:default` and `:null` options to `column_exists?`. - - column_exists?(:testings, :taggable_id, :integer, null: false) - column_exists?(:testings, :taggable_type, :string, default: 'Photo') - - *Aleksey Magusev* - -* `ActiveRecord::Relation#inspect` now makes it clear that you are - dealing with a `Relation` object rather than an array:. - - User.where(age: 30).inspect - # => <ActiveRecord::Relation [#<User ...>, #<User ...>, ...]> - - User.where(age: 30).to_a.inspect - # => [#<User ...>, #<User ...>] - - The number of records displayed will be limited to 10. - - *Brian Cardarella, Jon Leighton & Damien Mathieu* - -* Add `collation` and `ctype` support to PostgreSQL. These are available for PostgreSQL 8.4 or later. - Example: - - development: - adapter: postgresql - host: localhost - database: rails_development - username: foo - password: bar - encoding: UTF8 - collation: ja_JP.UTF8 - ctype: ja_JP.UTF8 +* Abort a rake task when missing db/structure.sql like `db:schema:load` task. *kennyj* -* Changed `validates_presence_of` on an association so that children objects - do not validate as being present if they are marked for destruction. This - prevents you from saving the parent successfully and thus putting the parent - in an invalid state. - - *Nick Monje & Brent Wheeldon* - -* `FinderMethods#exists?` now returns `false` with the `false` argument. - - *Egor Lynko* - -* Added support for specifying the precision of a timestamp in the PostgreSQL - adapter. So, instead of having to incorrectly specify the precision using the - `:limit` option, you may use `:precision`, as intended. For example, in a migration: - - def change - create_table :foobars do |t| - t.timestamps precision: 0 - end - end - - *Tony Schneider* - -* Allow `ActiveRecord::Relation#pluck` to accept multiple columns. Returns an - array of arrays containing the typecasted values: - - Person.pluck(:id, :name) - # SELECT people.id, people.name FROM people - # [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']] - - *Jeroen van Ingen & Carlos Antonio da Silva* - -* Improve the derivation of HABTM join table name to take account of nesting. - It now takes the table names of the two models, sorts them lexically and - then joins them, stripping any common prefix from the second table name. - - Some examples: - - Top level models (Category <=> Product) - Old: categories_products - New: categories_products - - Top level models with a global table_name_prefix (Category <=> Product) - Old: site_categories_products - New: site_categories_products - - Nested models in a module without a table_name_prefix method (Admin::Category <=> Admin::Product) - Old: categories_products - New: categories_products - - Nested models in a module with a table_name_prefix method (Admin::Category <=> Admin::Product) - Old: categories_products - New: admin_categories_products - - Nested models in a parent model (Catalog::Category <=> Catalog::Product) - Old: categories_products - New: catalog_categories_products - - Nested models in different parent models (Catalog::Category <=> Content::Page) - Old: categories_pages - New: catalog_categories_content_pages - - *Andrew White* - -* Move HABTM validity checks to `ActiveRecord::Reflection`. One side effect of - this is to move when the exceptions are raised from the point of declaration - to when the association is built. This is consistent with other association - validity checks. - - *Andrew White* - -* Added `stored_attributes` hash which contains the attributes stored using - `ActiveRecord::Store`. This allows you to retrieve the list of attributes - you've defined. - - class User < ActiveRecord::Base - store :settings, accessors: [:color, :homepage] - end - - User.stored_attributes[:settings] # [:color, :homepage] - - *Joost Baaij & Carlos Antonio da Silva* - -* PostgreSQL default log level is now 'warning', to bypass the noisy notice - messages. You can change the log level using the `min_messages` option - available in your config/database.yml. - - *kennyj* - -* Add uuid datatype support to PostgreSQL adapter. - - *Konstantin Shabanov* - -* Added `ActiveRecord::Migration.check_pending!` that raises an error if - migrations are pending. - - *Richard Schneeman* - -* Added `#destroy!` which acts like `#destroy` but will raise an - `ActiveRecord::RecordNotDestroyed` exception instead of returning `false`. - - *Marc-André Lafortune* - -* Added support to `CollectionAssociation#delete` for passing `fixnum` - or `string` values as record ids. This finds the records responding - to the `id` and executes delete on them. - - class Person < ActiveRecord::Base - has_many :pets - end - - person.pets.delete("1") # => [#<Pet id: 1>] - person.pets.delete(2, 3) # => [#<Pet id: 2>, #<Pet id: 3>] - - *Francesco Rodriguez* - -* Deprecated most of the 'dynamic finder' methods. All dynamic methods - except for `find_by_...` and `find_by_...!` are deprecated. Here's - how you can rewrite the code: - - * `find_all_by_...` can be rewritten using `where(...)` - * `find_last_by_...` can be rewritten using `where(...).last` - * `scoped_by_...` can be rewritten using `where(...)` - * `find_or_initialize_by_...` can be rewritten using - `where(...).first_or_initialize` - * `find_or_create_by_...` can be rewritten using - `find_or_create_by(...)` or where(...).first_or_create` - * `find_or_create_by_...!` can be rewritten using - `find_or_create_by!(...) or `where(...).first_or_create!` - - The implementation of the deprecated dynamic finders has been moved - to the `activerecord-deprecated_finders` gem. See below for details. - - *Jon Leighton* - -* Deprecated the old-style hash based finder API. This means that - methods which previously accepted "finder options" no longer do. For - example this: - - Post.find(:all, conditions: { comments_count: 10 }, limit: 5) - - Should be rewritten in the new style which has existed since Rails 3: - - Post.where(comments_count: 10).limit(5) - - Note that as an interim step, it is possible to rewrite the above as: - - Post.all.merge(where: { comments_count: 10 }, limit: 5) - - This could save you a lot of work if there is a lot of old-style - finder usage in your application. - - `Relation#merge` now accepts a hash of - options, but they must be identical to the names of the equivalent - finder method. These are mostly identical to the old-style finder - option names, except in the following cases: - - * `:conditions` becomes `:where`. - * `:include` becomes `:includes`. - - The code to implement the deprecated features has been moved out to the - `activerecord-deprecated_finders` gem. This gem is a dependency of Active - Record in Rails 4.0, so the interface works out of the box. It will no - longer be a dependency from Rails 4.1 (you'll need to add it to the - `Gemfile` in 4.1), and will be maintained until Rails 5.0. - - *Jon Leighton* - -* It's not possible anymore to destroy a model marked as read only. - - *Johannes Barre* - -* Added ability to ActiveRecord::Relation#from to accept other ActiveRecord::Relation objects. - - Record.from(subquery) - Record.from(subquery, :a) - - *Radoslav Stankov* - -* Added custom coders support for ActiveRecord::Store. Now you can set - your custom coder like this: - - store :settings, accessors: [ :color, :homepage ], coder: JSON - - *Andrey Voronkov* - -* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by - default to avoid silent data loss. This can be disabled by specifying - `strict: false` in your `database.yml`. - - *Michael Pearson* - -* Added default order to `first` to assure consistent results among - different database engines. Introduced `take` as a replacement to - the old behavior of `first`. - - *Marcelo Silveira* - -* Added an `:index` option to automatically create indexes for references - and belongs_to statements in migrations. - - The `references` and `belongs_to` methods now support an `index` - option that receives either a boolean value or an options hash - that is identical to options available to the add_index method: - - create_table :messages do |t| - t.references :person, index: true - end - - Is the same as: - - create_table :messages do |t| - t.references :person - end - add_index :messages, :person_id - - Generators have also been updated to use the new syntax. - - *Joshua Wood* - -* Added `#find_by` and `#find_by!` to mirror the functionality - provided by dynamic finders in a way that allows dynamic input more - easily: - - Post.find_by name: 'Spartacus', rating: 4 - Post.find_by "published_at < ?", 2.weeks.ago - Post.find_by! name: 'Spartacus' - - *Jon Leighton* - -* Added ActiveRecord::Base#slice to return a hash of the given methods with - their names as keys and returned values as values. - - *Guillermo Iguaran* - -* Deprecate eager-evaluated scopes. - - Don't use this: - - scope :red, where(color: 'red') - default_scope where(color: 'red') - - Use this: - - scope :red, -> { where(color: 'red') } - default_scope { where(color: 'red') } - - The former has numerous issues. It is a common newbie gotcha to do - the following: - - scope :recent, where(published_at: Time.now - 2.weeks) - - Or a more subtle variant: - - scope :recent, -> { where(published_at: Time.now - 2.weeks) } - scope :recent_red, recent.where(color: 'red') - - Eager scopes are also very complex to implement within Active - Record, and there are still bugs. For example, the following does - not do what you expect: - - scope :remove_conditions, except(:where) - where(...).remove_conditions # => still has conditions - - *Jon Leighton* - -* Remove IdentityMap - - IdentityMap has never graduated to be an "enabled-by-default" feature, due - to some inconsistencies with associations, as described in this commit: - - https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6 - - Hence the removal from the codebase, until such issues are fixed. - - *Carlos Antonio da Silva* - -* Added the schema cache dump feature. - - `Schema cache dump` feature was implemented. This feature can dump/load internal state of `SchemaCache` instance - because we want to boot rails more quickly when we have many models. - - Usage notes: - - 1) execute rake task. - RAILS_ENV=production bundle exec rake db:schema:cache:dump - => generate db/schema_cache.dump - - 2) add config.active_record.use_schema_cache_dump = true in config/production.rb. BTW, true is default. - - 3) boot rails. - RAILS_ENV=production bundle exec rails server - => use db/schema_cache.dump - - 4) If you remove clear dumped cache, execute rake task. - RAILS_ENV=production bundle exec rake db:schema:cache:clear - => remove db/schema_cache.dump - - *kennyj* - -* Added support for partial indices to PostgreSQL adapter. - - The `add_index` method now supports a `where` option that receives a - string with the partial index criteria. - - add_index(:accounts, :code, where: 'active') - - generates - - CREATE INDEX index_accounts_on_code ON accounts(code) WHERE active - - *Marcelo Silveira* - -* Implemented `ActiveRecord::Relation#none` method. - - The `none` method returns a chainable relation with zero records - (an instance of the NullRelation class). - - Any subsequent condition chained to the returned relation will continue - generating an empty relation and will not fire any query to the database. - - *Juanjo Bazán* - -* Added the `ActiveRecord::NullRelation` class implementing the null - object pattern for the Relation class. - - *Juanjo Bazán* - -* Added new `dependent: :restrict_with_error` option. This will add - an error to the model, rather than raising an exception. - - The `:restrict` option is renamed to `:restrict_with_exception` to - make this distinction explicit. - - *Manoj Kumar & Jon Leighton* - -* Added `create_join_table` migration helper to create HABTM join tables. - - create_join_table :products, :categories - # => - # create_table :categories_products, id: false do |td| - # td.integer :product_id, null: false - # td.integer :category_id, null: false - # end - - *Rafael Mendonça França* - -* The primary key is always initialized in the @attributes hash to `nil` (unless - another value has been specified). - - *Aaron Paterson* - -* In previous releases, the following would generate a single query with - an `OUTER JOIN comments`, rather than two separate queries: - - Post.includes(:comments) - .where("comments.name = 'foo'") - - This behaviour relies on matching SQL string, which is an inherently - flawed idea unless we write an SQL parser, which we do not wish to - do. - - Therefore, it is now deprecated. - - To avoid deprecation warnings and for future compatibility, you must - explicitly state which tables you reference, when using SQL snippets: - - Post.includes(:comments) - .where("comments.name = 'foo'") - .references(:comments) - - Note that you do not need to explicitly specify references in the - following cases, as they can be automatically inferred: - - Post.includes(:comments).where(comments: { name: 'foo' }) - Post.includes(:comments).where('comments.name' => 'foo') - Post.includes(:comments).order('comments.name') - - You do not need to worry about this unless you are doing eager - loading. Basically, don't worry unless you see a deprecation warning - or (in future releases) an SQL error due to a missing JOIN. - - *Jon Leighton* - -* Support for the `schema_info` table has been dropped. Please - switch to `schema_migrations`. - - *Aaron Patterson* - -* Connections *must* be closed at the end of a thread. If not, your - connection pool can fill and an exception will be raised. - - *Aaron Patterson* - -* PostgreSQL hstore records can be created. - - *Aaron Patterson* - -* PostgreSQL hstore types are automatically deserialized from the database. - - *Aaron Patterson* - - -Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activerecord/CHANGELOG.md) for previous changes. +Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 3e3475f709..337106cb92 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -24,6 +24,6 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'activemodel', version - s.add_dependency 'arel', '~> 4.0.0.beta2' - s.add_dependency 'activerecord-deprecated_finders', '~> 0.0.3' + s.add_dependency 'arel', '~> 4.0.0' + s.add_dependency 'activerecord-deprecated_finders', '~> 1.0.2' end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3c92e379f1..5e5995f566 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1073,6 +1073,9 @@ module ActiveRecord # with +attributes+, linked to this object through a foreign key, and that has already # been saved (if it passed the validation). *Note*: This only works if the base model # already exists in the DB, not if it is a new (unsaved) record! + # [collection.create!(attributes = {})] + # Does the same as <tt>collection.create</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt> + # if the record is invalid. # # (*Note*: +collection+ is replaced with the symbol passed as the first argument, so # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.) @@ -1094,6 +1097,7 @@ module ActiveRecord # * <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>) + # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>) # The declaration can also include an options hash to specialize the behavior of the association. # # === Options diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 3ba6a71366..543a0247d1 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -25,9 +25,10 @@ module ActiveRecord::Associations::Builder mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def belongs_to_counter_cache_after_create_for_#{name} - record = #{name} - record.class.increment_counter(:#{cache_column}, record.id) unless record.nil? - @_after_create_counter_called = true + if record = #{name} + record.class.increment_counter(:#{cache_column}, record.id) + @_after_create_counter_called = true + end end def belongs_to_counter_cache_before_destroy_for_#{name} @@ -70,8 +71,8 @@ module ActiveRecord::Associations::Builder old_foreign_id = attribute_was(foreign_key_field) if old_foreign_id - reflection_klass = #{reflection.klass} - old_record = reflection_klass.find_by(reflection_klass.primary_key => old_foreign_id) + klass = association(#{name.inspect}).klass + old_record = klass.find_by(klass.primary_key => old_foreign_id) if old_record old_record.touch #{options[:touch].inspect if options[:touch] != true} diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 98bd010f70..920038a543 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -25,9 +25,8 @@ module ActiveRecord raise_on_type_mismatch!(record) if record load_target - # If target and record are nil, or target is equal to record, - # we don't need to have transaction. - if (target || record) && target != record + return self.target if !(target || record) + if (target != record) || record.changed? transaction_if(save) do remove_target!(options[:dependent]) if target && !target.destroyed? diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 22405c5d74..609c6e8cab 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -56,7 +56,7 @@ module ActiveRecord # # => false def instance_method_already_implemented?(method_name) if dangerous_attribute_method?(method_name) - raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" + raise DangerousAttributeError, "#{method_name} is defined by Active Record" end if superclass == Base @@ -163,8 +163,22 @@ module ActiveRecord # person.respond_to('age?') # => true # person.respond_to(:nothing) # => false def respond_to?(name, include_private = false) + name = name.to_s self.class.define_attribute_methods unless self.class.attribute_methods_generated? - super + result = super + + # If the result is false the answer is false. + return false unless result + + # If the result is true then check for the select case. + # For queries selecting a subset of columns, return false for unselected columns. + # We check defined?(@attributes) not to issue warnings if called on objects that + # have been allocated but not yet initialized. + if defined?(@attributes) && @attributes.present? && self.class.column_names.include?(name) + return has_attribute?(name) + end + + return true end # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+. @@ -328,6 +342,7 @@ module ActiveRecord end def attribute_method?(attr_name) # :nodoc: + # We check defined? because Syck calls respond_to? before actually calling initialize. defined?(@attributes) && @attributes.include?(attr_name) end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 22226b2f4f..e4c484d64b 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -83,7 +83,7 @@ module ActiveRecord # # In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+. # So, use the callback macros when you want to ensure that a certain callback is called for the entire - # hierarchy, and use the regular overwriteable methods when you want to leave it up to each descendant + # hierarchy, and use the regular overwritable methods when you want to leave it up to each descendant # to decide whether they want to call +super+ and trigger the inherited callbacks. # # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the 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 bf2f945448..816b397fcf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -253,14 +253,6 @@ module ActiveRecord @available = Queue.new self end - # Hack for tests to be able to add connections. Do not call outside of tests - def insert_connection_for_test!(c) #:nodoc: - synchronize do - @connections << c - @available.add c - end - end - # Retrieve the connection associated with the current thread, or call # #checkout to obtain one if necessary. # diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 1138b10a1b..26586f0974 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -433,7 +433,7 @@ module ActiveRecord def translate_exception(exception, message) # override in derived class - ActiveRecord::StatementInvalid.new(message) + ActiveRecord::StatementInvalid.new(message, exception) end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 94d9efe521..d098ded77c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -287,7 +287,7 @@ module ActiveRecord end rescue ActiveRecord::StatementInvalid => exception if exception.message.split(":").first =~ /Packets out of order/ - raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings." + raise ActiveRecord::StatementInvalid.new("'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings.", exception.original_exception) else raise end @@ -364,7 +364,9 @@ module ActiveRecord # and creates it again using the provided +options+. def recreate_database(name, options = {}) drop_database(name) - create_database(name, options) + sql = create_database(name, options) + reconnect! + sql end # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>. diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 09ba2e0d4a..f23521430d 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -383,7 +383,7 @@ module ActiveRecord TYPES = {} - # Register an MySQL +type_id+ with a typcasting object in + # Register an MySQL +type_id+ with a typecasting object in # +type+. def self.register_type(type_id, type) TYPES[type_id] = type diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index 14ef07a75e..a9ef11aa83 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -26,6 +26,15 @@ module ActiveRecord end end + def string_to_bit(value) + case value + when /^0x/i + value[2..-1].hex.to_s(2) # Hexadecimal notation + else + value # Bit-string notation + end + end + def hstore_to_string(object) if Hash === object object.map { |k,v| diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 51f377dfd7..1be116ce10 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -18,8 +18,19 @@ module ActiveRecord end end + class Bit < Type + def type_cast(value) + if String === value + ConnectionAdapters::PostgreSQLColumn.string_to_bit value + else + value + end + end + end + class Bytea < Type def type_cast(value) + return if value.nil? PGconn.unescape_bytea value end end @@ -322,14 +333,14 @@ module ActiveRecord # FIXME: why are we keeping these types as strings? alias_type 'tsvector', 'text' alias_type 'interval', 'text' - alias_type 'bit', 'text' - alias_type 'varbit', 'text' alias_type 'macaddr', 'text' alias_type 'uuid', 'text' register_type 'money', OID::Money.new register_type 'bytea', OID::Bytea.new register_type 'bool', OID::Boolean.new + register_type 'bit', OID::Bit.new + register_type 'varbit', OID::Bit.new register_type 'float4', OID::Float.new alias_type 'float8', 'float4' diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 6329733abc..40a3b82839 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -32,7 +32,7 @@ module ActiveRecord when 'point' then super(PostgreSQLColumn.point_to_string(value)) else if column.array - "'#{PostgreSQLColumn.array_to_string(value, column, self)}'" + "'#{PostgreSQLColumn.array_to_string(value, column, self).gsub(/'/, "''")}'" else super end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 733d4e1c67..ba053700f2 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -307,9 +307,11 @@ module ActiveRecord id.hash end - # Freeze the attributes hash such that associations are still accessible, even on destroyed records. + # Clone and freeze the attributes hash such that associations are still + # accessible, even on destroyed records, but cloned models will not be + # frozen. def freeze - @attributes.freeze + @attributes = @attributes.clone.freeze self end @@ -350,7 +352,9 @@ module ActiveRecord # Returns the contents of the record as a nicely formatted string. def inspect - inspection = if @attributes + # We check defined?(@attributes) not to issue warnings if the object is + # allocated but not initialized. + inspection = if defined?(@attributes) && @attributes self.class.column_names.collect { |name| if has_attribute?(name) "#{name}: #{attribute_for_inspect(name)}" diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index c615d59725..cd31147414 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -57,24 +57,25 @@ module ActiveRecord class RecordNotDestroyed < ActiveRecordError end - # Raised when SQL statement cannot be executed by the database (for example, it's often the case for - # MySQL when Ruby driver used is too old). + # Superclass for all database execution errors. + # + # Wraps the underlying database error as +original_exception+. class StatementInvalid < ActiveRecordError + attr_reader :original_exception + + def initialize(message, original_exception = nil) + super(message) + @original_exception = original_exception + end end # Raised when SQL statement is invalid and the application gets a blank result. class ThrowResult < ActiveRecordError end - # Parent class for all specific exceptions which wrap database driver exceptions - # provides access to the original exception also. + # Defunct wrapper class kept for compatibility. + # +StatementInvalid+ wraps the original exception now. class WrappedDatabaseException < StatementInvalid - attr_reader :original_exception - - def initialize(message, original_exception) - super(message) - @original_exception = original_exception - end end # Raised when a record cannot be inserted because it would violate a uniqueness constraint. diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index 15736575a2..e65dab07ba 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -1,22 +1,22 @@ require 'active_support/lazy_load_hooks' +require 'active_record/explain_registry' module ActiveRecord module Explain - # Relation#explain needs to be able to collect the queries. + # Executes the block with the collect flag enabled. Queries are collected + # asynchronously by the subscriber and returned. def collecting_queries_for_explain # :nodoc: - current = Thread.current - original, current[:available_queries_for_explain] = current[:available_queries_for_explain], [] + ExplainRegistry.collect = true yield - return current[:available_queries_for_explain] + ExplainRegistry.queries ensure - # Note that the return value above does not depend on this assignment. - current[:available_queries_for_explain] = original + ExplainRegistry.reset end # Makes the adapter execute EXPLAIN for the tuples of queries and bindings. # Returns a formatted string ready to be logged. def exec_explain(queries) # :nodoc: - str = queries && queries.map do |sql, bind| + str = queries.map do |sql, bind| [].tap do |msg| msg << "EXPLAIN for: #{sql}" unless bind.empty? @@ -31,6 +31,7 @@ module ActiveRecord def str.inspect self end + str end end diff --git a/activerecord/lib/active_record/explain_registry.rb b/activerecord/lib/active_record/explain_registry.rb new file mode 100644 index 0000000000..f5cd57e075 --- /dev/null +++ b/activerecord/lib/active_record/explain_registry.rb @@ -0,0 +1,30 @@ +require 'active_support/per_thread_registry' + +module ActiveRecord + # This is a thread locals registry for EXPLAIN. For example + # + # ActiveRecord::ExplainRegistry.queries + # + # returns the collected queries local to the current thread. + # + # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt> + # for further details. + class ExplainRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_accessor :queries, :collect + + def initialize + reset + end + + def collect? + @collect + end + + def reset + @collect = false + @queries = [] + end + end +end diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb index 0f927496fb..a3bc56d600 100644 --- a/activerecord/lib/active_record/explain_subscriber.rb +++ b/activerecord/lib/active_record/explain_subscriber.rb @@ -1,4 +1,5 @@ require 'active_support/notifications' +require 'active_record/explain_registry' module ActiveRecord class ExplainSubscriber # :nodoc: @@ -7,8 +8,8 @@ module ActiveRecord end def finish(name, id, payload) - if queries = Thread.current[:available_queries_for_explain] - queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload) + if ExplainRegistry.collect? && !ignore_payload?(payload) + ExplainRegistry.queries << payload.values_at(:sql, :binds) end end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 451104106c..6c020e1d57 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -867,11 +867,15 @@ module ActiveRecord alias :current :current_migration def run - target = migrations.detect { |m| m.version == @target_version } - raise UnknownMigrationVersionError.new(@target_version) if target.nil? - unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i)) - target.migrate(@direction) - record_version_state_after_migrating(target.version) + migration = migrations.detect { |m| m.version == @target_version } + raise UnknownMigrationVersionError.new(@target_version) if migration.nil? + unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i)) + begin + execute_migration_in_transaction(migration, @direction) + rescue => e + canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : "" + raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace + end end end @@ -892,10 +896,7 @@ module ActiveRecord Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger begin - ddl_transaction(migration) do - migration.migrate(@direction) - record_version_state_after_migrating(migration.version) - end + execute_migration_in_transaction(migration, @direction) rescue => e canceled_msg = use_transaction?(migration) ? "this and " : "" raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace @@ -932,6 +933,13 @@ module ActiveRecord migrated.include?(migration.version.to_i) end + def execute_migration_in_transaction(migration, direction) + ddl_transaction(migration) do + migration.migrate(direction) + record_version_state_after_migrating(migration.version) + end + end + def target migrations.detect { |m| m.version == @target_version } end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index fbb64ad68d..a8905ed739 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -51,7 +51,7 @@ module ActiveRecord # how this "single-table" inheritance mapping is implemented. def instantiate(record, column_types = {}) klass = discriminate_class_for_record(record) - column_types = klass.decorate_columns(column_types) + column_types = klass.decorate_columns(column_types.dup) klass.allocate.init_with('attributes' => record, 'column_types' => column_types) end @@ -436,23 +436,11 @@ module ActiveRecord # Updates the associated record with values matching those of the instance attributes. # Returns the number of affected rows. def update_record(attribute_names = @attributes.keys) - attributes_with_values = arel_attributes_with_values_for_update(attribute_names) - if attributes_with_values.empty? + attributes_values = arel_attributes_with_values_for_update(attribute_names) + if attributes_values.empty? 0 else - klass = self.class - column_hash = klass.connection.schema_cache.columns_hash klass.table_name - db_columns_with_values = attributes_with_values.map { |attr,value| - real_column = column_hash[attr.name] - [real_column, value] - } - bind_attrs = attributes_with_values.dup - bind_attrs.keys.each_with_index do |column, i| - real_column = db_columns_with_values[i].first - bind_attrs[column] = klass.connection.substitute_at(real_column, i) - end - stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id_was || id)).arel.compile_update(bind_attrs) - klass.connection.update stmt, 'SQL', db_columns_with_values + self.class.unscoped.update_record attributes_values, id, id_was end end diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 902fd90c54..f78ccb01aa 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -8,7 +8,7 @@ module ActiveRecord delegate :find_each, :find_in_batches, :to => :all delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, - :having, :create_with, :uniq, :distinct, :references, :none, :to => :all + :having, :create_with, :uniq, :distinct, :references, :none, :unscope, :to => :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all # Executes a custom SQL query against your database and returns all the results. The results will diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 99117b74c5..e36888d4a8 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -36,6 +36,20 @@ module ActiveRecord rake_tasks do require "active_record/base" + + ActiveRecord::Tasks::DatabaseTasks.env = Rails.env + ActiveRecord::Tasks::DatabaseTasks.db_dir = Rails.application.config.paths["db"].first + ActiveRecord::Tasks::DatabaseTasks.seed_loader = Rails.application + ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration + ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a + ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures' + + if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) + if engine.paths['db/migrate'].existent + ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a + end + end + load "active_record/railties/databases.rake" end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 48febcbc43..92bef09ff5 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -2,14 +2,8 @@ require 'active_record' db_namespace = namespace :db do task :load_config do - ActiveRecord::Base.configurations = Rails.application.config.database_configuration || {} - ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a - - if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) - if engine.paths['db/migrate'].existent - ActiveRecord::Migrator.migrations_paths += engine.paths['db/migrate'].to_a - end - end + ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {} + ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths end namespace :create do @@ -156,7 +150,7 @@ db_namespace = namespace :db do begin puts ActiveRecord::Tasks::DatabaseTasks.collation_current rescue NoMethodError - $stderr.puts 'Sorry, your database adapter is not supported yet, feel free to submit a patch' + $stderr.puts 'Sorry, your database adapter is not supported yet. Feel free to submit a patch.' end end @@ -170,7 +164,7 @@ db_namespace = namespace :db do pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations if pending_migrations.any? - puts "You have #{pending_migrations.size} pending migrations:" + puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}" pending_migrations.each do |pending_migration| puts ' %4d %s' % [pending_migration.version, pending_migration.name] end @@ -184,7 +178,7 @@ db_namespace = namespace :db do desc 'Load the seed data from db/seeds.rb' task :seed do db_namespace['abort_if_pending_migrations'].invoke - Rails.application.load_seed + ActiveRecord::Tasks::DatabaseTasks.load_seed end namespace :fixtures do @@ -192,7 +186,15 @@ db_namespace = namespace :db do task :load => [:environment, :load_config] do require 'active_record/fixtures' - base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten + base_dir = if ENV['FIXTURES_PATH'] + STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " + + "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " + + "instead." + File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten + else + ActiveRecord::Tasks::DatabaseTasks.fixtures_path + end + fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file| @@ -209,7 +211,16 @@ db_namespace = namespace :db do puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label - base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') + base_dir = if ENV['FIXTURES_PATH'] + STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " + + "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " + + "instead." + File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten + else + ActiveRecord::Tasks::DatabaseTasks.fixtures_path + end + + Dir["#{base_dir}/**/*.yml"].each do |file| if data = YAML::load(ERB.new(IO.read(file)).result) data.keys.each do |key| @@ -228,7 +239,7 @@ db_namespace = namespace :db do desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR' task :dump => [:environment, :load_config] do require 'active_record/schema_dumper' - filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" + filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb') File.open(filename, "w:utf-8") do |file| ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) end @@ -237,12 +248,9 @@ db_namespace = namespace :db do desc 'Load a schema.rb file into the database' task :load => [:environment, :load_config] do - file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" - if File.exists?(file) - load(file) - else - abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded} - end + file = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb') + ActiveRecord::Tasks::DatabaseTasks.check_schema_file(file) + load(file) end task :load_if_ruby => ['db:create', :environment] do @@ -253,7 +261,7 @@ db_namespace = namespace :db do desc 'Create a db/schema_cache.dump file.' task :dump => [:environment, :load_config] do con = ActiveRecord::Base.connection - filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump") + filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump") con.schema_cache.clear! con.tables.each { |table| con.schema_cache.add(table) } @@ -262,7 +270,7 @@ db_namespace = namespace :db do desc 'Clear a db/schema_cache.dump file.' task :clear => [:environment, :load_config] do - filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump") + filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump") FileUtils.rm(filename) if File.exists?(filename) end end @@ -272,7 +280,7 @@ db_namespace = namespace :db do namespace :structure do desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql' task :dump => [:environment, :load_config] do - filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") + filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql") current_config = ActiveRecord::Tasks::DatabaseTasks.current_config ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) @@ -286,7 +294,8 @@ db_namespace = namespace :db do # desc "Recreate the databases from the structure.sql file" task :load => [:environment, :load_config] do - filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") + filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql") + ActiveRecord::Tasks::DatabaseTasks.check_schema_file(filename) current_config = ActiveRecord::Tasks::DatabaseTasks.current_config ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename) end @@ -383,5 +392,5 @@ namespace :railties do end end -task 'test:prepare' => ['db:test:prepare', 'db:test:load_schema', 'db:abort_if_pending_migrations'] +task 'test:prepare' => ['db:test:prepare', 'db:test:load', 'db:abort_if_pending_migrations'] diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 56462d355b..913f6f88f2 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -39,7 +39,7 @@ module ActiveRecord reset end - def insert(values) + def insert(values) # :nodoc: primary_key_value = nil if primary_key && Hash === values @@ -56,16 +56,7 @@ module ActiveRecord im = arel.create_insert im.into @table - conn = @klass.connection - - substitutes = values.sort_by { |arel_attr,_| arel_attr.name } - binds = substitutes.map do |arel_attr, value| - [@klass.columns_hash[arel_attr.name], value] - end - - substitutes.each_with_index do |tuple, i| - tuple[1] = conn.substitute_at(binds[i][0], i) - end + substitutes, binds = substitute_values values if values.empty? # empty insert im.values = Arel.sql(connection.empty_insert_statement_value) @@ -73,7 +64,7 @@ module ActiveRecord im.insert substitutes end - conn.insert( + @klass.connection.insert( im, 'SQL', primary_key, @@ -82,6 +73,29 @@ module ActiveRecord binds) end + def update_record(values, id, id_was) # :nodoc: + substitutes, binds = substitute_values values + um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes) + + @klass.connection.update( + um, + 'SQL', + binds) + end + + def substitute_values(values) # :nodoc: + substitutes = values.sort_by { |arel_attr,_| arel_attr.name } + binds = substitutes.map do |arel_attr, value| + [@klass.columns_hash[arel_attr.name], value] + end + + substitutes.each_with_index do |tuple, i| + tuple[1] = @klass.connection.substitute_at(binds[i][0], i) + end + + [substitutes, binds] + end + # Initializes new record from relation while maintaining the current # scope. # diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 1b6239eb38..8d6740246c 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -14,14 +14,14 @@ module ActiveRecord delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :columns_hash, :to => :klass - module ClassSpecificRelation + module ClassSpecificRelation # :nodoc: extend ActiveSupport::Concern included do @delegation_mutex = Mutex.new end - module ClassMethods + module ClassMethods # :nodoc: def name superclass.name end @@ -70,7 +70,7 @@ module ActiveRecord end end - module ClassMethods + module ClassMethods # :nodoc: @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2) def new(klass, *args) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 6ee3711052..9fcd2d06c5 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -551,7 +551,6 @@ module ActiveRecord # Order.having('SUM(price) > 30').group('user_id') def having(opts, *rest) opts.blank? ? self : spawn.having!(opts, *rest) - spawn.having!(opts, *rest) end def having!(opts, *rest) # :nodoc: diff --git a/activerecord/lib/active_record/runtime_registry.rb b/activerecord/lib/active_record/runtime_registry.rb index 17890dd29f..63e6738622 100644 --- a/activerecord/lib/active_record/runtime_registry.rb +++ b/activerecord/lib/active_record/runtime_registry.rb @@ -1,7 +1,7 @@ require 'active_support/per_thread_registry' module ActiveRecord - # This is a thread locals registry for Active Record. For example + # This is a thread locals registry for Active Record. For example: # # ActiveRecord::RuntimeRegistry.connection_handler # @@ -9,7 +9,7 @@ module ActiveRecord # # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt> # for further details. - class RuntimeRegistry + class RuntimeRegistry # :nodoc: extend ActiveSupport::PerThreadRegistry attr_accessor :connection_handler, :sql_runtime, :connection_id diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 36133bab4c..3e8b79c7a0 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -3,10 +3,41 @@ module ActiveRecord class DatabaseAlreadyExists < StandardError; end # :nodoc: class DatabaseNotSupported < StandardError; end # :nodoc: - module DatabaseTasks # :nodoc: + # <tt>ActiveRecord::Tasks::DatabaseTasks</tt> is a utility class, which encapsulates + # logic behind common tasks used to manage database and migrations. + # + # The tasks defined here are used in rake tasks provided by Active Record. + # + # In order to use DatabaseTasks, a few config values need to be set. All the needed + # config values are set by Rails already, so it's necessary to do it only if you + # want to change the defaults or when you want to use Active Record outside of Rails + # (in such case after configuring the database tasks, you can also use the rake tasks + # defined in Active Record). + # + # + # The possible config values are: + # + # * +env+: current environment (like Rails.env). + # * +database_configuration+: configuration of your databases (as in +config/database.yml+). + # * +db_dir+: your +db+ directory. + # * +fixtures_path+: a path to fixtures directory. + # * +migrations_paths+: a list of paths to directories with migrations. + # * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method. + # + # Example usage of +DatabaseTasks+ outside Rails could look as such: + # + # include ActiveRecord::Tasks + # DatabaseTasks.database_configuration = YAML.load(File.read('my_database_config.yml')) + # DatabaseTasks.db_dir = 'db' + # # other settings... + # + # DatabaseTasks.create_current('production') + module DatabaseTasks extend self attr_writer :current_config + attr_accessor :database_configuration, :migrations_paths, :seed_loader, :db_dir, + :fixtures_path, :env LOCAL_HOSTS = ['127.0.0.1', 'localhost'] @@ -24,7 +55,7 @@ module ActiveRecord register_task(/(oci|oracle)/, ActiveRecord::Tasks::OracleDatabaseTasks) def current_config(options = {}) - options.reverse_merge! :env => Rails.env + options.reverse_merge! :env => env if options.has_key?(:config) @current_config = options[:config] else @@ -50,7 +81,7 @@ module ActiveRecord each_local_configuration { |configuration| create configuration } end - def create_current(environment = Rails.env) + def create_current(environment = env) each_current_configuration(environment) { |configuration| create configuration } @@ -73,7 +104,7 @@ module ActiveRecord each_local_configuration { |configuration| drop configuration } end - def drop_current(environment = Rails.env) + def drop_current(environment = env) each_current_configuration(environment) { |configuration| drop configuration } @@ -83,7 +114,7 @@ module ActiveRecord drop database_url_config end - def charset_current(environment = Rails.env) + def charset_current(environment = env) charset ActiveRecord::Base.configurations[environment] end @@ -92,7 +123,7 @@ module ActiveRecord class_for_adapter(configuration['adapter']).new(*arguments).charset end - def collation_current(environment = Rails.env) + def collation_current(environment = env) collation ActiveRecord::Base.configurations[environment] end @@ -117,6 +148,24 @@ module ActiveRecord class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename) end + def check_schema_file(filename) + unless File.exists?(filename) + message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.} + message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails) + Kernel.abort message + end + end + + def load_seed + if seed_loader + seed_loader.load_seed + else + raise "You tried to load seed data, but no seed loader is specified. Please specify seed " + + "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" + + "Seed loader should respond to load_seed method" + end + end + private def database_url_config @@ -134,7 +183,7 @@ module ActiveRecord def each_current_configuration(environment) environments = [environment] - environments << 'test' if environment.development? + environments << 'test' if environment == 'development' configurations = ActiveRecord::Base.configurations.values_at(*environments) configurations.compact.each do |configuration| diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index 0b1b030516..4413330fab 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -59,7 +59,7 @@ module ActiveRecord def structure_load(filename) set_psql_env - Kernel.system("psql -f #{filename} #{configuration['database']}") + Kernel.system("psql -q -f #{filename} #{configuration['database']}") end private diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index f2b041ad97..de5fd05468 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -1,7 +1,7 @@ module ActiveRecord # Returns the version of the currently loaded ActiveRecord as a Gem::Version def self.version - Gem::Version.new "4.0.0.beta1" + Gem::Version.new "4.1.0.beta" end module VERSION #:nodoc: diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 8774bf626f..61a3a2ba0f 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -81,6 +81,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_cycle(['1',nil,nil]) end + def test_insert_fixture + tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"] + @connection.insert_fixture({"tags" => tag_values}, "pg_arrays" ) + assert_equal(PgArray.last.tags, tag_values) + end + private def assert_cycle array # test creation diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb new file mode 100644 index 0000000000..d7d77f96e2 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -0,0 +1,87 @@ +# encoding: utf-8 + +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlByteaTest < ActiveRecord::TestCase + class ByteaDataType < ActiveRecord::Base + self.table_name = 'bytea_data_type' + end + + def setup + @connection = ActiveRecord::Base.connection + begin + @connection.transaction do + @connection.create_table('bytea_data_type') do |t| + t.binary 'payload' + end + end + end + @column = ByteaDataType.columns.find { |c| c.name == 'payload' } + assert(@column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn)) + end + + def teardown + @connection.execute 'drop table if exists bytea_data_type' + end + + def test_column + assert_equal :binary, @column.type + end + + def test_type_cast_binary_converts_the_encoding + assert @column + + data = "\u001F\x8B" + assert_equal('UTF-8', data.encoding.name) + assert_equal('ASCII-8BIT', @column.type_cast(data).encoding.name) + end + + def test_type_cast_binary_value + data = "\u001F\x8B".force_encoding("BINARY") + assert_equal(data, @column.type_cast(data)) + end + + def test_type_case_nil + assert_equal(nil, @column.type_cast(nil)) + end + + def test_read_value + data = "\u001F" + @connection.execute "insert into bytea_data_type (payload) VALUES ('#{data}')" + record = ByteaDataType.first + assert_equal(data, record.payload) + record.delete + end + + def test_read_nil_value + @connection.execute "insert into bytea_data_type (payload) VALUES (null)" + record = ByteaDataType.first + assert_equal(nil, record.payload) + record.delete + end + + def test_write_value + data = "\u001F" + record = ByteaDataType.create(payload: data) + refute record.new_record? + assert_equal(data, record.payload) + end + + def test_write_binary + data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log')) + assert(data.size > 1) + record = ByteaDataType.create(payload: data) + refute record.new_record? + assert_equal(data, record.payload) + assert_equal(data, ByteaDataType.where(id: record.id).first.payload) + end + + def test_write_nil + record = ByteaDataType.create(payload: nil) + refute record.new_record? + assert_equal(nil, record.payload) + assert_equal(nil, ByteaDataType.where(id: record.id).first.payload) + end +end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 1e6ae85a25..8c17372286 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -545,13 +545,19 @@ _SQL def test_update_bit_string new_bit_string = '11111111' - new_bit_string_varying = '11111110' + new_bit_string_varying = '0xFF' assert @first_bit_string.bit_string = new_bit_string assert @first_bit_string.bit_string_varying = new_bit_string_varying assert @first_bit_string.save assert @first_bit_string.reload - assert_equal new_bit_string, @first_bit_string.bit_string - assert_equal new_bit_string_varying, @first_bit_string.bit_string_varying + assert_equal @first_bit_string.bit_string, new_bit_string + assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying + end + + def test_invalid_hex_string + new_bit_string = 'FF' + @first_bit_string.bit_string = new_bit_string + assert_raise(ActiveRecord::StatementInvalid) { assert @first_bit_string.save } end def test_update_oid diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb index 619d581d5f..0b61f61572 100644 --- a/activerecord/test/cases/adapters/postgresql/explain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -22,13 +22,6 @@ module ActiveRecord assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain assert_match %(Seq Scan on audit_logs), explain end - - def test_dont_explain_for_set_search_path - queries = Thread.current[:available_queries_for_explain] = [] - ActiveRecord::Base.connection.schema_search_path = "public" - assert queries.empty? - end - end end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 781b87741d..d85570236f 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -755,6 +755,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal topic.replies.to_a.size, topic.replies_count end + def test_pushing_association_updates_counter_cache + topic = Topic.order("id ASC").first + reply = Reply.create! + + assert_difference "topic.reload.replies_count", 1 do + topic.replies << reply + end + end + def test_deleting_updates_counter_cache_without_dependent_option post = posts(:welcome) diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 4ed09a3bf7..0e48fbca9c 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -522,4 +522,20 @@ class HasOneAssociationsTest < ActiveRecord::TestCase account = Account.find(2) assert_queries { company.account = account } end + + def test_has_one_assignment_triggers_save_on_change + pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + ship = pirate.build_ship(name: 'old name') + ship.save! + + ship.name = 'new name' + assert ship.changed? + assert_queries(2) do + # One query for updating name and second query for updating pirate_id + pirate.ship = ship + end + + assert_equal 'new name', pirate.ship.reload.name + end + end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 387c741762..d9c032392d 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -141,13 +141,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_respond_to topic, :title end - # IRB inspects the return value of "MyModel.allocate" - # by inspecting it. + # IRB inspects the return value of "MyModel.allocate". def test_allocated_object_can_be_inspected topic = Topic.allocate - topic.instance_eval { @attributes = nil } - assert_nothing_raised { topic.inspect } - assert topic.inspect, "#<Topic not initialized>" + assert_equal "#<Topic not initialized>", topic.inspect end def test_array_content diff --git a/activerecord/test/cases/clone_test.rb b/activerecord/test/cases/clone_test.rb index d91646efca..5e43082c33 100644 --- a/activerecord/test/cases/clone_test.rb +++ b/activerecord/test/cases/clone_test.rb @@ -29,5 +29,12 @@ module ActiveRecord topic.author_name = 'Aaron' assert_equal 'Aaron', cloned.author_name end + + def test_freezing_a_cloned_model_does_not_freeze_clone + cloned = Topic.new + clone = cloned.clone + cloned.freeze + assert_not clone.frozen? + end end end diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb index 1fd64dd0af..eb2fe5639b 100644 --- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb +++ b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb @@ -2,6 +2,15 @@ require "cases/helper" module ActiveRecord module ConnectionAdapters + class ConnectionPool + def insert_connection_for_test!(c) + synchronize do + @connections << c + @available.add c + end + end + end + class AbstractAdapterTest < ActiveRecord::TestCase attr_reader :adapter diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 7b2034dadf..36b87033ae 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -213,9 +213,11 @@ class DirtyTest < ActiveRecord::TestCase topic = target.create assert_nil topic.written_on - topic.written_on = "" - assert_nil topic.written_on - assert !topic.written_on_changed? + ["", nil].each do |value| + topic.written_on = value + assert_nil topic.written_on + assert !topic.written_on_changed? + end end end diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb index b425967678..fb53a92c89 100644 --- a/activerecord/test/cases/explain_subscriber_test.rb +++ b/activerecord/test/cases/explain_subscriber_test.rb @@ -1,55 +1,54 @@ require 'cases/helper' +require 'active_record/explain_subscriber' +require 'active_record/explain_registry' if ActiveRecord::Base.connection.supports_explain? class ExplainSubscriberTest < ActiveRecord::TestCase SUBSCRIBER = ActiveRecord::ExplainSubscriber.new - def test_collects_nothing_if_available_queries_for_explain_is_nil - with_queries(nil) do - SUBSCRIBER.finish(nil, nil, {}) - assert_nil Thread.current[:available_queries_for_explain] - end + def setup + ActiveRecord::ExplainRegistry.reset + ActiveRecord::ExplainRegistry.collect = true end def test_collects_nothing_if_the_payload_has_an_exception - with_queries([]) do |queries| - SUBSCRIBER.finish(nil, nil, :exception => Exception.new) - assert queries.empty? - end + SUBSCRIBER.finish(nil, nil, exception: Exception.new) + assert queries.empty? end def test_collects_nothing_for_ignored_payloads - with_queries([]) do |queries| - ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip| - SUBSCRIBER.finish(nil, nil, :name => ip) - end - assert queries.empty? + ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip| + SUBSCRIBER.finish(nil, nil, name: ip) end + assert queries.empty? + end + + def test_collects_nothing_if_collect_is_false + ActiveRecord::ExplainRegistry.collect = false + SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select 1 from users', binds: [1, 2]) + assert queries.empty? end def test_collects_pairs_of_queries_and_binds sql = 'select 1 from users' binds = [1, 2] - with_queries([]) do |queries| - SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => sql, :binds => binds) - assert_equal 1, queries.size - assert_equal sql, queries[0][0] - assert_equal binds, queries[0][1] - end + SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: sql, binds: binds) + assert_equal 1, queries.size + assert_equal sql, queries[0][0] + assert_equal binds, queries[0][1] end - def test_collects_nothing_if_unexplained_sqls - with_queries([]) do |queries| - SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => 'SHOW max_identifier_length') - assert queries.empty? - end + def test_collects_nothing_if_the_statement_is_not_whitelisted + SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'SHOW max_identifier_length') + assert queries.empty? + end + + def teardown + ActiveRecord::ExplainRegistry.reset end - def with_queries(queries) - Thread.current[:available_queries_for_explain] = queries - yield queries - ensure - Thread.current[:available_queries_for_explain] = nil + def queries + ActiveRecord::ExplainRegistry.queries end end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index e505fe9f18..557cc7e7a0 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -31,6 +31,13 @@ class FinderTest < ActiveRecord::TestCase assert_equal(topics(:first).title, Topic.find(1).title) end + def test_symbols_table_ref + Post.first # warm up + x = Symbol.all_symbols.count + Post.where("title" => {"xxxqqqq" => "bar"}) + assert_equal x, Symbol.all_symbols.count + end + # find should handle strings that come from URLs # (example: Category.find(params[:id])) def test_find_with_string @@ -833,6 +840,8 @@ class FinderTest < ActiveRecord::TestCase rescue ActiveRecord::RecordNotFound => e assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message end + ensure + Toy.reset_primary_key end def test_finder_with_offset_string diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index f8afb7c591..193ffb26e3 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -258,6 +258,32 @@ class MigrationTest < ActiveRecord::TestCase "On error, the Migrator should revert schema changes but it did not." end + def test_migrator_one_up_with_exception_and_rollback_using_run + unless ActiveRecord::Base.connection.supports_ddl_transactions? + skip "not supported on #{ActiveRecord::Base.connection.class}" + end + + assert_not Person.column_methods_hash.include?(:last_name) + + migration = Class.new(ActiveRecord::Migration) { + def version; 100 end + def migrate(x) + add_column "people", "last_name", :string + raise 'Something broke' + end + }.new + + migrator = ActiveRecord::Migrator.new(:up, [migration], 100) + + e = assert_raise(StandardError) { migrator.run } + + assert_equal "An error has occurred, this migration was canceled:\n\nSomething broke", e.message + + Person.reset_column_information + assert_not Person.column_methods_hash.include?(:last_name), + "On error, the Migrator should revert schema changes but it did not." + end + def test_migration_without_transaction unless ActiveRecord::Base.connection.supports_ddl_transactions? skip "not supported on #{ActiveRecord::Base.connection.class}" diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 34ecdb3cc9..482c1b3d48 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -185,6 +185,14 @@ module ActiveRecord assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length end + def test_respond_to_for_non_selected_element + post = Post.select(:title).first + assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception" + + post = Post.select("'title' as post_title").first + assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception" + end + end class RelationMutationTest < ActiveSupport::TestCase diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 9008c2785e..cf6af4e8f4 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1299,6 +1299,14 @@ class RelationTest < ActiveRecord::TestCase assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name) end + def test_doesnt_add_having_values_if_options_are_blank + scope = Post.having('') + assert_equal [], scope.having_values + + scope = Post.having([]) + assert_equal [], scope.having_values + end + def test_references_triggers_eager_loading scope = Post.includes(:comments) assert !scope.eager_loading? diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index 5a65ad5dfa..0f69443839 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -198,6 +198,16 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end + def test_unscope_and_scope + developer_klass = Class.new(Developer) do + scope :by_name, -> name { unscope(where: :name).where(name: name) } + end + + expected = developer_klass.where(name: 'Jamis').collect { |dev| [dev.name, dev.id] } + received = developer_klass.where(name: 'David').by_name('Jamis').collect { |dev| [dev.name, dev.id] } + assert_equal expected, received + end + def test_unscope_errors_with_invalid_value assert_raises(ArgumentError) do Developer.includes(:projects).where(name: "Jamis").unscope(:stupidly_incorrect_value) diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 726338db14..765e92ccca 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -1,5 +1,6 @@ require 'cases/helper' require 'models/topic' +require 'models/reply' require 'models/person' require 'models/traffic_light' require 'bcrypt' @@ -241,4 +242,17 @@ class SerializedAttributeTest < ActiveRecord::TestCase assert_equal [], light.state assert_equal [], light.long_state end + + def test_serialized_column_should_not_be_wrapped_twice + Topic.serialize(:content, MyObject) + + myobj = MyObject.new('value1', 'value2') + Topic.create(content: myobj) + Topic.create(content: myobj) + + Topic.all.each do |topic| + type = topic.instance_variable_get("@columns_hash")["content"] + assert !type.instance_variable_get("@column").is_a?(ActiveRecord::AttributeMethods::Serialization::Type) + end + end end diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 3bfbc92afd..e9000fef25 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -305,4 +305,11 @@ module ActiveRecord end end end + + class DatabaseTasksCheckSchemaFileTest < ActiveRecord::TestCase + def test_check_schema_file + Kernel.expects(:abort).with(regexp_matches(/awesome-file.sql/)) + ActiveRecord::Tasks::DatabaseTasks.check_schema_file("awesome-file.sql") + end + end end diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb index 7e7a469edd..f31896bc7f 100644 --- a/activerecord/test/cases/tasks/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -227,7 +227,7 @@ module ActiveRecord def test_structure_load filename = "awesome-file.sql" - Kernel.expects(:system).with("psql -f #{filename} my-app-db") + Kernel.expects(:system).with("psql -q -f #{filename} my-app-db") ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) end diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 9d84f64c96..ff1b01556d 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -176,6 +176,28 @@ class TimestampTest < ActiveRecord::TestCase assert_not_equal time, owner.updated_at end + def test_touching_a_record_touches_polymorphic_record + klass = Class.new(ActiveRecord::Base) do + def self.name; 'Toy'; end + end + + wheel_klass = Class.new(ActiveRecord::Base) do + def self.name; 'Wheel'; end + belongs_to :wheelable, :polymorphic => true, :touch => true + end + + toy = klass.first + time = 3.days.ago + toy.update_columns(updated_at: time) + + wheel = wheel_klass.new + wheel.wheelable = toy + wheel.save + wheel.touch + + assert_not_equal time, toy.updated_at + end + def test_changing_parent_of_a_record_touches_both_new_and_old_parent_record klass = Class.new(ActiveRecord::Base) do def self.name; 'Toy'; end @@ -202,6 +224,38 @@ class TimestampTest < ActiveRecord::TestCase assert_not_equal time, old_pet.updated_at end + def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record + klass = Class.new(ActiveRecord::Base) do + def self.name; 'Toy'; end + end + + wheel_klass = Class.new(ActiveRecord::Base) do + def self.name; 'Wheel'; end + belongs_to :wheelable, :polymorphic => true, :touch => true + end + + toy1 = klass.find(1) + toy2 = klass.find(2) + + wheel = wheel_klass.new + wheel.wheelable = toy1 + wheel.save! + + time = 3.days.ago.at_beginning_of_hour + + toy1.update_columns(updated_at: time) + toy2.update_columns(updated_at: time) + + wheel.wheelable = toy2 + wheel.save! + + toy1.reload + toy2.reload + + assert_not_equal time, toy1.updated_at + assert_not_equal time, toy2.updated_at + end + def test_clearing_association_touches_the_old_record klass = Class.new(ActiveRecord::Base) do def self.name; 'Toy'; end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 545a9ec0af..7f2e776825 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,501 +1,3 @@ -## Rails 4.0.0 (unreleased) ## +* No changes. -* `Class#class_attribute` accepts an `instance_predicate` option which - defaults to `true`. If set to `false` the predicate method will not - be defined. - - *Agis Anastasopoulos* - -* `fast_xs` support has been removed. Use `String#encode(xml: :attr)`. - -* `ActiveSupport::Notifications::Instrumenter#instrument` should - yield its payload. - - *stopdropandrew* - -* `ActiveSupport::TimeWithZone` raises `NoMethodError` in proper context. - Fixes #9772. - - *Yves Senn* - -* Fix deletion of empty directories in `ActiveSupport::Cache::FileStore`. - - *Charles Jones* - - -## Rails 4.0.0.beta1 (February 25, 2013) ## - -* Improve singularizing a singular for multiple cases. - Fixes #2608 #1825 #2395. - - Example: - - # Before - 'address'.singularize # => 'addres' - - # After - 'address'.singularize # => 'address' - - *Mark McSpadden* - -* Prevent `DateTime#change` from truncating the second fraction, when seconds - do not need to be changed. - - *Chris Baynes* - -* Added `ActiveSupport::TimeWithZone#to_r` for `Time#at` compatibility. - - Before this change: - - Time.zone = 'Tokyo' - time = Time.zone.now - time == Time.at(time) # => false - - After the change: - - Time.zone = 'Tokyo' - time = Time.zone.now - time == Time.at(time) # => true - - *stopdropandrew* - -* `ActiveSupport::NumberHelper#number_to_human` returns the number unaltered when - the given units hash does not contain the needed key, e.g. when the number provided - is less than the largest key provided. - Fixes #9269. - - Examples: - - number_to_human(123, units: {}) # => 123 - number_to_human(123, units: { thousand: 'k' }) # => 123 - - *Michael Hoffman* - -* Added `beginning_of_minute` support to core ext calculations for `Time` and `DateTime`. - - *Gagan Awhad* - -* Add `:nsec` date format. - - *Jamie Gaskins* - -* `ActiveSupport::Gzip.compress` allows two optional arguments for compression - level and strategy. - - *Beyond* - -* Modify `TimeWithZone#as_json` to include 3 decimal places of sub-second accuracy - by default, which is optional as per the ISO8601 spec, but extremely useful. Also - the default behaviour of `Date#toJSON()` in recent versions of Chrome, Safari and - Firefox. - - *James Harton* - -* Improve `String#squish` to handle Unicode whitespace. *Antoine Lyset* - -* Standardise on `to_time` returning an instance of `Time` in the local system timezone - across `String`, `Time`, `Date`, `DateTime` and `ActiveSupport::TimeWithZone`. - - *Andrew White* - -* Extract `ActiveSupport::Testing::Performance` into https://github.com/rails/rails-perftest - You can add the gem to your `Gemfile` to keep using performance tests. - - 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 `Float::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. - - before_save :foo, :bar, :foo - - will at first call `bar`, then `foo`. `foo` will no more be called - twice. - - *Dmitriy Kiriyenko* - -* Add `ActiveSupport::Logger#silence` that works the same as the old `Logger#silence` extension. - - *DHH* - -* Remove surrogate unicode character encoding from `ActiveSupport::JSON.encode` - The encoding scheme was broken for unicode characters outside the basic multilingual plane; - since json is assumed to be UTF-8, and we already force the encoding to UTF-8, - simply pass through the un-encoded characters. - - *Brett Carter* - -* Deprecate `Time.time_with_date_fallback`, `Time.utc_time` and `Time.local_time`. - These methods were added to handle the limited range of Ruby's native `Time` - implementation. Those limitations no longer apply so we are deprecating them in 4.0 - and they will be removed in 4.1. - - *Andrew White* - -* Deprecate `Date#to_time_in_current_zone` and add `Date#in_time_zone`. *Andrew White* - -* Add `String#in_time_zone` method to convert a string to an `ActiveSupport::TimeWithZone`. *Andrew White* - -* Deprecate `ActiveSupport::BasicObject` in favor of `ActiveSupport::ProxyObject`. - This class is used for proxy classes. It avoids confusion with Ruby's `BasicObject` - class. - - *Francesco Rodriguez* - -* Patched `Marshal#load` to work with constant autoloading. Fixes autoloading - with cache stores that rely on `Marshal` (`MemCacheStore` and `FileStore`). - Fixes #8167. - - *Uriel Katz* - -* Make `Time.zone.parse` to work with JavaScript format date strings. *Andrew White* - -* Add `DateTime#seconds_until_end_of_day` and `Time#seconds_until_end_of_day` - as a complement for `seconds_from_midnight`; useful when setting expiration - times for caches, e.g.: - - <% cache('dashboard', expires_in: Date.current.seconds_until_end_of_day) do %> - ... - - *Olek Janiszewski* - -* No longer proxy `ActiveSupport::Multibyte#class`. *Steve Klabnik* - -* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from minitest instead. *Carlos Antonio da Silva* - -* `XmlMini.with_backend` now may be safely used with threads: - - Thread.new do - XmlMini.with_backend("REXML") { rexml_power } - end - Thread.new do - XmlMini.with_backend("LibXML") { libxml_power } - end - - Each thread will use it's own backend. - - *Nikita Afanasenko* - -* Dependencies no longer trigger `Kernel#autoload` in `remove_constant`. Fixes #8213. *Xavier Noria* - -* Simplify `mocha` integration and remove monkey-patches, bumping `mocha` to 0.13.0. *James Mead* - -* `#as_json` isolates options when encoding a hash. Fixes #8182. - - *Yves Senn* - -* Deprecate `Hash#diff` in favor of minitest's #diff. *Steve Klabnik* - -* `Kernel#capture` can catch output from subprocesses. *Dmitry Vorotilin* - -* `to_xml` conversions now use builder's `tag!` method instead of explicit invocation of `method_missing`. - - *Nikita Afanasenko* - -* Fixed timezone mapping of the Solomon Islands. *Steve Klabnik* - -* Make callstack attribute optional in `ActiveSupport::Deprecation::Reporting` - methods `warn` and `deprecation_warning`. - - *Alexey Gaziev* - -* Implement `HashWithIndifferentAccess#replace` so `key?` works correctly. *David Graham* - -* Handle the possible permission denied errors `atomic.rb` might trigger due to its `chown` - and `chmod` calls. - - *Daniele Sluijters* - -* `Hash#extract!` returns only those keys that present in the receiver. - - {a: 1, b: 2}.extract!(:a, :x) # => {:a => 1} - - *Mikhail Dieterle* - -* `Hash#extract!` returns the same subclass, that the receiver is. I.e. - `HashWithIndifferentAccess#extract!` returns a `HashWithIndifferentAccess` instance. - - *Mikhail Dieterle* - -* Optimize `ActiveSupport::Cache::Entry` to reduce memory and processing overhead. *Brian Durand* - -* Tests tag the Rails log with the current test class and test case: - - [SessionsControllerTest] [test_0002_sign in] Processing by SessionsController#create as HTML - [SessionsControllerTest] [test_0002_sign in] ... - - *Jeremy Kemper* - -* Add `logger.push_tags` and `.pop_tags` to complement `logger.tagged`: - - class Job - def before - Rails.logger.push_tags :jobs, self.class.name - end - - def after - Rails.logger.pop_tags 2 - end - end - - *Jeremy Kemper* - -* Allow delegation to the class using the `:class` keyword, replacing - `self.class` usage: - - class User - def self.hello - "world" - end - - delegate :hello, to: :class - end - - *Marc-Andre Lafortune* - -* `Date.beginning_of_week` thread local and `beginning_of_week` application - config option added (default is Monday). - - *Innokenty Mikhailov* - -* An optional block can be passed to `config_accessor` to set its default value - - class User - include ActiveSupport::Configurable - - config_accessor :hair_colors do - [:brown, :black, :blonde, :red] - end - end - - User.hair_colors # => [:brown, :black, :blonde, :red] - - *Larry Lv* - -* `ActiveSupport::Benchmarkable#silence` has been deprecated due to its lack of - thread safety. It will be removed without replacement in Rails 4.1. - - *Steve Klabnik* - -* An optional block can be passed to `Hash#deep_merge`. The block will be invoked - for each duplicated key and used to resolve the conflict. - - *Pranas Kiziela* - -* `ActiveSupport::Deprecation` is now a class. It is possible to create an instance - of deprecator. Backwards compatibility has been preserved. - - You can choose which instance of the deprecator will be used. - - deprecate :method_name, deprecator: deprecator_instance - - You can use `ActiveSupport::Deprecation` in your gem. - - require 'active_support/deprecation' - require 'active_support/core_ext/module/deprecation' - - class MyGem - def self.deprecator - ActiveSupport::Deprecation.new('2.0', 'MyGem') - end - - def old_method - end - - def new_method - end - - deprecate old_method: :new_method, deprecator: deprecator - end - - MyGem.new.old_method - # => DEPRECATION WARNING: old_method is deprecated and will be removed from MyGem 2.0 (use new_method instead). (called from <main> at file.rb:18) - - *Piotr Niełacny & Robert Pankowecki* - -* `ERB::Util.html_escape` encodes single quote as `#39`. Decimal form has better support in old browsers. *Kalys Osmonov* - -* `ActiveSupport::Callbacks`: deprecate monkey patch of object callbacks. - Using the `filter` method like this: - - before_filter MyFilter.new - - class MyFilter - def filter(controller) - end - end - - Is now deprecated with recommendation to use the corresponding filter type - (`#before`, `#after` or `#around`): - - before_filter MyFilter.new - - class MyFilter - def before(controller) - end - end - - *Bogdan Gusiev* - -* An optional block can be passed to `HashWithIndifferentAccess#update` and `#merge`. - The block will be invoked for each duplicated key, and used to resolve the conflict, - thus replicating the behaviour of the corresponding methods on the `Hash` class. - - *Leo Cassarani* - -* Remove `j` alias for `ERB::Util#json_escape`. - The `j` alias is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript` - and both modules are included in the view context that would confuse the developers. - - *Akira Matsuda* - -* Replace deprecated `memcache-client` gem with `dalli` in `ActiveSupport::Cache::MemCacheStore`. - - *Guillermo Iguaran* - -* Add default values to all `ActiveSupport::NumberHelper` methods, to avoid - errors with empty locales or missing values. - - *Carlos Antonio da Silva* - -* `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and - `#encode_json` methods for custom JSON string literals. - - *Erich Menge* - -* Add `String#indent`. *fxn & Ace Suares* - -* Inflections can now be defined per locale. `singularize` and `pluralize` - accept locale as an extra argument. - - *David Celis* - -* `Object#try` will now return `nil` instead of raise a `NoMethodError` if the - receiving object does not implement the method, but you can still get the - old behavior by using the new `Object#try!`. - - *DHH* - -* `ERB::Util.html_escape` now escapes single quotes. *Santiago Pastorino* - -* `Time#change` now works with time values with offsets other than UTC or the local time zone. *Andrew White* - -* `ActiveSupport::Callbacks`: deprecate usage of filter object with `#before` and `#after` methods as `around` callback. *Bogdan Gusiev* - -* Add `Time#prev_quarter` and `Time#next_quarter` short-hands for `months_ago(3)` and `months_since(3)`. *SungHee Kang* - -* Remove obsolete and unused `require_association` method from dependencies. *fxn* - -* Add `:instance_accessor` option for `config_accessor`. - - class User - include ActiveSupport::Configurable - config_accessor :allowed_access, instance_accessor: false - end - - User.new.allowed_access = true # => NoMethodError - User.new.allowed_access # => NoMethodError - - *Francesco Rodriguez* - -* `ActionView::Helpers::NumberHelper` methods have been moved to `ActiveSupport::NumberHelper` and are now available via - `Numeric#to_s`. `Numeric#to_s` now accepts the formatting options `:phone`, `:currency`, `:percentage`, `:delimited`, - `:rounded`, `:human`, and `:human_size`. - - *Andrew Mutz* - -* Add `Hash#transform_keys`, `Hash#transform_keys!`, `Hash#deep_transform_keys`, and `Hash#deep_transform_keys!`. *Mark McSpadden* - -* Changed XML type `datetime` to `dateTime` (with upper case letter `T`). *Angelo Capilleri* - -* Add `:instance_accessor` option for `class_attribute`. *Alexey Vakhov* - -* `constantize` now looks in the ancestor chain. *Marc-Andre Lafortune & Andrew White* - -* Adds `Hash#deep_stringify_keys` and `Hash#deep_stringify_keys!` to convert all keys from a `Hash` instance into strings. *Lucas Húngaro* - -* Adds `Hash#deep_symbolize_keys` and `Hash#deep_symbolize_keys!` to convert all keys from a `Hash` instance into symbols. *Lucas Húngaro* - -* `Object#try` can't call private methods. *Vasiliy Ermolovich* - -* `AS::Callbacks#run_callbacks` remove `key` argument. *Francesco Rodriguez* - -* `deep_dup` works more expectedly now and duplicates also values in `Hash` instances and elements in `Array` instances. *Alexey Gaziev* - -* Inflector no longer applies ice -> ouse to words like "slice", "police", etc. *Wes Morgan* - -* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations. *twinturbo* - -* Make `Module#delegate` stop using `send` - can no longer delegate to private methods. *dasch* - -* `ActiveSupport::Callbacks`: deprecate `:rescuable` option. *Bogdan Gusiev* - -* Adds `Integer#ordinal` to get the ordinal suffix string of an integer. *Tim Gildea* - -* `ActiveSupport::Callbacks`: `:per_key` option is no longer supported. *Bogdan Gusiev* - -* `ActiveSupport::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option. *Bogdan Gusiev* - -* Add `html_escape_once` to `ERB::Util`, and delegate the `escape_once` tag helper to it. *Carlos Antonio da Silva* - -* Deprecates the compatibility method `Module#local_constant_names`, - use `Module#local_constants` instead (which returns symbols). *Xavier Noria* - -* Deletes the compatibility method `Module#method_names`, - use `Module#methods` from now on (which returns symbols). *Xavier Noria* - -* Deletes the compatibility method `Module#instance_method_names`, - use `Module#instance_methods` from now on (which returns symbols). *Xavier Noria* - -* `BufferedLogger` is deprecated. Use `ActiveSupport::Logger`, or the logger - from the Ruby standard library. - - *Aaron Patterson* - -* Unicode database updated to 6.1.0. *Norman Clarke* - -* Adds `encode_big_decimal_as_string` option to force JSON serialization of `BigDecimal` as numeric instead - of wrapping them in strings for safety. - -* Optimize log subscribers to check log level before doing any processing. *Brian Durand* - -Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activesupport/CHANGELOG.md) for previous changes. +Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 6bfac15289..6c220ae625 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -56,16 +56,7 @@ module ActiveSupport case store when Symbol - store_class_name = store.to_s.camelize - store_class = - begin - require "active_support/cache/#{store}" - rescue LoadError => e - raise "Could not find cache store adapter for #{store} (#{e})" - else - ActiveSupport::Cache.const_get(store_class_name) - end - store_class.new(*parameters) + retrieve_store_class(store).new(*parameters) when nil ActiveSupport::Cache::MemoryStore.new else @@ -73,6 +64,18 @@ module ActiveSupport end end + # Expands out the +key+ argument into a key that can be used for the + # cache store. Optionally accepts a namespace, and all keys will be + # scoped within that namespace. + # + # If the +key+ argument provided is an array, or responds to +to_a+, then + # each of elements in the array will be turned into parameters/keys and + # concatenated into a single key. For example: + # + # expand_cache_key([:foo, :bar]) # => "foo/bar" + # expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar" + # + # The +key+ argument can also respond to +cache_key+ or +to_param+. def expand_cache_key(key, namespace = nil) expanded_cache_key = namespace ? "#{namespace}/" : "" @@ -94,6 +97,16 @@ module ActiveSupport else key.to_param end.to_s end + + # Obtains the specified cache store class, given the name of the +store+. + # Raises an error when the store class cannot be found. + def retrieve_store_class(store) + require "active_support/cache/#{store}" + rescue LoadError => e + raise "Could not find cache store adapter for #{store} (#{e})" + else + ActiveSupport::Cache.const_get(store.to_s.camelize) + end end # An abstract cache store class. There are multiple cache store @@ -522,7 +535,7 @@ module ActiveSupport 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) + if race_ttl && (Time.now.to_f - 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 @@ -562,38 +575,38 @@ module ActiveSupport # +:compress+, +:compress_threshold+, and +:expires_in+. def initialize(value, options = {}) if should_compress?(value, options) - @v = compress(value) - @c = true + @value = compress(value) + @compressed = true else - @v = value - end - if expires_in = options[:expires_in] - @x = (Time.now + expires_in).to_i + @value = value end + @created_at = Time.now.to_f + @expires_in = options[:expires_in] + @expires_in = @expires_in.to_f if @expires_in end def value - convert_version_3_entry! if defined?(@value) - compressed? ? uncompress(@v) : @v + convert_version_4beta1_entry! if defined?(@v) + compressed? ? uncompress(@value) : @value end # Check if the entry is expired. The +expires_in+ parameter can override # the value set when the entry was created. def expired? - convert_version_3_entry! if defined?(@value) - if defined?(@x) - @x && @x < Time.now.to_i - else - false - end + convert_version_4beta1_entry! if defined?(@value) + @expires_in && @created_at + @expires_in <= Time.now.to_f end def expires_at - Time.at(@x) if defined?(@x) + @expires_in ? @created_at + @expires_in : nil end def expires_at=(value) - @x = value.to_i + if value + @expires_in = value.to_f - @created_at + else + @expires_in = nil + end end # Returns the size of the cached value. This could be less than @@ -606,9 +619,9 @@ module ActiveSupport when NilClass 0 when String - @v.bytesize + @value.bytesize else - @s = Marshal.dump(@v).bytesize + @s = Marshal.dump(@value).bytesize end end end @@ -616,12 +629,12 @@ module ActiveSupport # Duplicate the value in a class. This is used by cache implementations that don't natively # serialize entries to protect against accidental cache modifications. def dup_value! - convert_version_3_entry! if defined?(@value) - if @v && !compressed? && !(@v.is_a?(Numeric) || @v == true || @v == false) - if @v.is_a?(String) - @v = @v.dup + convert_version_4beta1_entry! if defined?(@v) + if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false) + if @value.is_a?(String) + @value = @value.dup else - @v = Marshal.load(Marshal.dump(@v)) + @value = Marshal.load(Marshal.dump(@value)) end end end @@ -637,7 +650,7 @@ module ActiveSupport end def compressed? - defined?(@c) ? @c : false + defined?(@compressed) ? @compressed : false end def compress(value) @@ -650,19 +663,19 @@ module ActiveSupport # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue # to ensure that cache entries created under the old version still work with the new class definition. - def convert_version_3_entry! - if defined?(@value) - @v = @value - remove_instance_variable(:@value) + def convert_version_4beta1_entry! + if defined?(@v) + @value = @v + remove_instance_variable(:@v) end - if defined?(@compressed) - @c = @compressed - remove_instance_variable(:@compressed) + if defined?(@c) + @compressed = @c + remove_instance_variable(:@c) end - if defined?(@expires_in) && defined?(@created_at) && @expires_in && @created_at - @x = (@created_at + @expires_in).to_i - remove_instance_variable(:@created_at) - remove_instance_variable(:@expires_in) + if defined?(@x) && @x + @created_at ||= Time.now.to_f + @expires_in = @x - @created_at + remove_instance_variable(:@x) end end end diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index db5f228a70..fb42c4a41e 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -8,6 +8,23 @@ module ActiveSupport # duration of a block. Repeated calls to the cache for the same key will hit the # in-memory cache for faster access. module LocalCache + # Class for storing and registering the local caches. + class LocalCacheRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + def initialize + @registry = {} + end + + def cache_for(local_cache_key) + @registry[local_cache_key] + end + + def set_cache_for(local_cache_key, value) + @registry[local_cache_key] = value + end + end + # Simple memory backed cache. This cache is not thread safe and is intended only # for serving as a temporary memory cache for a single thread. class LocalStore < Store @@ -41,24 +58,18 @@ module ActiveSupport # Use a local cache for the duration of block. def with_local_cache - save_val = Thread.current[thread_local_key] - begin - Thread.current[thread_local_key] = LocalStore.new - yield - ensure - Thread.current[thread_local_key] = save_val - end + use_temporary_local_cache(LocalStore.new) { yield } end #-- # This class wraps up local storage for middlewares. Only the middleware method should # construct them. class Middleware # :nodoc: - attr_reader :name, :thread_local_key + attr_reader :name, :local_cache_key - def initialize(name, thread_local_key) + def initialize(name, local_cache_key) @name = name - @thread_local_key = thread_local_key + @local_cache_key = local_cache_key @app = nil end @@ -68,10 +79,10 @@ module ActiveSupport end def call(env) - Thread.current[thread_local_key] = LocalStore.new + LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new) @app.call(env) ensure - Thread.current[thread_local_key] = nil + LocalCacheRegistry.set_cache_for(local_cache_key, nil) end end @@ -80,7 +91,7 @@ module ActiveSupport def middleware @middleware ||= Middleware.new( "ActiveSupport::Cache::Strategy::LocalCache", - thread_local_key) + local_cache_key) end def clear(options = nil) # :nodoc: @@ -95,29 +106,13 @@ module ActiveSupport def increment(name, amount = 1, options = nil) # :nodoc: value = bypass_local_cache{super} - if local_cache - local_cache.mute do - if value - local_cache.write(name, value, options) - else - local_cache.delete(name, options) - end - end - end + increment_or_decrement(value, name, amount, options) value end def decrement(name, amount = 1, options = nil) # :nodoc: value = bypass_local_cache{super} - if local_cache - local_cache.mute do - if value - local_cache.write(name, value, options) - else - local_cache.delete(name, options) - end - end - end + increment_or_decrement(value, name, amount, options) value end @@ -146,21 +141,37 @@ module ActiveSupport end private - def thread_local_key - @thread_local_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym + def increment_or_decrement(value, name, amount, options) + if local_cache + local_cache.mute do + if value + local_cache.write(name, value, options) + else + local_cache.delete(name, options) + end + end + end + end + + def local_cache_key + @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym end def local_cache - Thread.current[thread_local_key] + LocalCacheRegistry.cache_for(local_cache_key) end def bypass_local_cache - save_cache = Thread.current[thread_local_key] + use_temporary_local_cache(nil) { yield } + end + + def use_temporary_local_cache(temporary_cache) + save_cache = LocalCacheRegistry.cache_for(local_cache_key) begin - Thread.current[thread_local_key] = nil + LocalCacheRegistry.set_cache_for(local_cache_key, temporary_cache) yield ensure - Thread.current[thread_local_key] = save_cache + LocalCacheRegistry.set_cache_for(local_cache_key, save_cache) end end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 6c0cae71ed..893c2500d7 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -61,6 +61,8 @@ module ActiveSupport extend ActiveSupport::DescendantsTracker end + CALLBACK_FILTER_TYPES = [:before, :after, :around] + # Runs the callbacks for the given event. # # Calls the before and around callbacks in the order they were set, yields @@ -131,7 +133,13 @@ module ActiveSupport end def matches?(_kind, _filter) - @kind == _kind && @filter == _filter + if @_is_object_filter + _filter_matches = @filter.to_s.start_with?(_method_name_for_object_filter(_kind, _filter, false)) + else + _filter_matches = (@filter == _filter) + end + + @kind == _kind && _filter_matches end def duplicates?(other) @@ -234,6 +242,16 @@ module ActiveSupport @compiled_options = conditions.flatten.join(" && ") end + def _method_name_for_object_filter(kind, filter, append_next_id = true) + class_name = filter.kind_of?(Class) ? filter.to_s : filter.class.to_s + class_name.gsub!(/<|>|#/, '') + class_name.gsub!(/\/|:/, "_") + + method_name = "_callback_#{kind}_#{class_name}" + method_name << "_#{next_id}" if append_next_id + method_name + end + # Filters support: # # Arrays:: Used in conditions. This is used to specify @@ -255,6 +273,8 @@ module ActiveSupport # a method is created that calls the before_foo method # on the object. def _compile_filter(filter) + @_is_object_filter = false + case filter when Array filter.map {|f| _compile_filter(f)} @@ -269,7 +289,8 @@ module ActiveSupport method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ") else - method_name = "_callback_#{@kind}_#{next_id}" + method_name = _method_name_for_object_filter(kind, filter) + @_is_object_filter = true @klass.send(:define_method, "#{method_name}_object") { filter } _normalize_legacy_filter(kind, filter) @@ -319,10 +340,7 @@ module ActiveSupport end def compile - method = [] - method << "value = nil" - method << "halted = false" - + method = ["value = nil", "halted = false"] callbacks = "value = !halted && (!block_given? || yield)" reverse_each do |callback| callbacks = callback.apply(callbacks) @@ -396,7 +414,7 @@ module ActiveSupport # This is used internally to append, prepend and skip callbacks to the # CallbackChain. def __update_callbacks(name, filters = [], block = nil) #:nodoc: - type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before + type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before options = filters.last.is_a?(Hash) ? filters.pop : {} filters.unshift(block) if block diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index fa1dbfdf06..34859617c9 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -32,7 +32,7 @@ class Class def cattr_reader(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) unless defined? @@#{sym} @@#{sym} = nil @@ -93,7 +93,7 @@ class Class def cattr_writer(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) unless defined? @@#{sym} @@#{sym} = nil diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index cdf606f28c..0637fe4929 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,7 +1,6 @@ require 'date' require 'active_support/inflector/methods' require 'active_support/core_ext/date/zones' -require 'active_support/core_ext/module/remove_method' class Date DATE_FORMATS = { diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb index 5b89ace66b..0d14cba7cc 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb @@ -109,11 +109,11 @@ module DateAndTime alias :at_beginning_of_year :beginning_of_year # Returns a new date/time representing the given day in the next week. - # Week is assumed to start on +start_day+, default is - # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. - # DateTime objects have their time set to 0:00. - def next_week(start_day = Date.beginning_of_week) - first_hour{ weeks_since(1).beginning_of_week.days_since(days_span(start_day)) } + # The +given_day_in_next_week+ defaults to the beginning of the week + # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+ + # when set. +DateTime+ objects have their time set to 0:00. + def next_week(given_day_in_next_week = Date.beginning_of_week) + first_hour{ weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)) } end # Short-hand for months_since(1). diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index e608eeaf42..6d42667e97 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -1,8 +1,10 @@ class Module - # Provides a delegate class method to easily expose contained objects' public methods - # as your own. Pass one or more methods (specified as symbols or strings) - # and the name of the target object via the <tt>:to</tt> option (also a symbol - # or string). At least one method and the <tt>:to</tt> option are required. + # Provides a +delegate+ class method to easily expose contained objects' + # public methods as your own. + # + # The macro receives one or more method names (specified as symbols or + # strings) and the name of the target object via the <tt>:to</tt> option + # (also a symbol or string). # # Delegation is particularly useful with Active Record associations: # @@ -89,29 +91,44 @@ class Module # invoice.customer_name # => 'John Doe' # invoice.customer_address # => 'Vimmersvej 13' # - # If the delegate object is +nil+ an exception is raised, and that happens - # no matter whether +nil+ responds to the delegated method. You can get a - # +nil+ instead with the +:allow_nil+ option. + # If the target is +nil+ and does not respond to the delegated method a + # +NoMethodError+ is raised, as with any other value. Sometimes, however, it + # makes sense to be robust to that situation and that is the purpose of the + # <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and + # responds to the method, everything works as usual. But if it is +nil+ and + # does not respond to the delegated method, +nil+ is returned. # - # class Foo - # attr_accessor :bar - # def initialize(bar = nil) - # @bar = bar - # end - # delegate :zoo, to: :bar + # class User < ActiveRecord::Base + # has_one :profile + # delegate :age, to: :profile # end # - # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo) + # User.new.age # raises NoMethodError: undefined method `age' + # + # But if not having a profile yet is fine and should not be an error + # condition: + # + # class User < ActiveRecord::Base + # has_one :profile + # delegate :age, to: :profile, allow_nil: true + # end + # + # User.new.age # nil + # + # Note that if the target is not +nil+ then the call is attempted regardless of the + # <tt>:allow_nil</tt> option, and thus an exception is still raised if said object + # does not respond to the method: # # class Foo - # attr_accessor :bar - # def initialize(bar = nil) + # def initialize(bar) # @bar = bar # end - # delegate :zoo, to: :bar, allow_nil: true + # + # delegate :name, to: :@bar, allow_nil: true # end # - # Foo.new.zoo # returns nil + # Foo.new("Bar").name # raises NoMethodError: undefined method `name' + # def delegate(*methods) options = methods.pop unless options.is_a?(Hash) && to = options[:to] @@ -142,22 +159,31 @@ class Module # methods still accept two arguments. definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block' + # The following generated methods call the target exactly once, storing + # the returned value in a dummy variable. + # + # Reason is twofold: On one hand doing less calls is in general better. + # On the other hand it could be that the target has side-effects, + # whereas conceptualy, from the user point of view, the delegator should + # be doing one call. if allow_nil - module_eval(<<-EOS, file, line - 2) + module_eval(<<-EOS, file, line - 3) def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block) - if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name) - #{to}.#{method}(#{definition}) # client.name(*args, &block) + _ = #{to} # _ = client + if !_.nil? || nil.respond_to?(:#{method}) # if !_.nil? || nil.respond_to?(:name) + _.#{method}(#{definition}) # _.name(*args, &block) end # end end # end EOS else exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") - module_eval(<<-EOS, file, line - 1) + module_eval(<<-EOS, file, line - 2) def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block) - #{to}.#{method}(#{definition}) # client.name(*args, &block) + _ = #{to} # _ = client + _.#{method}(#{definition}) # _.name(*args, &block) rescue NoMethodError # rescue NoMethodError - if #{to}.nil? # if client.nil? + if _.nil? # if _.nil? #{exception} # # add helpful message to the exception else # else raise # raise diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb index cc45cee5b8..d873de197f 100644 --- a/activesupport/lib/active_support/core_ext/module/deprecation.rb +++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb @@ -14,8 +14,8 @@ class Module # method where you can implement your custom warning behavior. # # class MyLib::Deprecator - # def deprecation_warning(deprecated_method_name, message, caller_backtrace) - # message = "#{method_name} is deprecated and will be removed from MyLibrary | #{message}" + # def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil) + # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}" # Kernel.warn message # end # end diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index 428fa1f826..d2a2db32bb 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -20,19 +20,17 @@ class String return if parts.empty? now = Time.now - offset = parts[:offset] - utc_offset = form == :utc ? 0 : now.utc_offset - adjustment = offset ? offset - utc_offset : 0 - - Time.send( - form, + time = Time.new( 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 + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset, form == :utc ? 0 : nil) + ) + + form == :utc ? time.utc : time.getlocal end # Converts a string to a Date value. diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index a1b3f79748..c62bb41416 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -50,6 +50,6 @@ class String length_with_room_for_omission end - self[0...stop] + options[:omission] + "#{self[0...stop]}#{options[:omission]}" end end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 485dc91063..a03a66b96b 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -25,7 +25,7 @@ module ActiveSupport end end - # This DeprecatedObjectProxy transforms object to depracated object. + # This DeprecatedObjectProxy transforms object to deprecated object. # # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!") # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance) @@ -52,7 +52,7 @@ module ActiveSupport end # This DeprecatedInstanceVariableProxy transforms instance variable to - # depracated instance variable. + # deprecated instance variable. # # class Example # def initialize(deprecator) @@ -93,7 +93,7 @@ module ActiveSupport end end - # This DeprecatedConstantProxy transforms constant to depracated constant. + # This DeprecatedConstantProxy transforms constant to deprecated constant. # # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST') # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance) diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 837db05dcc..1b20592e4c 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -223,7 +223,7 @@ module ActiveSupport def deep_stringify_keys; dup end undef :symbolize_keys! undef :deep_symbolize_keys! - def symbolize_keys; to_hash.symbolize_keys end + def symbolize_keys; to_hash.symbolize_keys! end def deep_symbolize_keys; to_hash.deep_symbolize_keys end def to_options!; self end diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index c4b64bd1a6..e95dc5a866 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/class/attribute' +require 'active_support/subscriber' module ActiveSupport # ActiveSupport::LogSubscriber is an object set to consume @@ -33,7 +34,7 @@ module ActiveSupport # Log subscriber also has some helpers to deal with logging and automatically # flushes all logs when the request finishes (via action_dispatch.callback # notification) in a Rails environment. - class LogSubscriber + class LogSubscriber < Subscriber # Embed in a String to clear all previous ANSI sequences. CLEAR = "\e[0m" BOLD = "\e[1m" @@ -60,18 +61,8 @@ module ActiveSupport attr_writer :logger - def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications) - log_subscribers << log_subscriber - - log_subscriber.public_methods(false).each do |event| - next if %w{ start finish }.include?(event.to_s) - - notifier.subscribe("#{event}.#{namespace}", log_subscriber) - end - end - def log_subscribers - @@log_subscribers ||= [] + subscribers end # Flush all log_subscribers' logger. @@ -80,39 +71,18 @@ module ActiveSupport end end - def initialize - @queue_key = [self.class.name, object_id].join "-" - super - end - def logger LogSubscriber.logger end def start(name, id, payload) - return unless logger - - e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload) - parent = event_stack.last - parent << e if parent - - event_stack.push e + super if logger end def finish(name, id, payload) - return unless logger - - finished = Time.now - event = event_stack.pop - event.end = finished - event.payload.merge!(payload) - - method = name.split('.').first - begin - send(method, event) - rescue Exception => e - logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" - end + super if logger + rescue Exception => e + logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" end protected @@ -135,11 +105,5 @@ module ActiveSupport bold = bold ? BOLD : "" "#{bold}#{color}#{text}#{CLEAR}" end - - private - - def event_stack - Thread.current[@queue_key] ||= [] - end end end diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index ce40a7d689..bffdfc6201 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -12,10 +12,11 @@ module ActiveSupport # This can be used in situations similar to the <tt>MessageVerifier</tt>, but # where you don't want users to be able to determine the value of the payload. # - # key = OpenSSL::Digest::SHA256.new('password').digest # => "\x89\xE0\x156\xAC..." - # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...> - # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." - # crypt.decrypt_and_verify(encrypted_data) # => "my secret data" + # salt = SecureRandom.random_bytes(64) + # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..." + # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...> + # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." + # crypt.decrypt_and_verify(encrypted_data) # => "my secret data" class MessageEncryptor module NullSerializer #:nodoc: def self.load(value) @@ -28,7 +29,7 @@ module ActiveSupport end class InvalidMessage < StandardError; end - OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError + OpenSSLCipherError = OpenSSL::Cipher::CipherError # Initialize a new MessageEncryptor. +secret+ must be at least as long as # the cipher key size. For the default 'aes-256-cbc' cipher, this is 256 @@ -66,12 +67,11 @@ module ActiveSupport def _encrypt(value) cipher = new_cipher - # Rely on OpenSSL for the initialization vector - iv = cipher.random_iv - cipher.encrypt cipher.key = @secret - cipher.iv = iv + + # Rely on OpenSSL for the initialization vector + iv = cipher.random_iv encrypted_data = cipher.update(@serializer.dump(value)) encrypted_data << cipher.final diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index cc935e6cb9..414960d2b1 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -295,7 +295,7 @@ module ActiveSupport options = format_options(options[:locale]).merge!(options) - parts = number.to_s.to_str.split('.') + parts = number.to_s.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") parts.join(options[:separator]) end @@ -356,7 +356,8 @@ module ActiveSupport digits, rounded_number = 1, 0 else digits = (Math.log10(number.abs) + 1).floor - rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision) + multiplier = 10 ** (digits - precision) + rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new(multiplier.to_f.to_s)).round.to_f * multiplier digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed end precision -= digits diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb new file mode 100644 index 0000000000..34c6f900c1 --- /dev/null +++ b/activesupport/lib/active_support/subscriber.rb @@ -0,0 +1,93 @@ +require 'active_support/per_thread_registry' + +module ActiveSupport + # ActiveSupport::Subscriber is an object set to consume + # ActiveSupport::Notifications. The subscriber dispatches notifications to + # a registered object based on its given namespace. + # + # An example would be Active Record subscriber responsible for collecting + # statistics about queries: + # + # module ActiveRecord + # class StatsSubscriber < ActiveSupport::Subscriber + # def sql(event) + # Statsd.timing("sql.#{event.payload[:name]}", event.duration) + # end + # end + # end + # + # And it's finally registered as: + # + # ActiveRecord::StatsSubscriber.attach_to :active_record + # + # Since we need to know all instance methods before attaching the log + # subscriber, the line above should be called after your subscriber definition. + # + # After configured, whenever a "sql.active_record" notification is published, + # it will properly dispatch the event (ActiveSupport::Notifications::Event) to + # the +sql+ method. + class Subscriber + class << self + + # Attach the subscriber to a namespace. + def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications) + subscribers << subscriber + + subscriber.public_methods(false).each do |event| + next if %w{ start finish }.include?(event.to_s) + + notifier.subscribe("#{event}.#{namespace}", subscriber) + end + end + + def subscribers + @@subscribers ||= [] + end + end + + def initialize + @queue_key = [self.class.name, object_id].join "-" + super + end + + def start(name, id, payload) + e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload) + parent = event_stack.last + parent << e if parent + + event_stack.push e + end + + def finish(name, id, payload) + finished = Time.now + event = event_stack.pop + event.end = finished + event.payload.merge!(payload) + + method = name.split('.').first + send(method, event) + end + + private + + def event_stack + SubscriberQueueRegistry.get_queue(@queue_key) + end + end + + # This is a registry for all the event stacks kept for subscribers. + # + # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt> + # for further details. + class SubscriberQueueRegistry # :nodoc: + extend PerThreadRegistry + + def initialize + @registry = {} + end + + def get_queue(queue_key) + @registry[queue_key] ||= [] + end + end +end diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index ca23057189..8762330a6e 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -1,7 +1,7 @@ module ActiveSupport # Returns the version of the currently loaded ActiveSupport as a Gem::Version def self.version - Gem::Version.new "4.0.0.beta1" + Gem::Version.new "4.1.0.beta" end module VERSION #:nodoc: diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 571be5f296..bcc200cf33 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -943,29 +943,28 @@ class CacheEntryTest < ActiveSupport::TestCase assert_equal value.bytesize, entry.size end - def test_restoring_version_3_entries - version_3_entry = ActiveSupport::Cache::Entry.allocate - version_3_entry.instance_variable_set(:@value, "hello") - version_3_entry.instance_variable_set(:@created_at, Time.now - 60) - entry = Marshal.load(Marshal.dump(version_3_entry)) + def test_restoring_version_4beta1_entries + version_4beta1_entry = ActiveSupport::Cache::Entry.allocate + version_4beta1_entry.instance_variable_set(:@v, "hello") + version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i + 60) + entry = Marshal.load(Marshal.dump(version_4beta1_entry)) assert_equal "hello", entry.value assert_equal false, entry.expired? end - def test_restoring_compressed_version_3_entries - version_3_entry = ActiveSupport::Cache::Entry.allocate - version_3_entry.instance_variable_set(:@value, Zlib::Deflate.deflate(Marshal.dump("hello"))) - version_3_entry.instance_variable_set(:@compressed, true) - entry = Marshal.load(Marshal.dump(version_3_entry)) + def test_restoring_compressed_version_4beta1_entries + version_4beta1_entry = ActiveSupport::Cache::Entry.allocate + version_4beta1_entry.instance_variable_set(:@v, Zlib::Deflate.deflate(Marshal.dump("hello"))) + version_4beta1_entry.instance_variable_set(:@c, true) + entry = Marshal.load(Marshal.dump(version_4beta1_entry)) assert_equal "hello", entry.value end - def test_restoring_expired_version_3_entries - version_3_entry = ActiveSupport::Cache::Entry.allocate - version_3_entry.instance_variable_set(:@value, "hello") - version_3_entry.instance_variable_set(:@created_at, Time.now - 60) - version_3_entry.instance_variable_set(:@expires_in, 58.9) - entry = Marshal.load(Marshal.dump(version_3_entry)) + def test_restoring_expired_version_4beta1_entries + version_4beta1_entry = ActiveSupport::Cache::Entry.allocate + version_4beta1_entry.instance_variable_set(:@v, "hello") + version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i - 1) + entry = Marshal.load(Marshal.dump(version_4beta1_entry)) assert_equal "hello", entry.value assert_equal true, entry.expired? end diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 13f2e3cdaf..5afc2094e8 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -66,6 +66,16 @@ module CallbacksTest end end + class CallbackClass + def self.before(model) + model.history << [:before_save, :class] + end + + def self.after(model) + model.history << [:after_save, :class] + end + end + class Person < Record [:before_save, :after_save].each do |callback_method| callback_method_sym = callback_method.to_sym @@ -73,6 +83,7 @@ module CallbacksTest send(callback_method, callback_string(callback_method_sym)) send(callback_method, callback_proc(callback_method_sym)) send(callback_method, callback_object(callback_method_sym.to_s.gsub(/_save/, ''))) + send(callback_method, CallbackClass) send(callback_method) { |model| model.history << [callback_method_sym, :block] } end @@ -86,6 +97,7 @@ module CallbacksTest skip_callback :save, :after, :before_save_method, :unless => :yes skip_callback :save, :after, :before_save_method, :if => :no skip_callback :save, :before, :before_save_method, :unless => :no + skip_callback :save, :before, CallbackClass , :if => :yes def yes; true; end def no; false; end end @@ -430,6 +442,7 @@ module CallbacksTest [:before_save, :object], [:before_save, :block], [:after_save, :block], + [:after_save, :class], [:after_save, :object], [:after_save, :proc], [:after_save, :string], @@ -449,8 +462,10 @@ module CallbacksTest [:before_save, :string], [:before_save, :proc], [:before_save, :object], + [:before_save, :class], [:before_save, :block], [:after_save, :block], + [:after_save, :class], [:after_save, :object], [:after_save, :proc], [:after_save, :string], @@ -715,8 +730,10 @@ module CallbacksTest [:before_save, :string], [:before_save, :proc], [:before_save, :object], + [:before_save, :class], [:before_save, :block], [:after_save, :block], + [:after_save, :class], [:after_save, :object], [:after_save, :proc], [:after_save, :string], diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb index 8d827f054e..0d5f39a72b 100644 --- a/activesupport/test/core_ext/class/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/class/attribute_accessor_test.rb @@ -44,16 +44,18 @@ class ClassAttributeAccessorTest < ActiveSupport::TestCase end def test_should_raise_name_error_if_attribute_name_is_invalid - assert_raises NameError do + exception = assert_raises NameError do Class.new do - cattr_reader "invalid attribute name" + cattr_reader "1nvalid" end end + assert_equal "invalid class attribute name: 1nvalid", exception.message - assert_raises NameError do + exception = assert_raises NameError do Class.new do - cattr_writer "invalid attribute name" + cattr_writer "1nvalid" end end + assert_equal "invalid class attribute name: 1nvalid", exception.message end end diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb index 9927856aa2..b4ef5a0597 100644 --- a/activesupport/test/core_ext/date_and_time_behavior.rb +++ b/activesupport/test/core_ext/date_and_time_behavior.rb @@ -95,6 +95,11 @@ module DateAndTimeBehavior end def test_next_week + # M | T | W | T | F | S | S # M | T | W | T | F | S | S # + # | 22/2 | | | | | # 28/2 | | | | | | # monday in next week `next_week` + # | 22/2 | | | | | # | | | | 4/3 | | # friday in next week `next_week(:friday)` + # 23/10 | | | | | | # 30/10 | | | | | | # monday in next week `next_week` + # 23/10 | | | | | | # | | 1/11 | | | | # wednesday in next week `next_week(:wednesday)` assert_equal date_time_init(2005,2,28,0,0,0), date_time_init(2005,2,22,15,15,10).next_week assert_equal date_time_init(2005,3,4,0,0,0), date_time_init(2005,2,22,15,15,10).next_week(:friday) assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,10,23,0,0,0).next_week diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 82249ddd1b..8872611fb1 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -82,6 +82,21 @@ class Name end end +class SideEffect + attr_reader :ints + + delegate :to_i, :to => :shift, :allow_nil => true + delegate :to_s, :to => :shift + + def initialize + @ints = [1, 2, 3] + end + + def shift + @ints.shift + end +end + class ModuleTest < ActiveSupport::TestCase def setup @david = Someone.new("David", Somewhere.new("Paulina", "Chicago")) @@ -171,6 +186,17 @@ class ModuleTest < ActiveSupport::TestCase assert_nil rails.name end + # Ensures with check for nil, not for a falseish target. + def test_delegation_with_allow_nil_and_false_value + project = Project.new(false, false) + assert_raise(NoMethodError) { project.name } + end + + def test_delegation_with_allow_nil_and_invalid_value + rails = Project.new("Rails", "David") + assert_raise(NoMethodError) { rails.name } + end + def test_delegation_with_allow_nil_and_nil_value_and_prefix Project.class_eval do delegate :name, :to => :person, :allow_nil => true, :prefix => true @@ -228,6 +254,16 @@ class ModuleTest < ActiveSupport::TestCase "[#{e.backtrace.inspect}] did not include [#{file_and_line}]" end + def test_delegation_invokes_the_target_exactly_once + se = SideEffect.new + + assert_equal 1, se.to_i + assert_equal [2, 3], se.ints + + assert_equal '2', se.to_s + assert_equal [3], se.ints + end + def test_parent assert_equal Yz::Zy, Yz::Zy::Cd.parent assert_equal Yz, Yz::Zy.parent diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 62c5741ffb..8f0ebc13ea 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -300,9 +300,9 @@ class StringConversionsTest < ActiveSupport::TestCase 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 13:50 -0100".to_time + assert_equal Time.local(2011, 2, 27, 17, 50), "2011-02-27 13: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 14:50 -0500".to_time + assert_equal Time.local(2005, 2, 27, 22, 50), "2005-02-27 14:50 -0500".to_time assert_nil "".to_time end end @@ -326,6 +326,122 @@ class StringConversionsTest < ActiveSupport::TestCase end end + def test_standard_time_string_to_time_when_current_time_is_standard_time + with_env_tz "US/Eastern" do + Time.stubs(:now).returns(Time.local(2012, 1, 1)) + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time + assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time + assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc) + end + end + + def test_standard_time_string_to_time_when_current_time_is_daylight_savings + with_env_tz "US/Eastern" do + Time.stubs(:now).returns(Time.local(2012, 7, 1)) + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time + assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time + assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc) + end + end + + def test_daylight_savings_string_to_time_when_current_time_is_standard_time + with_env_tz "US/Eastern" do + Time.stubs(:now).returns(Time.local(2012, 1, 1)) + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time + assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time + assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time + assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time + assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc) + end + end + + def test_daylight_savings_string_to_time_when_current_time_is_daylight_savings + with_env_tz "US/Eastern" do + Time.stubs(:now).returns(Time.local(2012, 7, 1)) + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time + assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time + assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time + assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time + assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc) + end + end + + def test_partial_string_to_time_when_current_time_is_standard_time + with_env_tz "US/Eastern" do + Time.stubs(:now).returns(Time.local(2012, 1, 1)) + assert_equal Time.local(2012, 1, 1, 10, 0), "10:00".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 6, 0), "10:00 -0100".to_time + assert_equal Time.utc(2012, 1, 1, 11, 0), "10:00 -0100".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 -0500".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 -0500".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 5, 0), "10:00 UTC".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 13, 0), "10:00 PST".to_time + assert_equal Time.utc(2012, 1, 1, 18, 0), "10:00 PST".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 12, 0), "10:00 PDT".to_time + assert_equal Time.utc(2012, 1, 1, 17, 0), "10:00 PDT".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 EST".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 EST".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 9, 0), "10:00 EDT".to_time + assert_equal Time.utc(2012, 1, 1, 14, 0), "10:00 EDT".to_time(:utc) + end + end + + def test_partial_string_to_time_when_current_time_is_daylight_savings + with_env_tz "US/Eastern" do + Time.stubs(:now).returns(Time.local(2012, 7, 1)) + assert_equal Time.local(2012, 7, 1, 10, 0), "10:00".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 7, 0), "10:00 -0100".to_time + assert_equal Time.utc(2012, 7, 1, 11, 0), "10:00 -0100".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 -0500".to_time + assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 -0500".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 6, 0), "10:00 UTC".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 14, 0), "10:00 PST".to_time + assert_equal Time.utc(2012, 7, 1, 18, 0), "10:00 PST".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 13, 0), "10:00 PDT".to_time + assert_equal Time.utc(2012, 7, 1, 17, 0), "10:00 PDT".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 EST".to_time + assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 EST".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 10, 0), "10:00 EDT".to_time + assert_equal Time.utc(2012, 7, 1, 14, 0), "10:00 EDT".to_time(:utc) + end + end + def test_string_to_datetime assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_datetime assert_equal 0, "2039-02-27 23:50".to_datetime.offset # use UTC offset diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index b0e52847e1..766f7f6f56 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,12 +1,3 @@ -## Rails 4.0.0 (unreleased) ## -* Change Service pages(404, etc). *Stanislav Sobolev* +* No changes. -## Rails 4.0.0.beta1 (unreleased) ## - -* Split Validations and Callbacks guide into two. *Steve Klabnik* - -* New guide _Working with JavaScript in Rails_. *Steve Klabnik* - -* Guides updated to reflect new test locations. *Mike Moore* - -* Guides have a responsive design. *Joe Fiorini* +Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/guides/CHANGELOG.md) for previous changes. diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile index f06fc65de6..acd2ed5160 100644 --- a/guides/code/getting_started/Gemfile +++ b/guides/code/getting_started/Gemfile @@ -2,6 +2,7 @@ source 'https://rubygems.org' gem 'rails', '4.0.0' +# Use sqlite3 as the database for Active Record gem 'sqlite3' # Use SCSS for stylesheets diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb index ab890f202c..ce409868ca 100644 --- a/guides/rails_guides.rb +++ b/guides/rails_guides.rb @@ -22,7 +22,7 @@ end begin require 'redcarpet' -rescue Gem::LoadError +rescue LoadError # This can happen if doc:guides is executed in an application. $stderr.puts('Generating guides requires Redcarpet 2.1.1+.') $stderr.puts(<<ERROR) if bundler? diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 85301f69bc..a2849dd98f 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -416,7 +416,7 @@ You can call this task on the server during deployment to create compiled versio The rake task is: ```bash -$ bundle exec rake assets:precompile +$ RAILS_ENV=production bundle exec rake assets:precompile ``` For faster asset precompiles, you can partially load your application by setting diff --git a/guides/source/security.md b/guides/source/security.md index d56ce47b3c..b2d09369e2 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -1,4 +1,4 @@ -Ruby On Rails Security Guide +Ruby on Rails Security Guide ============================ This manual describes common security problems in web applications and how to avoid them with Rails. diff --git a/rails.gemspec b/rails.gemspec index a223ea1413..1993467455 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |s| s.email = 'david@loudthinking.com' s.homepage = 'http://www.rubyonrails.org' - s.files = ['README.rdoc'] + Dir['guides/**/*'] + s.files = ['README.md'] + Dir['guides/**/*'] s.add_dependency 'activesupport', version s.add_dependency 'actionpack', version @@ -25,5 +25,5 @@ Gem::Specification.new do |s| s.add_dependency 'railties', version s.add_dependency 'bundler', '>= 1.3.0', '< 2.0' - s.add_dependency 'sprockets-rails', '~> 2.0.0.rc3' + s.add_dependency 'sprockets-rails', '~> 2.0.0.rc4' end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index f67177a047..e1b98eda55 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,375 +1,7 @@ -## Rails 4.0.0 (unreleased) ## +* Rails::Railtie no longer forces the Rails::Configurable module on everything + that subclassess it. Instead, the methods from Rails::Configurable have been + moved to class methods in Railtie and the Railtie has been made abstract. -* Move rails.png into a data-uri. One less file to get generated into a new - application. This is also consistent with the removal of index.html. + *John Wang* - *Steve Klabnik* - -* The application rake task `doc:rails` generates now an API like the - official one (except for the links to GitHub). - - *Xavier Noria* - -* Allow vanilla apps to render CoffeeScript templates in production - - Vanilla apps already render CoffeeScript templates in development and test - environments. With this change, the production behavior matches that of - the other environments. - - Effectively, this meant moving coffee-rails (and the JavaScript runtime on - which it is dependent) from the :assets group to the top-level of the - generated Gemfile. - - *Gabe Kopley* - -* Don't generate a scaffold.css when --no-assets is specified - - *Kevin Glowacz* - -* Add support for generate scaffold password:digest - - * adds password_digest attribute to the migration - * adds has_secure_password to the model - * adds password and password_confirmation password_fields to _form.html - * omits password from index.html and show.html - * adds password and password_confirmation to the controller - * adds unencrypted password and password_confirmation to the controller test - * adds encrypted password_digest to the fixture - - *Sam Ruby* - -* Improved `rake test` command for running tests - - To run all tests: - - $ rake test - - To run a test suite - - $ rake test:[models,helpers,units,controllers,mailers,...] - - To run a selected test file(s): - - $ rake test test/unit/foo_test.rb [test/unit/bar_test.rb ...] - - To run a single test from a test file - - $ rake test test/unit/foo_test.rb TESTOPTS='-n test_the_truth' - -* Improve service pages with new layout (404, etc). - - *Stanislav Sobolev* - - -## Rails 4.0.0.beta1 (February 25, 2013) ## - -* Improve `rake stats` for JavaScript and CoffeeScript: ignore block comments - and calculates number of functions. - - *Hendy Tanata* - -* Ability to use a custom builder by passing `--builder` (or `-b`) has been removed. - Consider using application template instead. See this guide for more detail: - http://guides.rubyonrails.org/rails_application_templates.html - - *Prem Sichanugrist* - -* Fix `rake db:*` tasks to work with `DATABASE_URL` and without `config/database.yml`. - - *Terence Lee* - -* Add notice message for destroy action in scaffold generator. - - *Rahul P. Chaudhari* - -* Add two new test rake tasks to speed up full test runs. - - * `test:all`: run tests quickly by merging all types and not resetting db. - * `test:all:db`: run tests quickly, but also reset db. - - *Ryan Davis* - -* Add `--rc` option to support the load of a custom rc file during the generation of a new app. - - *Amparo Luna* - -* Add `--no-rc` option to skip the loading of railsrc file during the generation of a new app. - - *Amparo Luna* - -* Fixes database.yml when creating a new rails application with '.' - Fixes #8304. - - *Jeremy W. Rowe* - -* Restore Rails::Engine::Railties#engines with deprecation to ensure - compatibility with gems such as Thinking Sphinx - Fixes #8551. - - *Tim Raymond* - -* 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. - Fixes #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' - Fixes #8612. - - *Yves Senn* - -* Explicit options have precedence over `~/.railsrc` on the `rails new` command. - - *Rafael Mendonça França* - -* Generated migrations now always use the `change` method. - - *Marc-André Lafortune* - -* Add `app/models/concerns` and `app/controllers/concerns` to the default directory structure and load path. - See http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns for usage instructions. - - *DHH* - -* The `rails/info/routes` now correctly formats routing output as an html table. - - *Richard Schneeman* - -* The `public/index.html` is no longer generated for new projects. - Page is replaced by internal `welcome_controller` inside of railties. - - *Richard Schneeman* - -* Add `ENV['RACK_ENV']` support to `rails runner/console/server`. - - *kennyj* - -* Add `db` to list of folders included by `rake notes` and `rake notes:custom`. *Antonio Cangiano* - -* Engines with a dummy app include the rake tasks of dependencies in the app namespace. - Fixes #8229. - - *Yves Senn* - -* Add `sqlserver.yml` template file to satisfy `-d sqlserver` being passed to `rails new`. - Fixes #6882. - - *Robert Nesius* - -* Rake test:uncommitted finds git directory in ancestors *Nicolas Despres* - -* Add dummy app Rake tasks when `--skip-test-unit` and `--dummy-path` is passed to the plugin generator. - Fixes #8121. - - *Yves Senn* - -* Add `.rake` to list of file extensions included by `rake notes` and `rake notes:custom`. *Brent J. Nordquist* - -* New test locations `test/models`, `test/helpers`, `test/controllers`, and - `test/mailers`. Corresponding rake tasks added as well. *Mike Moore* - -* Set a different cache per environment for assets pipeline - through `config.assets.cache`. - - *Guillermo Iguaran* - -* `Rails.public_path` now returns a Pathname object. *Prem Sichanugrist* - -* Remove highly uncommon `config.assets.manifest` option for moving the manifest path. - This option is now unsupported in sprockets-rails. - - *Guillermo Iguaran & Dmitry Vorotilin* - -* Add `config.action_controller.permit_all_parameters` to disable - StrongParameters protection, it's false by default. - - *Guillermo Iguaran* - -* Remove `config.active_record.whitelist_attributes` and - `config.active_record.mass_assignment_sanitizer` from new applications since - MassAssignmentSecurity has been extracted from Rails. - - *Guillermo Iguaran* - -* Change `rails new` and `rails plugin new` generators to name the `.gitkeep` files - as `.keep` in a more SCM-agnostic way. - - Change `--skip-git` option to only skip the `.gitignore` file and still generate - the `.keep` files. - - Add `--skip-keeps` option to skip the `.keep` files. - - *Derek Prior & Francesco Rodriguez* - -* Fixed support for `DATABASE_URL` environment variable for rake db tasks. - - *Grace Liu* - -* `rails dbconsole` now can use SSL for MySQL. The `database.yml` options sslca, sslcert, sslcapath, sslcipher - and sslkey now affect `rails dbconsole`. - - *Jim Kingdon and Lars Petrus* - -* Correctly handle SCRIPT_NAME when generating routes to engine in application - that's mounted at a sub-uri. With this behavior, you *should not* use - `default_url_options[:script_name]` to set proper application's mount point by - yourself. - - *Piotr Sarnacki* - -* `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded . - - *José Valim* - -* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance - - rails g migration AddReferencesToProducts user:references supplier:references{polymorphic} - - will generate the migration with: - - add_reference :products, :user, index: true - add_reference :products, :supplier, polymorphic: true, index: true - - *Aleksey Magusev* - -* Allow scaffold/model/migration generators to accept a `polymorphic` modifier - for `references`/`belongs_to`, for instance - - rails g model Product supplier:references{polymorphic} - - will generate the model with `belongs_to :supplier, polymorphic: true` - association and appropriate migration. - - *Aleksey Magusev* - -* Set `config.active_record.migration_error` to `:page_load` for development. - - *Richard Schneeman* - -* Add runner to `Rails::Railtie` as a hook called just after runner starts. - - *José Valim & kennyj* - -* Add `/rails/info/routes` path, displays same information as `rake routes` . - - *Richard Schneeman & Andrew White* - -* Improved `rake routes` output for redirects. - - *Łukasz Strzałkowski & Andrew White* - -* Load all environments available in `config.paths["config/environments"]`. - - *Piotr Sarnacki* - -* Remove `Rack::SSL` in favour of `ActionDispatch::SSL`. - - *Rafael Mendonça França* - -* Remove Active Resource from Rails framework. - - *Prem Sichangrist* - -* Allow to set class that will be used to run as a console, other than IRB, with `Rails.application.config.console=`. It's best to add it to `console` block. - - Example: - - # it can be added to config/application.rb - console do - # this block is called only when running console, - # so we can safely require pry here - require "pry" - config.console = Pry - end - - *Piotr Sarnacki* - -* Add convenience `hide!` method to Rails generators to hide current generator - namespace from showing when running `rails generate`. - - *Carlos Antonio da Silva* - -* 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 - message delivery. - - *Brian Cardarella* - -Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/railties/CHANGELOG.md) for previous changes. +Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/railties/CHANGELOG.md) for previous changes. diff --git a/railties/bin/rails b/railties/bin/rails index a1c4faaa73..b3026e8a93 100755 --- a/railties/bin/rails +++ b/railties/bin/rails @@ -1,6 +1,8 @@ #!/usr/bin/env ruby -if File.exists?(File.join(File.expand_path('../../..', __FILE__), '.git')) +git_path = File.join(File.expand_path('../../..', __FILE__), '.git') + +if File.exists?(git_path) railties_path = File.expand_path('../../lib', __FILE__) $:.unshift(railties_path) end diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb index 1493815c30..6c9c53fc69 100644 --- a/railties/lib/rails/all.rb +++ b/railties/lib/rails/all.rb @@ -1,9 +1,5 @@ require "rails" -if defined?(Rake) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any? - ENV['RAILS_ENV'] ||= 'test' -end - %w( active_record action_controller diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 86f62dfb40..bfb9b456db 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -124,7 +124,7 @@ module Rails # # Now you can mount your engine in application's routes just like that: # - # MyRailsApp::Application.routes.draw do + # Rails.application.routes.draw do # mount MyEngine::Engine => "/engine" # end # @@ -154,7 +154,7 @@ module Rails # Note that now there can be more than one router in your application, and it's better to avoid # passing requests through many routers. Consider this situation: # - # MyRailsApp::Application.routes.draw do + # Rails.application.routes.draw do # mount MyEngine::Engine => "/blog" # get "/blog/omg" => "main#omg" # end @@ -164,7 +164,7 @@ module Rails # and if there is no such route in +Engine+'s routes, it will be dispatched to <tt>main#omg</tt>. # It's much better to swap that: # - # MyRailsApp::Application.routes.draw do + # Rails.application.routes.draw do # get "/blog/omg" => "main#omg" # mount MyEngine::Engine => "/blog" # end @@ -251,7 +251,7 @@ module Rails # created to allow you to do that. Consider such a scenario: # # # config/routes.rb - # MyApplication::Application.routes.draw do + # Rails.application.routes.draw do # mount MyEngine::Engine => "/my_engine", as: "my_engine" # get "/foo" => "foo#index" # end @@ -634,6 +634,10 @@ module Rails end end + def routes? #:nodoc: + @routes + end + protected def run_tasks_blocks(*) #:nodoc: @@ -641,10 +645,6 @@ module Rails paths["lib/tasks"].existent.sort.each { |ext| load(ext) } end - def routes? #:nodoc: - @routes - end - def has_migrations? #:nodoc: paths["db/migrate"].existent.any? end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 28593c5907..366c72ebaa 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -86,7 +86,7 @@ module Rails # end def environment(data=nil, options={}, &block) sentinel = /class [a-z_:]+ < Rails::Application/i - env_file_sentinel = /::Application\.configure do/ + env_file_sentinel = /Rails\.application\.configure do/ data = block.call if !data && block_given? in_root do diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index b62d1fff14..853d6fa4b9 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -115,9 +115,9 @@ module Rails end def database_gemfile_entry - options[:skip_active_record] ? "" : - <<-GEMFILE.gsub(/^ {12}/, '').strip - # Use #{options[:database]} as the database for ActiveRecord + options[:skip_active_record] ? "" : + <<-GEMFILE.strip_heredoc.chomp + # Use #{options[:database]} as the database for Active Record gem '#{gem_for_database}' GEMFILE end @@ -135,13 +135,11 @@ module Rails <<-GEMFILE.strip_heredoc gem 'rails', path: '#{Rails::Generators::RAILS_DEV_PATH}' gem 'arel', github: 'rails/arel' - gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders' GEMFILE elsif options.edge? <<-GEMFILE.strip_heredoc gem 'rails', github: 'rails/rails' gem 'arel', github: 'rails/arel' - gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders' GEMFILE else <<-GEMFILE.strip_heredoc @@ -182,55 +180,53 @@ module Rails return if options[:skip_sprockets] gemfile = if options.dev? || options.edge? - <<-GEMFILE.gsub(/^ {12}/, '') + <<-GEMFILE.strip_heredoc # Use edge version of sprockets-rails gem 'sprockets-rails', github: 'rails/sprockets-rails' # Use SCSS for stylesheets - gem 'sass-rails', github: 'rails/sass-rails' - - # Use Uglifier as compressor for JavaScript assets - gem 'uglifier', '~> 1.3' + gem 'sass-rails', github: 'rails/sass-rails' GEMFILE else - <<-GEMFILE.gsub(/^ {12}/, '') + <<-GEMFILE.strip_heredoc # Use SCSS for stylesheets - gem 'sass-rails', '~> 4.0.0.beta1' - - # Use Uglifier as compressor for JavaScript assets - gem 'uglifier', '~> 1.3' + gem 'sass-rails', '~> 4.0.0.rc1' GEMFILE end + gemfile += <<-GEMFILE.strip_heredoc + + # Use Uglifier as compressor for JavaScript assets + gem 'uglifier', '>= 1.3.0' + GEMFILE + if options[:skip_javascript] - gemfile += <<-GEMFILE.gsub(/^ {12}/, '') + gemfile += <<-GEMFILE #{coffee_gemfile_entry} #{javascript_runtime_gemfile_entry} GEMFILE end - gemfile.strip_heredoc.gsub(/^[ \t]*$/, '') + gemfile.gsub(/^[ \t]+/, '') end def coffee_gemfile_entry - gemfile = if options.dev? || options.edge? - <<-GEMFILE.gsub(/^ {12}/, '') + if options.dev? || options.edge? + <<-GEMFILE # Use CoffeeScript for .js.coffee assets and views gem 'coffee-rails', github: 'rails/coffee-rails' GEMFILE else - <<-GEMFILE.gsub(/^ {12}/, '') + <<-GEMFILE # Use CoffeeScript for .js.coffee assets and views - gem 'coffee-rails', '~> 4.0.0.beta1' + gem 'coffee-rails', '~> 4.0.0' GEMFILE end - - gemfile.strip_heredoc.gsub(/^[ \t]*$/, '') end def javascript_gemfile_entry unless options[:skip_javascript] - <<-GEMFILE.gsub(/^ {12}/, '').strip_heredoc + <<-GEMFILE.gsub(/^[ \t]+/, '') #{coffee_gemfile_entry} #{javascript_runtime_gemfile_entry} # Use #{options[:javascript]} as the JavaScript library @@ -248,7 +244,7 @@ module Rails else "# gem 'therubyracer', platforms: :ruby" end - <<-GEMFILE.gsub(/^ {10}/, '') + <<-GEMFILE # See https://github.com/sstephenson/execjs#readme for more supported runtimes #{runtime} GEMFILE diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb index 85a1b01cc6..1799e823b6 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb @@ -14,14 +14,14 @@ <% attributes.each do |attribute| -%> <div class="field"> <% if attribute.password_digest? -%> - <%%= f.label :password %><br /> + <%%= f.label :password %><br> <%%= f.password_field :password %> </div> <div> - <%%= f.label :password_confirmation %><br /> + <%%= f.label :password_confirmation %><br> <%%= f.password_field :password_confirmation %> <% else -%> - <%%= f.label :<%= attribute.name %> %><br /> + <%%= f.label :<%= attribute.name %> %><br> <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> <% end -%> </div> 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 d2fd99fdcb..9d778642f2 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb @@ -14,18 +14,18 @@ <tbody> <%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %> - <tr> + <tr> <% attributes.reject(&:password_digest?).each do |attribute| -%> - <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td> + <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td> <% end -%> - <td><%%= link_to 'Show', <%= singular_table_name %> %></td> - <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td> - <td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> + <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> -<br /> +<br> <%%= link_to 'New <%= human_name %>', new_<%= singular_table_name %>_path %> diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 1842e09bae..ace804ffe6 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -9,14 +9,14 @@ 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' + group :doc do # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', require: false end -# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem 'jbuilder', '~> 1.0.1' - # Use ActiveModel has_secure_password # gem 'bcrypt-ruby', '~> 3.0.0' diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile index 6eb23f68a3..ba6b733dd2 100644 --- a/railties/lib/rails/generators/rails/app/templates/Rakefile +++ b/railties/lib/rails/generators/rails/app/templates/Rakefile @@ -3,4 +3,4 @@ require File.expand_path('../config/application', __FILE__) -<%= app_const %>.load_tasks +Rails.application.load_tasks diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb index e080ebd74e..00a613ff04 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb @@ -2,4 +2,4 @@ require File.expand_path('../application', __FILE__) # Initialize the rails application. -<%= app_const %>.initialize! +Rails.application.initialize! diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index 8b64881dbc..91253d1508 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -1,4 +1,4 @@ -<%= app_const %>.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on 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 c40eef145f..1dfc9f136b 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 @@ -1,4 +1,4 @@ -<%= app_const %>.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index 3c9c787948..ba0742f97f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -1,4 +1,4 @@ -<%= app_const %>.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's @@ -13,7 +13,7 @@ config.eager_load = false # Configure static asset server for tests with Cache-Control for performance. - config.serve_static_assets = true + config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" # Show full error reports and disable caching. diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt index efccf72d3d..f3cc6098a3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt @@ -9,4 +9,4 @@ # Make sure your secret_key_base is kept private # if you're sharing your code publicly. -<%= app_const %>.config.secret_key_base = '<%= app_secret %>' +Rails.application.config.secret_key_base = '<%= app_secret %>' diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt index 4a099a4ce2..2bb9b82c61 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -<%= app_const %>.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %> +Rails.application.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index 1794ffa833..3dfb724164 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -1,4 +1,4 @@ -<%= app_const %>.routes.draw do +Rails.application.routes.draw do # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 9437e9c406..89ca8cbe11 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -112,7 +112,6 @@ module Rails # Be sure to look at the documentation of those specific classes for more information. # class Railtie - autoload :Configurable, "rails/railtie/configurable" autoload :Configuration, "rails/railtie/configuration" include Initializable @@ -121,6 +120,7 @@ module Rails class << self private :new + delegate :config, to: :instance def subclasses @subclasses ||= [] @@ -128,7 +128,6 @@ module Rails def inherited(base) unless base.abstract_railtie? - base.send(:include, Railtie::Configurable) subclasses << base end end @@ -166,14 +165,51 @@ module Rails @railtie_name ||= generate_railtie_name(self.name) end + # Since Rails::Railtie cannot be instantiated, any methods that call + # +instance+ are intended to be called only on subclasses of a Railtie. + def instance + @instance ||= new + end + + def respond_to_missing?(*args) + instance.respond_to?(*args) || super + end + + # Allows you to configure the railtie. This is the same method seen in + # Railtie::Configurable, but this module is no longer required for all + # subclasses of Railtie so we provide the class method here. + def configure(&block) + instance.configure(&block) + end + protected def generate_railtie_name(class_or_module) ActiveSupport::Inflector.underscore(class_or_module).tr("/", "_") end + + # If the class method does not have a method, then send the method call + # to the Railtie instance. + def method_missing(name, *args, &block) + if instance.respond_to?(name) + instance.public_send(name, *args, &block) + else + super + end + end end delegate :railtie_name, to: :class + def initialize + if self.class.abstract_railtie? + raise "#{self.class.name} is abstract, you cannot instantiate it directly." + end + end + + def configure(&block) + instance_eval(&block) + end + def config @config ||= Railtie::Configuration.new end diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index 1c3426028d..f89d6b12e1 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -57,8 +57,8 @@ namespace :doc do # desc "Generate Rails Guides" task :guides do - # FIXME: Reaching outside lib directory is a bad idea - require File.expand_path('../../../../../guides/rails_guides', __FILE__) + rails_gem_dir = Gem::Specification.find_by_name("rails").gem_dir + require File.expand_path(File.join(rails_gem_dir, "/guides/rails_guides")) RailsGuides::Generator.new(Rails.root.join("doc/guides")).generate end end diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb index f52c4c44b7..ab1ebe1f8c 100644 --- a/railties/lib/rails/test_unit/railtie.rb +++ b/railties/lib/rails/test_unit/railtie.rb @@ -1,3 +1,7 @@ +if defined?(Rake) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any? + ENV['RAILS_ENV'] ||= 'test' +end + module Rails class TestUnitRailtie < Rails::Railtie config.app_generators do |c| diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index fee352db5a..dcbf57a4df 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -3,7 +3,7 @@ module Rails MAJOR = 4 MINOR = 0 TINY = 0 - PRE = "beta1" + PRE = "rc1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index 22de640236..25372d0a50 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -277,6 +277,30 @@ module ApplicationTests end end + def test_root_path + app('development') + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render :text => "foo" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + get 'foo', :to => 'foo#index' + root :to => 'foo#index' + end + RUBY + + remove_file 'public/index.html' + + get '/' + assert_equal 'foo', last_response.body + end + test 'routes are added and removed when reloading' do app('development') diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index f8fa8ee153..0db40c1d32 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -103,7 +103,7 @@ class ActionsTest < Rails::Generators::TestCase run_generator autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]' action :environment, autoload_paths, env: 'development' - assert_file "config/environments/development.rb", /Application\.configure do\n #{Regexp.escape(autoload_paths)}/ + assert_file "config/environments/development.rb", /Rails\.application\.configure do\n #{Regexp.escape(autoload_paths)}/ end def test_environment_with_block_should_include_block_contents_in_environment_initializer_block diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 5fdf58c8ee..f70c90bffa 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -65,7 +65,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_invalid_application_name_is_fixed run_generator [File.join(destination_root, "things-43")] - assert_file "things-43/config/environment.rb", /Things43::Application\.initialize!/ + assert_file "things-43/config/environment.rb", /Rails\.application\.initialize!/ assert_file "things-43/config/application.rb", /^module Things43$/ end @@ -111,7 +111,7 @@ class AppGeneratorTest < Rails::Generators::TestCase destination_root: app_moved_root, shell: @shell generator.send(:app_const) quietly { generator.send(:create_config_files) } - assert_file "myapp_moved/config/environment.rb", /Myapp::Application\.initialize!/ + assert_file "myapp_moved/config/environment.rb", /Rails\.application\.initialize!/ assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/ end @@ -131,7 +131,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_application_names_are_not_singularized run_generator [File.join(destination_root, "hats")] - assert_file "hats/config/environment.rb", /Hats::Application\.initialize!/ + assert_file "hats/config/environment.rb", /Rails\.application\.initialize!/ end def test_gemfile_has_no_whitespace_errors diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 01fa2c6864..0948ae59c0 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -416,11 +416,6 @@ YAML boot_rails end - test "Rails::Engine itself does not respond to config" do - boot_rails - assert !Rails::Engine.respond_to?(:config) - end - test "initializers are executed after application configuration initializers" do @plugin.write "lib/bukkits.rb", <<-RUBY module Bukkits diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb index 0abb2b7578..7348d70c56 100644 --- a/railties/test/railties/generators_test.rb +++ b/railties/test/railties/generators_test.rb @@ -44,7 +44,7 @@ module RailtiesTests Dir.chdir(engine_path) do File.open("Gemfile", "w") do |f| f.write <<-GEMFILE.gsub(/^ {12}/, '') - source "http://rubygems.org" + source "https://rubygems.org" gem 'rails', path: '#{RAILS_FRAMEWORK_ROOT}' gem 'sqlite3' diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index 0786b8f8c7..520a855c90 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -19,8 +19,8 @@ module RailtiesTest @app ||= Rails.application end - test "Rails::Railtie itself does not respond to config" do - assert !Rails::Railtie.respond_to?(:config) + test "cannot instantiate a Railtie object" do + assert_raise(RuntimeError) { Rails::Railtie.new } end test "Railtie provides railtie_name" do @@ -39,13 +39,6 @@ module RailtiesTest assert_equal "bar", Foo.railtie_name end - test "cannot inherit from a railtie" do - class Foo < Rails::Railtie ; end - assert_raise RuntimeError do - class Bar < Foo; end - end - end - test "config is available to railtie" do class Foo < Rails::Railtie ; end assert_nil Foo.config.action_controller.foo diff --git a/version.rb b/version.rb index fee352db5a..5a6d8d0983 100644 --- a/version.rb +++ b/version.rb @@ -1,9 +1,9 @@ module Rails module VERSION MAJOR = 4 - MINOR = 0 + MINOR = 1 TINY = 0 - PRE = "beta1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end |