diff options
214 files changed, 3411 insertions, 1443 deletions
diff --git a/.travis.yml b/.travis.yml index 930fc8f583..18b0100c62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ notifications: on_success: change on_failure: always rooms: - - secure: "qDX+Bw/tsg0ogYX0y/kTz5d326BL4JfjZQ66my2VdmRNwHyvv+mrONJ627Kt\n/U6HPA2FtSIMhtYWezi6qtGOKqZ3rns9B3gGGuaLgoCKLtCK2xzdR+OmNaNp\nXpuwGod2uABnpaDGDRYhsJ7X/efct1rk/C//S6s2b6rWKHNQd8k=" + - secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI=" bundler_args: --path vendor/bundle matrix: allow_failures: @@ -12,6 +12,7 @@ gem 'jquery-rails', '~> 2.1.4', github: 'rails/jquery-rails' gem 'turbolinks' gem 'coffee-rails', github: 'rails/coffee-rails' +gem 'thread_safe', '~> 0.1' gem 'journey', github: 'rails/journey', branch: 'master' gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders', branch: 'master' diff --git a/README.rdoc b/README.rdoc index 87ec64e3b5..91a5f27add 100644 --- a/README.rdoc +++ b/README.rdoc @@ -56,10 +56,10 @@ can read more about Action Pack in its {README}[link:/rails/rails/blob/master/ac 5. Follow the guidelines to start developing your application. You may find the following resources handy: * The README file created within your application. -* The {Getting Started with Rails}[http://guides.rubyonrails.org/getting_started.html]. -* The {Ruby on Rails Tutorial}[http://railstutorial.org/book]. -* The {Ruby on Rails Guides}[http://guides.rubyonrails.org]. -* The {API Documentation}[http://api.rubyonrails.org]. +* {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 diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc index af1def223a..9af79f73e2 100644 --- a/RELEASING_RAILS.rdoc +++ b/RELEASING_RAILS.rdoc @@ -164,6 +164,9 @@ Today, do this stuff in this order: * Update RAILS_VERSION to remove the rc * Build and test the gem * Release the gems +* If releasing a new stable version: + - Trigger stable docs generation (see below) + - Update the version in the home page * Email security lists * Email general announcement lists diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index af91907f46..3708672c76 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -2,7 +2,7 @@ * Explicit multipart messages no longer set the order of the MIME parts. *Nate Berkopec* - + * Do not render views when mail() isn't called. Fix #7761 @@ -15,8 +15,8 @@ def my_mailer(user,company) mail to: user.email, subject: "Welcome!", - delivery_method_options: {user_name: company.smtp_user, - password: company.smtp_password} + delivery_method_options: { user_name: company.smtp_user, + password: company.smtp_password } end This will ensure that your default SMTP settings will be overridden diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 31005c9d9b..4a61bac0a5 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -284,12 +284,12 @@ module ActionMailer # # = Callbacks # - # You can specify callbacks using before_filter and after_filter for configuring your messages. + # You can specify callbacks using before_action and after_action for configuring your messages. # This may be useful, for example, when you want to add default inline attachments for all # messages sent out by a certain mailer class: # # class Notifier < ActionMailer::Base - # before_filter :add_inline_attachment! + # before_action :add_inline_attachment! # # def welcome # mail @@ -306,15 +306,15 @@ module ActionMailer # can define and configure callbacks in the same manner that you would use callbacks in # classes that inherit from ActionController::Base. # - # Note that unless you have a specific reason to do so, you should prefer using before_filter - # rather than after_filter in your ActionMailer classes so that headers are parsed properly. + # Note that unless you have a specific reason to do so, you should prefer using before_action + # rather than after_action in your ActionMailer classes so that headers are parsed properly. # # = Configuration options # # These options are specified on the class level, like # <tt>ActionMailer::Base.raise_delivery_errors = true</tt> # - # * <tt>default</tt> - You can pass this in at a class level as well as within the class itself as + # * <tt>default_options</tt> - You can pass this in at a class level as well as within the class itself as # per the above section. # # * <tt>logger</tt> - the logger is used for generating information on the mailing run if available. @@ -499,6 +499,7 @@ module ActionMailer # method, for instance). def initialize(method_name=nil, *args) super() + @_mail_was_called = false @_message = Mail.new process(method_name, *args) if method_name end @@ -506,7 +507,8 @@ module ActionMailer def process(*args) #:nodoc: lookup_context.skip_default_locale! - @_message = NullMail.new unless super + super + @_message = NullMail.new unless @_mail_was_called end class NullMail #:nodoc: @@ -666,11 +668,11 @@ module ActionMailer # end # def mail(headers={}, &block) + @_mail_was_called = true m = @_message - # At the beginning, do not consider class default for parts order neither content_type + # At the beginning, do not consider class default for content_type content_type = headers[:content_type] - parts_order = headers[:parts_order] # Call all the procs (if any) class_default = self.class.default diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 3c21886502..170923673b 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -505,6 +505,12 @@ class BaseTest < ActiveSupport::TestCase mail.deliver end + test 'the return value of mailer methods is not relevant' do + mail = BaseMailer.with_nil_as_return_value + assert_equal('Welcome', mail.body.to_s.strip) + mail.deliver + end + # Before and After hooks class MyObserver @@ -584,9 +590,9 @@ class BaseTest < ActiveSupport::TestCase assert_equal("Thanks for signing up this afternoon", mail.subject) end - test "modifying the mail message with a before_filter" do - class BeforeFilterMailer < ActionMailer::Base - before_filter :add_special_header! + test "modifying the mail message with a before_action" do + class BeforeActionMailer < ActionMailer::Base + before_action :add_special_header! def welcome ; mail ; end @@ -596,12 +602,12 @@ class BaseTest < ActiveSupport::TestCase end end - assert_equal('Wow, so special', BeforeFilterMailer.welcome['X-Special-Header'].to_s) + assert_equal('Wow, so special', BeforeActionMailer.welcome['X-Special-Header'].to_s) end - test "modifying the mail message with an after_filter" do - class AfterFilterMailer < ActionMailer::Base - after_filter :add_special_header! + test "modifying the mail message with an after_action" do + class AfterActionMailer < ActionMailer::Base + after_action :add_special_header! def welcome ; mail ; end @@ -611,12 +617,12 @@ class BaseTest < ActiveSupport::TestCase end end - assert_equal('Testing', AfterFilterMailer.welcome['X-Special-Header'].to_s) + assert_equal('Testing', AfterActionMailer.welcome['X-Special-Header'].to_s) end - test "adding an inline attachment using a before_filter" do + test "adding an inline attachment using a before_action" do class DefaultInlineAttachmentMailer < ActionMailer::Base - before_filter :add_inline_attachment! + before_action :add_inline_attachment! def welcome ; mail ; end diff --git a/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/after_action_mailer/welcome.html.erb index e69de29bb2..e69de29bb2 100644 --- a/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb +++ b/actionmailer/test/fixtures/base_test/after_action_mailer/welcome.html.erb diff --git a/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/before_action_mailer/welcome.html.erb index e69de29bb2..e69de29bb2 100644 --- a/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb +++ b/actionmailer/test/fixtures/base_test/before_action_mailer/welcome.html.erb diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb index 52342bc59e..8fca6177bd 100644 --- a/actionmailer/test/mailers/base_mailer.rb +++ b/actionmailer/test/mailers/base_mailer.rb @@ -118,4 +118,9 @@ class BaseMailer < ActionMailer::Base def without_mail_call end + + def with_nil_as_return_value(hash = {}) + mail(:template_name => "welcome") + nil + end end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index b57408ede3..c3df2ebc0c 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,9 +1,57 @@ ## Rails 4.0.0 (unreleased) ## -* Add :if / :unless conditions to fragment cache: - <%= cache @model, if: some_condition(@model) do %> +* Clear url helper methods when routes are reloaded. *Andrew White* - *Stephen Ausman + Fabrizio Regini* +* 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 filter capability to ActionController logs for redirect locations: @@ -20,7 +68,7 @@ an invalid `:layout` argument. #8376 - render :partial => 'partial', :layout => true + render partial: 'partial', layout: true # results in ActionView::MissingTemplate: Missing partial /true @@ -38,11 +86,11 @@ *Drew Ulmer* -* No sort Hash options in #grouped_options_for_select. *Sergey Kojin* +* No sort Hash options in `grouped_options_for_select`. *Sergey Kojin* -* Accept symbols as #send_data :disposition value *Elia Schito* +* Accept symbols as `send_data :disposition` value *Elia Schito* -* Add i18n scope to distance_of_time_in_words. *Steve Klabnik* +* Add i18n scope to `distance_of_time_in_words`. *Steve Klabnik* * `assert_template`: - is no more passing with empty string. @@ -97,26 +145,22 @@ *Joost Baaij* -* Fix input name when `:multiple => true` and `:index` are set. +* Fix input name when `multiple: true` and `:index` are set. Before: - check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1) + 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) + check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1) #=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" /> Fix #8108 *Daniel Fox, Grant Hutchins & Trace Wax* -* Clear url helpers when reloading routes. - - *Santiago Pastorino* - * `BestStandardsSupport` middleware now appends it's `X-UA-Compatible` value to app's returned value if any. Fix #8086 @@ -282,7 +326,7 @@ New applications are generated with: - protect_from_forgery :with => :exception + protect_from_forgery with: :exception *Sergey Nartimov* @@ -290,7 +334,7 @@ * Add `separator` option for `ActionView::Helpers::TextHelper#excerpt`: - excerpt('This is a very beautiful morning', 'very', :separator => ' ', :radius => 1) + excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1) # => ...a very beautiful... *Guirec Corbel* @@ -407,7 +451,7 @@ We recommend the use of Unobtrusive JavaScript instead. For example: - link_to "Greeting", "#", :class => "nav_link" + link_to "Greeting", "#", class: "nav_link" $(function() { $('.nav_link').click(function() { @@ -453,7 +497,7 @@ * 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. +* 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* @@ -465,7 +509,7 @@ 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. + If you add the above code, you can use `<%= error %>` in an erb, and `redirect_to /foo, error: 'message'` in a controller. *kennyj* @@ -548,7 +592,7 @@ * 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. +* 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* @@ -570,7 +614,7 @@ * Add backtrace to development routing error page. *Richard Schneeman* -* Replace `include_seconds` boolean argument with `:include_seconds => true` option +* 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 @@ -594,11 +638,11 @@ * Changed default value for `config.action_view.embed_authenticity_token_in_remote_forms` to `false`. This change breaks remote forms that need to work also without javascript, so if you need such behavior, you can either set it to `true` or explicitly pass - `:authenticity_token => true` in form options + `authenticity_token: true` in form options * Added ActionDispatch::SSL middleware that when included force all the requests to be under HTTPS protocol. *Rafael Mendonça França* -* 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* +* 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* @@ -615,7 +659,7 @@ * 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? + force_ssl if: :ssl_configured? def ssl_configured? !Rails.env.development? @@ -684,15 +728,15 @@ *Carlos Antonio da Silva + Rafael Mendonça França* -* check_box with `:form` html5 attribute will now replicate the `:form` +* `check_box` with `:form` html5 attribute will now replicate the `:form` attribute to the hidden field as well. *Carlos Antonio da Silva* * Turn off verbose mode of rack-cache, we still have X-Rack-Cache to check that info. Closes #5245. *Santiago Pastorino* -* `label` form helper accepts :for => nil to not generate the attribute. *Carlos Antonio da Silva* +* `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 `:format` option to `number_to_percentage`. *Rodrigo Flores* * Add `config.action_view.logger` to configure logger for Action View. *Rafael Mendonça França* @@ -712,7 +756,7 @@ * Deprecated `ActionController::Routing` in favour of `ActionDispatch::Routing`. -* `check_box helper` with `:disabled => true` will generate a disabled +* `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* diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 02ac111392..599fff81c2 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -40,19 +40,22 @@ module AbstractController end end - # Skip before, after, and around filters matching any of the names + # Skip before, after, and around action callbacks matching any of the names + # Aliased as skip_filter. # # ==== Parameters # * <tt>names</tt> - A list of valid names that could be used for # callbacks. Note that skipping uses Ruby equality, so it's # impossible to skip a callback defined using an anonymous proc # using #skip_filter - def skip_filter(*names) - skip_before_filter(*names) - skip_after_filter(*names) - skip_around_filter(*names) + def skip_action_callback(*names) + skip_before_action(*names) + skip_after_action(*names) + skip_around_action(*names) end + alias_method :skip_filter, :skip_action_callback + # Take callback names and an optional callback proc, normalize them, # then call the block with each callback. This allows us to abstract # the normalization across several methods that use it. @@ -75,119 +78,138 @@ module AbstractController end ## - # :method: before_filter + # :method: before_action # - # :call-seq: before_filter(names, block) + # :call-seq: before_action(names, block) # - # Append a before filter. See _insert_callbacks for parameter details. + # Append a callback before actions. See _insert_callbacks for parameter details. + # Aliased as before_filter. ## - # :method: prepend_before_filter + # :method: prepend_before_action # - # :call-seq: prepend_before_filter(names, block) + # :call-seq: prepend_before_action(names, block) # - # Prepend a before filter. See _insert_callbacks for parameter details. + # Prepend a callback before actions. See _insert_callbacks for parameter details. + # Aliased as prepend_before_filter. ## - # :method: skip_before_filter + # :method: skip_before_action # - # :call-seq: skip_before_filter(names) + # :call-seq: skip_before_action(names) # - # Skip a before filter. See _insert_callbacks for parameter details. + # Skip a callback before actions. See _insert_callbacks for parameter details. + # Aliased as skip_before_filter. ## - # :method: append_before_filter + # :method: append_before_action # - # :call-seq: append_before_filter(names, block) + # :call-seq: append_before_action(names, block) # - # Append a before filter. See _insert_callbacks for parameter details. + # Append a callback before actions. See _insert_callbacks for parameter details. + # Aliased as append_before_filter. ## - # :method: after_filter + # :method: after_action # - # :call-seq: after_filter(names, block) + # :call-seq: after_action(names, block) # - # Append an after filter. See _insert_callbacks for parameter details. + # Append a callback after actions. See _insert_callbacks for parameter details. + # Aliased as after_filter. ## - # :method: prepend_after_filter + # :method: prepend_after_action # - # :call-seq: prepend_after_filter(names, block) + # :call-seq: prepend_after_action(names, block) # - # Prepend an after filter. See _insert_callbacks for parameter details. + # Prepend a callback after actions. See _insert_callbacks for parameter details. + # Aliased as prepend_after_filter. ## - # :method: skip_after_filter + # :method: skip_after_action # - # :call-seq: skip_after_filter(names) + # :call-seq: skip_after_action(names) # - # Skip an after filter. See _insert_callbacks for parameter details. + # Skip a callback after actions. See _insert_callbacks for parameter details. + # Aliased as skip_after_filter. ## - # :method: append_after_filter + # :method: append_after_action # - # :call-seq: append_after_filter(names, block) + # :call-seq: append_after_action(names, block) # - # Append an after filter. See _insert_callbacks for parameter details. + # Append a callback after actions. See _insert_callbacks for parameter details. + # Aliased as append_after_filter. ## - # :method: around_filter + # :method: around_action # - # :call-seq: around_filter(names, block) + # :call-seq: around_action(names, block) # - # Append an around filter. See _insert_callbacks for parameter details. + # Append a callback around actions. See _insert_callbacks for parameter details. + # Aliased as around_filter. ## - # :method: prepend_around_filter + # :method: prepend_around_action # - # :call-seq: prepend_around_filter(names, block) + # :call-seq: prepend_around_action(names, block) # - # Prepend an around filter. See _insert_callbacks for parameter details. + # Prepend a callback around actions. See _insert_callbacks for parameter details. + # Aliased as prepend_around_filter. ## - # :method: skip_around_filter + # :method: skip_around_action # - # :call-seq: skip_around_filter(names) + # :call-seq: skip_around_action(names) # - # Skip an around filter. See _insert_callbacks for parameter details. + # Skip a callback around actions. See _insert_callbacks for parameter details. + # Aliased as skip_around_filter. ## - # :method: append_around_filter + # :method: append_around_action # - # :call-seq: append_around_filter(names, block) + # :call-seq: append_around_action(names, block) # - # Append an around filter. See _insert_callbacks for parameter details. + # Append a callback around actions. See _insert_callbacks for parameter details. + # Aliased as append_around_filter. - # set up before_filter, prepend_before_filter, skip_before_filter, etc. + # set up before_action, prepend_before_action, skip_before_action, etc. # for each of before, after, and around. - [:before, :after, :around].each do |filter| + [:before, :after, :around].each do |callback| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - # Append a before, after or around filter. See _insert_callbacks + # Append a before, after or around callback. See _insert_callbacks # for details on the allowed parameters. - def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk) - _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| - set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options) - end # end - end # end + def #{callback}_action(*names, &blk) # def before_action(*names, &blk) + _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, :#{callback}, name, options) # set_callback(:process_action, :before, name, options) + end # end + end # end + + alias_method :#{callback}_filter, :#{callback}_action - # Prepend a before, after or around filter. See _insert_callbacks + # Prepend a before, after or around callback. See _insert_callbacks # for details on the allowed parameters. - def prepend_#{filter}_filter(*names, &blk) # def prepend_before_filter(*names, &blk) - _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| - set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true)) - end # end - end # end + def prepend_#{callback}_action(*names, &blk) # def prepend_before_action(*names, &blk) + _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, :#{callback}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true)) + end # end + end # end + + alias_method :prepend_#{callback}_filter, :prepend_#{callback}_action - # Skip a before, after or around filter. See _insert_callbacks + # Skip a before, after or around callback. See _insert_callbacks # for details on the allowed parameters. - def skip_#{filter}_filter(*names) # def skip_before_filter(*names) - _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options| - skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options) - end # end - end # end - - # *_filter is the same as append_*_filter - alias_method :append_#{filter}_filter, :#{filter}_filter # alias_method :append_before_filter, :before_filter + def skip_#{callback}_action(*names) # def skip_before_action(*names) + _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options| + skip_callback(:process_action, :#{callback}, name, options) # skip_callback(:process_action, :before, name, options) + end # end + end # end + + alias_method :skip_#{callback}_filter, :skip_#{callback}_action + + # *_action is the same as append_*_action + alias_method :append_#{callback}_action, :#{callback}_action # alias_method :append_before_action, :before_action + alias_method :append_#{callback}_filter, :#{callback}_action # alias_method :append_before_filter, :before_action RUBY_EVAL end end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index d4e73bf257..36a0dcb2de 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -19,7 +19,7 @@ module AbstractController def inherited(klass) helpers = _helpers klass._helpers = Module.new { include helpers } - klass.class_eval { default_helper_module! unless anonymous? } + klass.class_eval { default_helper_module! } unless klass.anonymous? super end diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index c38d8ccef3..f1e8714a86 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -32,14 +32,14 @@ module ActionController # ==== 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>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_filter(options) do + before_action(options) do force_ssl_redirect(host) end end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index d2cbbd3330..35facd13c8 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -1,4 +1,3 @@ - module ActionController # The \Rails framework provides a large number of helpers for working with assets, dates, forms, # numbers and model objects, to name a few. These helpers are available to all templates @@ -91,11 +90,11 @@ module ActionController end def all_helpers_from_path(path) - helpers = [] - Array(path).each do |_path| - extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ + helpers = Array(path).flat_map do |_path| + extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } - helpers += names.sort + names.sort! + names end helpers.uniq! helpers diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index d3b5bafee1..283f6413ec 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -25,7 +25,7 @@ module ActionController # the regular HTML interface is protected by a session approach: # # class ApplicationController < ActionController::Base - # before_filter :set_account, :authenticate + # before_action :set_account, :authenticate # # protected # def set_account @@ -68,7 +68,7 @@ module ActionController module ClassMethods def http_basic_authenticate_with(options = {}) - before_filter(options.except(:name, :password, :realm)) do + before_action(options.except(:name, :password, :realm)) do authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password| name == options[:name] && password == options[:password] end @@ -124,7 +124,7 @@ module ActionController # USERS = {"dhh" => "secret", #plain text password # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password # - # before_filter :authenticate, except: [:index] + # before_action :authenticate, except: [:index] # # def index # render text: "Everyone can see me!" @@ -317,7 +317,7 @@ module ActionController # class PostsController < ApplicationController # TOKEN = "secret" # - # before_filter :authenticate, except: [ :index ] + # before_action :authenticate, except: [ :index ] # # def index # render text: "Everyone can see me!" @@ -340,7 +340,7 @@ module ActionController # the regular HTML interface is protected by a session approach: # # class ApplicationController < ActionController::Base - # before_filter :set_account, :authenticate + # before_action :set_account, :authenticate # # protected # def set_account diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index b23938e7d9..091facfd8d 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -74,7 +74,7 @@ module ActionController private def _extract_redirect_to_status(options, response_status) - status = if options.is_a?(Hash) && options.key?(:status) + if options.is_a?(Hash) && options.key?(:status) Rack::Utils.status_code(options.delete(:status)) elsif response_status.key?(:status) Rack::Utils.status_code(response_status[:status]) @@ -94,8 +94,7 @@ module ActionController when String request.protocol + request.host_with_port + options when :back - raise RedirectBackError unless refer = request.headers["Referer"] - refer + request.headers["Referer"] or raise RedirectBackError when Proc _compute_redirect_to_location options.call else diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 265ce5d6f3..c5db0cb0d4 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -19,7 +19,7 @@ module ActionController #:nodoc: # # class ApplicationController < ActionController::Base # protect_from_forgery - # skip_before_filter :verify_authenticity_token, if: :json_request? + # skip_before_action :verify_authenticity_token, if: :json_request? # # protected # @@ -66,15 +66,15 @@ module ActionController #:nodoc: # # You can disable csrf protection on controller-by-controller basis: # - # skip_before_filter :verify_authenticity_token + # skip_before_action :verify_authenticity_token # # It can also be disabled for specific controller actions: # - # skip_before_filter :verify_authenticity_token, except: [:create] + # skip_before_action :verify_authenticity_token, except: [:create] # # Valid Options: # - # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified. + # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified. # * <tt>:with</tt> - Set the method to handle unverified request. # # Valid unverified request handling methods are: @@ -84,7 +84,7 @@ module ActionController #:nodoc: def protect_from_forgery(options = {}) include protection_method_module(options[:with] || :null_session) self.request_forgery_protection_token ||= :authenticity_token - prepend_before_filter :verify_authenticity_token, options + prepend_before_action :verify_authenticity_token, options end private @@ -152,7 +152,7 @@ module ActionController #:nodoc: end protected - # The actual before_filter that is used. Modify this to change how you handle unverified requests. + # The actual before_action that is used. Modify this to change how you handle unverified requests. def verify_authenticity_token unless verified_request? logger.warn "Can't verify CSRF token authenticity" if logger diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 25e72adbe0..8faa5f8a13 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,5 +1,6 @@ require 'active_support/concern' require 'active_support/core_ext/hash/indifferent_access' +require 'active_support/core_ext/array/wrap' require 'active_support/rescuable' module ActionController diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 4a7df6b657..02ab49b44e 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -1,4 +1,3 @@ -require 'mutex_m' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/object/duplicable' @@ -21,8 +20,6 @@ module ActionDispatch # end # => reverses the value to all keys matching /secret/i module FilterParameters - @@parameter_filter_for = {}.extend(Mutex_m) - ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc: NULL_PARAM_FILTER = ParameterFilter.new # :nodoc: NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc: @@ -65,11 +62,7 @@ module ActionDispatch end def parameter_filter_for(filters) - @@parameter_filter_for.synchronize do - # Do we *actually* need this cache? Constructing ParameterFilters - # doesn't seem too expensive. - @@parameter_filter_for[filters] ||= ParameterFilter.new(filters) - end + ParameterFilter.new(filters) end KV_RE = '[^&;=]+' @@ -79,7 +72,6 @@ module ActionDispatch parameter_filter.filter([[$1, $2]]).first.join("=") end end - end end end diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 0f98e84788..57660e93c4 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -68,7 +68,7 @@ module ActionDispatch # that are not controlled by the extension. # # class ApplicationController < ActionController::Base - # before_filter :adjust_format_for_iphone + # before_action :adjust_format_for_iphone # # private # def adjust_format_for_iphone @@ -87,7 +87,7 @@ module ActionDispatch # to the :html format. # # class ApplicationController < ActionController::Base - # before_filter :adjust_format_for_iphone_with_html_fallback + # before_action :adjust_format_for_iphone_with_html_fallback # # private # def adjust_format_for_iphone_with_html_fallback diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 9a7b5bc8c7..6610315da7 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -12,7 +12,11 @@ module ActionDispatch # Returns both GET and POST \parameters in a single hash. def parameters @env["action_dispatch.request.parameters"] ||= begin - params = request_parameters.merge(query_parameters) + params = begin + request_parameters.merge(query_parameters) + rescue EOFError + query_parameters.dup + end params.merge!(path_parameters) encode_params(params).with_indifferent_access end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 3de927abc8..d60c8775af 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -205,8 +205,9 @@ module ActionDispatch # work with raw requests directly. def raw_post unless @env.include? 'RAW_POST_DATA' - @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i) - body.rewind if body.respond_to?(:rewind) + raw_post_body = body + @env['RAW_POST_DATA'] = raw_post_body.read(@env['CONTENT_LENGTH'].to_i) + raw_post_body.rewind if raw_post_body.respond_to?(:rewind) end @env['RAW_POST_DATA'] end diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index c18dc94d4f..8d7461ecc3 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -51,7 +51,7 @@ module ActionDispatch end def internal? - path =~ %r{/rails/info.*|^#{Rails.application.config.assets.prefix}} + controller =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}} end def engine? diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 0f95daa790..eb9d4b24f1 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,5 +1,6 @@ require 'journey' require 'forwardable' +require 'thread_safe' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/module/remove_method' @@ -20,7 +21,7 @@ module ActionDispatch def initialize(options={}) @defaults = options[:defaults] @glob_param = options.delete(:glob) - @controllers = {} + @controller_class_names = ThreadSafe::Cache.new end def call(env) @@ -68,13 +69,8 @@ module ActionDispatch private def controller_reference(controller_param) - controller_name = "#{controller_param.camelize}Controller" - - unless controller = @controllers[controller_param] - controller = @controllers[controller_param] = - ActiveSupport::Dependencies.reference(controller_name) - end - controller.get(controller_name) + const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller" + ActiveSupport::Dependencies.constantize(const_name) end def dispatch(controller, action, env) @@ -130,6 +126,12 @@ module ActionDispatch end def clear! + @helpers.each do |helper| + @module.module_eval do + remove_possible_method helper + end + end + @routes.clear @helpers.clear end @@ -288,7 +290,6 @@ module ActionDispatch def clear! @finalized = false - @url_helpers = nil named_routes.clear set.clear formatter.clear diff --git a/actionpack/lib/action_view/digestor.rb b/actionpack/lib/action_view/digestor.rb index 1c6eaf36f7..8bc69b9246 100644 --- a/actionpack/lib/action_view/digestor.rb +++ b/actionpack/lib/action_view/digestor.rb @@ -1,4 +1,4 @@ -require 'mutex_m' +require 'thread_safe' module ActionView class Digestor @@ -21,23 +21,12 @@ module ActionView /x cattr_reader(:cache) - @@cache = Hash.new.extend Mutex_m + @@cache = ThreadSafe::Cache.new def self.digest(name, format, finder, options = {}) - cache.synchronize do - unsafe_digest name, format, finder, options - end - end - - ### - # This method is NOT thread safe. DO NOT CALL IT DIRECTLY, instead call - # Digestor.digest - def self.unsafe_digest(name, format, finder, options = {}) # :nodoc: - key = "#{name}.#{format}" - - cache.fetch(key) do + @@cache["#{name}.#{format}"] ||= begin klass = options[:partial] || name.include?("/_") ? PartialDigestor : Digestor - cache[key] = klass.new(name, format, finder).digest + klass.new(name, format, finder).digest end end @@ -93,7 +82,7 @@ module ActionView def dependency_digest dependencies.collect do |template_name| - Digestor.unsafe_digest(template_name, format, finder, partial: true) + Digestor.digest(template_name, format, finder, partial: true) end.join("-") end diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 8693f4f0e4..995aa10afb 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -110,15 +110,8 @@ module ActionView # <%= some_helper_method(person) %> # # Now all you'll have to do is change that timestamp when the helper method changes. - # - # ==== Conditional caching - # - # You can pass :if and :unless options, to conditionally perform or skip the cache. - # - # <%= cache @model, if: some_condition(@model) do %> - # def cache(name = {}, options = nil, &block) - if controller.perform_caching && conditions_match?(options) + if controller.perform_caching safe_concat(fragment_for(cache_fragment_name(name, options), options, &block)) else yield @@ -127,6 +120,32 @@ module ActionView nil end + # Cache fragments of a view if +condition+ is true + # + # <%= cache_if admin?, project do %> + # <b>All the topics on this project</b> + # <%= render project.topics %> + # <% end %> + def cache_if(condition, name = {}, options = nil, &block) + if condition + cache(name, options, &block) + else + yield + end + + nil + end + + # Cache fragments of a view unless +condition+ is true + # + # <%= cache_unless admin?, project do %> + # <b>All the topics on this project</b> + # <%= render project.topics %> + # <% end %> + def cache_unless(condition, name = {}, options = nil, &block) + cache_if !condition, name, options, &block + end + # This helper returns the name of a cache key for a given fragment cache # call. By supplying skip_digest: true to cache, the digestion of cache # fragments can be manually bypassed. This is useful when cache fragments @@ -144,10 +163,6 @@ module ActionView private - def conditions_match?(options) - !(options && (!options.fetch(:if, true) || options.fetch(:unless, false))) - end - def fragment_name_with_digest(name) #:nodoc: if @virtual_path [ diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 17386a57b8..516492ca30 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -775,8 +775,8 @@ module ActionView # text_field(:post, :title, class: "create_input") # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" /> # - # text_field(:session, :user, onchange: "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }") - # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/> + # text_field(:session, :user, onchange: "if $('#session_user').value == 'admin' { alert('Your login can not be admin!'); }") + # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('#session_user').value == 'admin' { alert('Your login can not be admin!'); }"/> # # text_field(:snippet, :code, size: 20, class: 'code_input') # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" /> @@ -830,13 +830,25 @@ module ActionView # # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>. # + # ==== Options + # * Creates standard HTML attributes for the tag. + # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. + # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files. + # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. + # # ==== Examples # file_field(:user, :avatar) # # => <input type="file" id="user_avatar" name="user[avatar]" /> # + # file_field(:post, :image, :multiple => true) + # # => <input type="file" id="post_image" name="post[image]" multiple="true" /> + # # file_field(:post, :attached, accept: 'text/html') # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" /> # + # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg') + # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" /> + # # file_field(:attachment, :file, class: 'file_input') # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> def file_field(object_name, method, options = {}) @@ -1214,6 +1226,255 @@ module ActionView RUBY_EVAL end + # Creates a scope around a specific model object like form_for, but + # doesn't create the form tags themselves. This makes fields_for suitable + # for specifying additional model objects in the same form. + # + # Although the usage and purpose of +field_for+ is similar to +form_for+'s, + # its method signature is slightly different. Like +form_for+, it yields + # a FormBuilder object associated with a particular model object to a block, + # and within the block allows methods to be called on the builder to + # generate fields associated with the model object. Fields may reflect + # a model object in two ways - how they are named (hence how submitted + # values appear within the +params+ hash in the controller) and what + # default values are shown when the form the fields appear in is first + # displayed. In order for both of these features to be specified independently, + # both an object name (represented by either a symbol or string) and the + # object itself can be passed to the method separately - + # + # <%= form_for @person do |person_form| %> + # First name: <%= person_form.text_field :first_name %> + # Last name : <%= person_form.text_field :last_name %> + # + # <%= fields_for :permission, @person.permission do |permission_fields| %> + # Admin? : <%= permission_fields.check_box :admin %> + # <% end %> + # + # <%= f.submit %> + # <% end %> + # + # In this case, the checkbox field will be represented by an HTML +input+ + # tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted + # value will appear in the controller as <tt>params[:permission][:admin]</tt>. + # If <tt>@person.permission</tt> is an existing record with an attribute + # +admin+, the initial state of the checkbox when first displayed will + # reflect the value of <tt>@person.permission.admin</tt>. + # + # Often this can be simplified by passing just the name of the model + # object to +fields_for+ - + # + # <%= fields_for :permission do |permission_fields| %> + # Admin?: <%= permission_fields.check_box :admin %> + # <% end %> + # + # ...in which case, if <tt>:permission</tt> also happens to be the name of an + # instance variable <tt>@permission</tt>, the initial state of the input + # field will reflect the value of that variable's attribute <tt>@permission.admin</tt>. + # + # Alternatively, you can pass just the model object itself (if the first + # argument isn't a string or symbol +fields_for+ will realize that the + # name has been omitted) - + # + # <%= fields_for @person.permission do |permission_fields| %> + # Admin?: <%= permission_fields.check_box :admin %> + # <% end %> + # + # and +fields_for+ will derive the required name of the field from the + # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is + # of class +Permission+, the field will still be named <tt>permission[admin]</tt>. + # + # Note: This also works for the methods in FormOptionHelper and + # DateHelper that are designed to work with an object as base, like + # FormOptionHelper#collection_select and DateHelper#datetime_select. + # + # === Nested Attributes Examples + # + # When the object belonging to the current scope has a nested attribute + # writer for a certain attribute, fields_for will yield a new scope + # for that attribute. This allows you to create forms that set or change + # the attributes of a parent object and its associations in one go. + # + # Nested attribute writers are normal setter methods named after an + # association. The most common way of defining these writers is either + # with +accepts_nested_attributes_for+ in a model definition or by + # defining a method with the proper name. For example: the attribute + # writer for the association <tt>:address</tt> is called + # <tt>address_attributes=</tt>. + # + # Whether a one-to-one or one-to-many style form builder will be yielded + # depends on whether the normal reader method returns a _single_ object + # or an _array_ of objects. + # + # ==== One-to-one + # + # Consider a Person class which returns a _single_ Address from the + # <tt>address</tt> reader method and responds to the + # <tt>address_attributes=</tt> writer method: + # + # class Person + # def address + # @address + # end + # + # def address_attributes=(attributes) + # # Process the attributes hash + # end + # end + # + # This model can now be used with a nested fields_for, like so: + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :address do |address_fields| %> + # Street : <%= address_fields.text_field :street %> + # Zip code: <%= address_fields.text_field :zip_code %> + # <% end %> + # ... + # <% end %> + # + # When address is already an association on a Person you can use + # +accepts_nested_attributes_for+ to define the writer method for you: + # + # class Person < ActiveRecord::Base + # has_one :address + # accepts_nested_attributes_for :address + # end + # + # If you want to destroy the associated model through the form, you have + # to enable it first using the <tt>:allow_destroy</tt> option for + # +accepts_nested_attributes_for+: + # + # class Person < ActiveRecord::Base + # has_one :address + # accepts_nested_attributes_for :address, allow_destroy: true + # end + # + # Now, when you use a form element with the <tt>_destroy</tt> parameter, + # with a value that evaluates to +true+, you will destroy the associated + # model (eg. 1, '1', true, or 'true'): + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :address do |address_fields| %> + # ... + # Delete: <%= address_fields.check_box :_destroy %> + # <% end %> + # ... + # <% end %> + # + # ==== One-to-many + # + # Consider a Person class which returns an _array_ of Project instances + # from the <tt>projects</tt> reader method and responds to the + # <tt>projects_attributes=</tt> writer method: + # + # class Person + # def projects + # [@project1, @project2] + # end + # + # def projects_attributes=(attributes) + # # Process the attributes hash + # end + # end + # + # Note that the <tt>projects_attributes=</tt> writer method is in fact + # required for fields_for to correctly identify <tt>:projects</tt> as a + # collection, and the correct indices to be set in the form markup. + # + # When projects is already an association on Person you can use + # +accepts_nested_attributes_for+ to define the writer method for you: + # + # class Person < ActiveRecord::Base + # has_many :projects + # accepts_nested_attributes_for :projects + # end + # + # This model can now be used with a nested fields_for. The block given to + # the nested fields_for call will be repeated for each instance in the + # collection: + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :projects do |project_fields| %> + # <% if project_fields.object.active? %> + # Name: <%= project_fields.text_field :name %> + # <% end %> + # <% end %> + # ... + # <% end %> + # + # It's also possible to specify the instance to be used: + # + # <%= form_for @person do |person_form| %> + # ... + # <% @person.projects.each do |project| %> + # <% if project.active? %> + # <%= person_form.fields_for :projects, project do |project_fields| %> + # Name: <%= project_fields.text_field :name %> + # <% end %> + # <% end %> + # <% end %> + # ... + # <% end %> + # + # Or a collection to be used: + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :projects, @active_projects do |project_fields| %> + # Name: <%= project_fields.text_field :name %> + # <% end %> + # ... + # <% end %> + # + # When projects is already an association on Person you can use + # +accepts_nested_attributes_for+ to define the writer method for you: + # + # class Person < ActiveRecord::Base + # has_many :projects + # accepts_nested_attributes_for :projects + # end + # + # If you want to destroy any of the associated models through the + # form, you have to enable it first using the <tt>:allow_destroy</tt> + # option for +accepts_nested_attributes_for+: + # + # class Person < ActiveRecord::Base + # has_many :projects + # accepts_nested_attributes_for :projects, allow_destroy: true + # end + # + # This will allow you to specify which models to destroy in the + # attributes hash by adding a form element for the <tt>_destroy</tt> + # parameter with a value that evaluates to +true+ + # (eg. 1, '1', true, or 'true'): + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :projects do |project_fields| %> + # Delete: <%= project_fields.check_box :_destroy %> + # <% end %> + # ... + # <% end %> + # + # When a collection is used you might want to know the index of each + # object into the array. For this purpose, the <tt>index</tt> method + # is available in the FormBuilder object. + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :projects do |project_fields| %> + # Project #<%= project_fields.index %> + # ... + # <% end %> + # ... + # <% end %> + # + # Note that fields_for will automatically generate a hidden field + # to store the ID of the record. There are circumstances where this + # hidden field is not needed and you can pass <tt>hidden_field_id: false</tt> + # to prevent fields_for from rendering it automatically. def fields_for(record_name, record_object = nil, fields_options = {}, &block) fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options? fields_options[:builder] ||= options[:builder] @@ -1243,23 +1504,186 @@ module ActionView @template.fields_for(record_name, record_object, fields_options, &block) end + # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object + # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation + # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly. + # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged + # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to + # target labels for radio_button tags (where the value is used in the ID of the input tag). + # + # ==== Examples + # label(:post, :title) + # # => <label for="post_title">Title</label> + # + # You can localize your labels based on model and attribute names. + # For example you can define the following in your locale (e.g. en.yml) + # + # helpers: + # label: + # post: + # body: "Write your entire text here" + # + # Which then will result in + # + # label(:post, :body) + # # => <label for="post_body">Write your entire text here</label> + # + # Localization can also be based purely on the translation of the attribute-name + # (if you are using ActiveRecord): + # + # activerecord: + # attributes: + # post: + # cost: "Total cost" + # + # label(:post, :cost) + # # => <label for="post_cost">Total cost</label> + # + # label(:post, :title, "A short title") + # # => <label for="post_title">A short title</label> + # + # label(:post, :title, "A short title", class: "title_label") + # # => <label for="post_title" class="title_label">A short title</label> + # + # label(:post, :privacy, "Public Post", value: "public") + # # => <label for="post_privacy_public">Public Post</label> + # + # label(:post, :terms) do + # 'Accept <a href="/terms">Terms</a>.'.html_safe + # end def label(method, text = nil, options = {}, &block) @template.label(@object_name, method, text, objectify_options(options), &block) end + # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object + # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object. + # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked. + # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1 + # while the default +unchecked_value+ is set to 0 which is convenient for boolean values. + # + # ==== Gotcha + # + # The HTML specification says unchecked check boxes are not successful, and + # thus web browsers do not send them. Unfortunately this introduces a gotcha: + # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid + # invoice the user unchecks its check box, no +paid+ parameter is sent. So, + # any mass-assignment idiom like + # + # @invoice.update_attributes(params[:invoice]) + # + # wouldn't update the flag. + # + # To prevent this the helper generates an auxiliary hidden field before + # the very check box. The hidden field has the same name and its + # attributes mimic an unchecked check box. + # + # This way, the client either sends only the hidden field (representing + # the check box is unchecked), or both fields. Since the HTML specification + # says key/value pairs have to be sent in the same order they appear in the + # form, and parameters extraction gets the last occurrence of any repeated + # key in the query string, that works for ordinary forms. + # + # Unfortunately that workaround does not work when the check box goes + # within an array-like parameter, as in + # + # <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %> + # <%= form.check_box :paid %> + # ... + # <% end %> + # + # because parameter name repetition is precisely what Rails seeks to distinguish + # the elements of the array. For each item with a checked check box you + # get an extra ghost item with only that attribute, assigned to "0". + # + # In that case it is preferable to either use +check_box_tag+ or to use + # hashes instead of arrays. + # + # # Let's say that @post.validated? is 1: + # check_box("post", "validated") + # # => <input name="post[validated]" type="hidden" value="0" /> + # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" /> + # + # # Let's say that @puppy.gooddog is "no": + # check_box("puppy", "gooddog", {}, "yes", "no") + # # => <input name="puppy[gooddog]" type="hidden" value="no" /> + # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" /> + # + # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no") + # # => <input name="eula[accepted]" type="hidden" value="no" /> + # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" /> def check_box(method, options = {}, checked_value = "1", unchecked_value = "0") @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value) end + # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object + # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the + # radio button will be checked. + # + # To force the radio button to be checked pass <tt>checked: true</tt> in the + # +options+ hash. You may pass HTML options there as well. + # + # # Let's say that @post.category returns "rails": + # radio_button("post", "category", "rails") + # radio_button("post", "category", "java") + # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" /> + # # <input type="radio" id="post_category_java" name="post[category]" value="java" /> + # + # radio_button("user", "receive_newsletter", "yes") + # radio_button("user", "receive_newsletter", "no") + # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" /> + # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" /> def radio_button(method, tag_value, options = {}) @template.radio_button(@object_name, method, tag_value, objectify_options(options)) end + # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object + # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a + # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example + # shown. + # + # ==== Examples + # hidden_field(:signup, :pass_confirm) + # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" /> + # + # hidden_field(:post, :tag_list) + # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" /> + # + # hidden_field(:user, :token) + # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" /> + # def hidden_field(method, options = {}) @emitted_hidden_id = true if method == :id @template.hidden_field(@object_name, method, objectify_options(options)) end + # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object + # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a + # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example + # shown. + # + # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>. + # + # ==== Options + # * Creates standard HTML attributes for the tag. + # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. + # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files. + # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. + # + # ==== Examples + # file_field(:user, :avatar) + # # => <input type="file" id="user_avatar" name="user[avatar]" /> + # + # file_field(:post, :image, :multiple => true) + # # => <input type="file" id="post_image" name="post[image]" multiple="true" /> + # + # file_field(:post, :attached, accept: 'text/html') + # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" /> + # + # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg') + # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" /> + # + # file_field(:attachment, :file, class: 'file_input') + # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> def file_field(method, options = {}) self.multipart = true @template.file_field(@object_name, method, objectify_options(options)) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index c0e7ee1f8d..1b5b788a35 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -2,6 +2,7 @@ require 'cgi' require 'erb' require 'action_view/helpers/form_helper' require 'active_support/core_ext/string/output_safety' +require 'active_support/core_ext/array/wrap' module ActionView # = Action View Form Option Helpers diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index e298751062..ff83ef3ca1 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -233,6 +233,8 @@ module ActionView # ==== Options # * Creates standard HTML attributes for the tag. # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. + # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files. + # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. # # ==== Examples # file_field_tag 'attachment' diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb index 5d706087b0..6c400f85cb 100644 --- a/actionpack/lib/action_view/helpers/tags/date_select.rb +++ b/actionpack/lib/action_view/helpers/tags/date_select.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/time/calculations' + module ActionView module Helpers module Tags @@ -58,7 +60,7 @@ module ActionView default[key] ||= time.send(key) end - Time.utc_time( + Time.utc( default[:year], default[:month], default[:day], default[:hour], default[:min], default[:sec] ) diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 76f4dea7b8..4e4816d983 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -1,3 +1,4 @@ +require 'thread_safe' require 'active_support/core_ext/module/remove_method' module ActionView @@ -51,7 +52,7 @@ module ActionView alias :object_hash :hash attr_reader :hash - @details_keys = Hash.new + @details_keys = ThreadSafe::Cache.new def self.get(details) @details_keys[details] ||= new diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 8fb9b6ff18..37f93a13fc 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -1,3 +1,5 @@ +require 'thread_safe' + module ActionView # = Action View Partials # @@ -247,7 +249,9 @@ module ActionView # <%- end -%> # <% end %> class PartialRenderer < AbstractRenderer - PREFIXED_PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} } + PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k| + h[k] = ThreadSafe::Cache.new + end def initialize(*) super diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index fc77c1485d..8b23029bbc 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -3,7 +3,7 @@ require "active_support/core_ext/class" require "active_support/core_ext/class/attribute_accessors" require "action_view/template" require "thread" -require "mutex_m" +require "thread_safe" module ActionView # = Action View Resolver @@ -35,52 +35,51 @@ module ActionView # Threadsafe template cache class Cache #:nodoc: - class CacheEntry - include Mutex_m - - attr_accessor :templates + class SmallCache < ThreadSafe::Cache + def initialize(options = {}) + super(options.merge(:initial_capacity => 2)) + end end + # preallocate all the default blocks for performance/memory consumption reasons + PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new} + PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)} + NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)} + KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)} + + # usually a majority of template look ups return nothing, use this canonical preallocated array to safe memory + NO_TEMPLATES = [].freeze + def initialize - @data = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2| - h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } } - @mutex = Mutex.new + @data = SmallCache.new(&KEY_BLOCK) end # Cache the templates returned by the block def cache(key, name, prefix, partial, locals) - cache_entry = nil - - # first obtain a lock on the main data structure to create the cache entry - @mutex.synchronize do - cache_entry = @data[key][name][prefix][partial][locals] ||= CacheEntry.new - end - - # then to avoid a long lasting global lock, obtain a more granular lock - # on the CacheEntry itself - cache_entry.synchronize do - if Resolver.caching? - cache_entry.templates ||= yield + if Resolver.caching? + @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield) + else + fresh_templates = yield + cached_templates = @data[key][name][prefix][partial][locals] + + if templates_have_changed?(cached_templates, fresh_templates) + @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates) else - fresh_templates = yield - - if templates_have_changed?(cache_entry.templates, fresh_templates) - cache_entry.templates = fresh_templates - else - cache_entry.templates ||= [] - end + cached_templates || NO_TEMPLATES end end end def clear - @mutex.synchronize do - @data.clear - end + @data.clear end private + def canonical_no_templates(templates) + templates.empty? ? NO_TEMPLATES : templates + end + def templates_have_changed?(cached_templates, fresh_templates) # if either the old or new template list is empty, we don't need to (and can't) # compare modification times, and instead just check whether the lists are different diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 5d1a703c55..1090af3060 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -28,9 +28,9 @@ module AbstractController end class Callback2 < ControllerWithCallbacks - before_filter :first - after_filter :second - around_filter :aroundz + before_action :first + after_action :second + around_action :aroundz def first @text = "Hello world" @@ -53,7 +53,7 @@ module AbstractController end class Callback2Overwrite < Callback2 - before_filter :first, :except => :index + before_action :first, except: :index end class TestCallbacks2 < ActiveSupport::TestCase @@ -61,22 +61,22 @@ module AbstractController @controller = Callback2.new end - test "before_filter works" do + test "before_action works" do @controller.process(:index) assert_equal "Hello world", @controller.response_body end - test "after_filter works" do + test "after_action works" do @controller.process(:index) assert_equal "Goodbye", @controller.instance_variable_get("@second") end - test "around_filter works" do + test "around_action works" do @controller.process(:index) assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz") end - test "before_filter with overwritten condition" do + test "before_action with overwritten condition" do @controller = Callback2Overwrite.new @controller.process(:index) assert_equal "", @controller.response_body @@ -84,11 +84,11 @@ module AbstractController end class Callback3 < ControllerWithCallbacks - before_filter do |c| + before_action do |c| c.instance_variable_set("@text", "Hello world") end - after_filter do |c| + after_action do |c| c.instance_variable_set("@second", "Goodbye") end @@ -102,20 +102,20 @@ module AbstractController @controller = Callback3.new end - test "before_filter works with procs" do + test "before_action works with procs" do @controller.process(:index) assert_equal "Hello world", @controller.response_body end - test "after_filter works with procs" do + test "after_action works with procs" do @controller.process(:index) assert_equal "Goodbye", @controller.instance_variable_get("@second") end end class CallbacksWithConditions < ControllerWithCallbacks - before_filter :list, :only => :index - before_filter :authenticate, :except => :index + before_action :list, :only => :index + before_action :authenticate, :except => :index def index self.response_body = @list.join(", ") @@ -141,25 +141,25 @@ module AbstractController @controller = CallbacksWithConditions.new end - test "when :only is specified, a before filter is triggered on that action" do + test "when :only is specified, a before action is triggered on that action" do @controller.process(:index) assert_equal "Hello, World", @controller.response_body end - test "when :only is specified, a before filter is not triggered on other actions" do + test "when :only is specified, a before action is not triggered on other actions" do @controller.process(:sekrit_data) assert_equal "true", @controller.response_body end - test "when :except is specified, an after filter is not triggered on that action" do + test "when :except is specified, an after action is not triggered on that action" do @controller.process(:index) assert !@controller.instance_variable_defined?("@authenticated") end end class CallbacksWithArrayConditions < ControllerWithCallbacks - before_filter :list, :only => [:index, :listy] - before_filter :authenticate, :except => [:index, :listy] + before_action :list, only: [:index, :listy] + before_action :authenticate, except: [:index, :listy] def index self.response_body = @list.join(", ") @@ -185,24 +185,24 @@ module AbstractController @controller = CallbacksWithArrayConditions.new end - test "when :only is specified with an array, a before filter is triggered on that action" do + test "when :only is specified with an array, a before action is triggered on that action" do @controller.process(:index) assert_equal "Hello, World", @controller.response_body end - test "when :only is specified with an array, a before filter is not triggered on other actions" do + test "when :only is specified with an array, a before action is not triggered on other actions" do @controller.process(:sekrit_data) assert_equal "true", @controller.response_body end - test "when :except is specified with an array, an after filter is not triggered on that action" do + test "when :except is specified with an array, an after action is not triggered on that action" do @controller.process(:index) assert !@controller.instance_variable_defined?("@authenticated") end end class ChangedConditions < Callback2 - before_filter :first, :only => :index + before_action :first, :only => :index def not_index @text ||= nil @@ -227,7 +227,7 @@ module AbstractController end class SetsResponseBody < ControllerWithCallbacks - before_filter :set_body + before_action :set_body def index self.response_body = "Fail" @@ -266,6 +266,50 @@ module AbstractController end end + class AliasedCallbacks < ControllerWithCallbacks + before_filter :first + after_filter :second + around_filter :aroundz + + def first + @text = "Hello world" + end + + def second + @second = "Goodbye" + end + + def aroundz + @aroundz = "FIRST" + yield + @aroundz << "SECOND" + end + def index + @text ||= nil + self.response_body = @text.to_s + end + end + + class TestAliasedCallbacks < ActiveSupport::TestCase + def setup + @controller = AliasedCallbacks.new + end + + test "before_filter works" do + @controller.process(:index) + assert_equal "Hello world", @controller.response_body + end + + test "after_filter works" do + @controller.process(:index) + assert_equal "Goodbye", @controller.instance_variable_get("@second") + end + + test "around_filter works" do + @controller.process(:index) + assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz") + end + end end end diff --git a/actionpack/test/controller/default_url_options_with_filter_test.rb b/actionpack/test/controller/default_url_options_with_before_action_test.rb index 9a9ab17fee..656fd0431e 100644 --- a/actionpack/test/controller/default_url_options_with_filter_test.rb +++ b/actionpack/test/controller/default_url_options_with_before_action_test.rb @@ -1,10 +1,10 @@ require 'abstract_unit' -class ControllerWithBeforeFilterAndDefaultUrlOptions < ActionController::Base +class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base - before_filter { I18n.locale = params[:locale] } - after_filter { I18n.locale = "en" } + before_action { I18n.locale = params[:locale] } + after_action { I18n.locale = "en" } def target render :text => "final response" @@ -19,11 +19,11 @@ class ControllerWithBeforeFilterAndDefaultUrlOptions < ActionController::Base end end -class ControllerWithBeforeFilterAndDefaultUrlOptionsTest < ActionController::TestCase +class ControllerWithBeforeActionAndDefaultUrlOptionsTest < ActionController::TestCase # This test has its roots in issue #1872 test "should redirect with correct locale :de" do get :redirect, :locale => "de" - assert_redirected_to "/controller_with_before_filter_and_default_url_options/target?locale=de" + assert_redirected_to "/controller_with_before_action_and_default_url_options/target?locale=de" end end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 6414ba3994..9d4356f546 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -53,8 +53,8 @@ class FlashTest < ActionController::TestCase render :inline => "hello" end - # methods for test_sweep_after_halted_filter_chain - before_filter :halt_and_redir, :only => "filter_halting_action" + # methods for test_sweep_after_halted_action_chain + before_action :halt_and_redir, only: 'filter_halting_action' def std_action @flash_copy = {}.update(flash) @@ -159,7 +159,7 @@ class FlashTest < ActionController::TestCase assert_nil session["flash"] end - def test_sweep_after_halted_filter_chain + def test_sweep_after_halted_action_chain get :std_action assert_nil assigns["flash_copy"]["foo"] get :filter_halting_action diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index 2dcfda02a7..90548d4294 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -2,9 +2,9 @@ require 'abstract_unit' class HttpBasicAuthenticationTest < ActionController::TestCase class DummyController < ActionController::Base - before_filter :authenticate, :only => :index - before_filter :authenticate_with_request, :only => :display - before_filter :authenticate_long_credentials, :only => :show + before_action :authenticate, only: :index + before_action :authenticate_with_request, only: :display + before_action :authenticate_long_credentials, only: :show http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index c4a94264c3..537de7a2dd 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -4,8 +4,8 @@ require 'active_support/key_generator' class HttpDigestAuthenticationTest < ActionController::TestCase class DummyDigestController < ActionController::Base - before_filter :authenticate, :only => :index - before_filter :authenticate_with_request, :only => :display + before_action :authenticate, only: :index + before_action :authenticate_with_request, only: :display USERS = { 'lifo' => 'world', 'pretty' => 'please', 'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))} diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb index ad4e743be8..8a409d6ed2 100644 --- a/actionpack/test/controller/http_token_authentication_test.rb +++ b/actionpack/test/controller/http_token_authentication_test.rb @@ -2,9 +2,9 @@ require 'abstract_unit' class HttpTokenAuthenticationTest < ActionController::TestCase class DummyController < ActionController::Base - before_filter :authenticate, :only => :index - before_filter :authenticate_with_request, :only => :display - before_filter :authenticate_long_credentials, :only => :show + before_action :authenticate, only: :index + before_action :authenticate_with_request, only: :display + before_action :authenticate_long_credentials, only: :show def index render :text => "Hello Secret" diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index d8b971f963..075347be52 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -13,7 +13,7 @@ module Another head :status => 406 end - before_filter :redirector, :only => :never_executed + before_action :redirector, only: :never_executed def never_executed end @@ -46,20 +46,20 @@ module Another render :inline => "<%= cache('foo%bar'){ 'Contains % sign in key' } %>" end - def with_fragment_cache_and_if_true_condition - render :inline => "<%= cache('foo', :if => true) { 'bar' } %>" + def with_fragment_cache_if_with_true_condition + render :inline => "<%= cache_if(true, 'foo') { 'bar' } %>" end - def with_fragment_cache_and_if_false_condition - render :inline => "<%= cache('foo', :if => false) { 'bar' } %>" + def with_fragment_cache_if_with_false_condition + render :inline => "<%= cache_if(false, 'foo') { 'bar' } %>" end - def with_fragment_cache_and_unless_false_condition - render :inline => "<%= cache('foo', :unless => false) { 'bar' } %>" + def with_fragment_cache_unless_with_false_condition + render :inline => "<%= cache_unless(false, 'foo') { 'bar' } %>" end - def with_fragment_cache_and_unless_true_condition - render :inline => "<%= cache('foo', :unless => true) { 'bar' } %>" + def with_fragment_cache_unless_with_true_condition + render :inline => "<%= cache_unless(true, 'foo') { 'bar' } %>" end def with_exception @@ -219,9 +219,9 @@ class ACLogSubscriberTest < ActionController::TestCase @controller.config.perform_caching = true end - def test_with_fragment_cache_and_if_true + def test_with_fragment_cache_if_with_true @controller.config.perform_caching = true - get :with_fragment_cache_and_if_true_condition + get :with_fragment_cache_if_with_true_condition wait assert_equal 4, logs.size @@ -231,9 +231,9 @@ class ACLogSubscriberTest < ActionController::TestCase @controller.config.perform_caching = true end - def test_with_fragment_cache_and_if_false + def test_with_fragment_cache_if_with_false @controller.config.perform_caching = true - get :with_fragment_cache_and_if_false_condition + get :with_fragment_cache_if_with_false_condition wait assert_equal 2, logs.size @@ -243,9 +243,9 @@ class ACLogSubscriberTest < ActionController::TestCase @controller.config.perform_caching = true end - def test_with_fragment_cache_and_unless_true + def test_with_fragment_cache_unless_with_true @controller.config.perform_caching = true - get :with_fragment_cache_and_unless_true_condition + get :with_fragment_cache_unless_with_true_condition wait assert_equal 2, logs.size @@ -255,9 +255,9 @@ class ACLogSubscriberTest < ActionController::TestCase @controller.config.perform_caching = true end - def test_with_fragment_cache_and_unless_false + def test_with_fragment_cache_unless_with_false @controller.config.perform_caching = true - get :with_fragment_cache_and_unless_false_condition + get :with_fragment_cache_unless_with_false_condition wait assert_equal 4, logs.size diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index d183b0be17..ed013e2185 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -1139,7 +1139,7 @@ end # For testing layouts which are set automatically class PostController < AbstractPostController - around_filter :with_iphone + around_action :with_iphone def index respond_to(:html, :iphone, :js) diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb index ed244513a5..964f22eb03 100644 --- a/actionpack/test/controller/new_base/base_test.rb +++ b/actionpack/test/controller/new_base/base_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' # Tests the controller dispatching happy path module Dispatching class SimpleController < ActionController::Base - before_filter :authenticate + before_action :authenticate def index render :text => "success" diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb index f41b14d5d6..177a1c088d 100644 --- a/actionpack/test/controller/new_base/render_context_test.rb +++ b/actionpack/test/controller/new_base/render_context_test.rb @@ -14,7 +14,7 @@ module RenderContext include ActionView::Context # 2) Call _prepare_context that will do the required initialization - before_filter :_prepare_context + before_action :_prepare_context def hello_world @value = "Hello" diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 859ed1466b..7640bc12a2 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -37,7 +37,7 @@ end class TestController < ActionController::Base protect_from_forgery - before_filter :set_variable_for_layout + before_action :set_variable_for_layout class LabellingFormBuilder < ActionView::Helpers::FormBuilder end @@ -137,7 +137,7 @@ class TestController < ActionController::Base def conditional_hello_with_bangs render :action => 'hello_world' end - before_filter :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs + before_action :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs def handle_last_modified_and_etags fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ]) @@ -710,7 +710,7 @@ class TestController < ActionController::Base render :action => "calling_partial_with_layout", :layout => "layouts/partial_with_layout" end - before_filter :only => :render_with_filters do + before_action only: :render_with_filters do request.format = :xml end diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 48e2d6491e..4898b0c57f 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -68,9 +68,9 @@ class RescueController < ActionController::Base render :text => 'io error' end - before_filter(:only => :before_filter_raises) { raise 'umm nice' } + before_action(only: :before_action_raises) { raise 'umm nice' } - def before_filter_raises + def before_action_raises end def raises diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index 718d06ef38..888791b874 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -5,7 +5,7 @@ module ShowExceptions use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public") use ActionDispatch::DebugExceptions - before_filter :only => :another_boom do + before_action only: :another_boom do request.env["action_dispatch.show_detailed_exceptions"] = true end diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index 40f6dc6f0f..c6e7a523b9 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -4,7 +4,7 @@ class ViewLoadPathsTest < ActionController::TestCase class TestController < ActionController::Base def self.controller_path() "test" end - before_filter :add_view_path, :only => :hello_world_at_request_time + before_action :add_view_path, only: :hello_world_at_request_time def hello_world() end def hello_world_at_request_time() render(:action => 'hello_world') end diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 63c5ea26a6..399f15199c 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -123,6 +123,18 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest end end + # This can happen in Internet Explorer when redirecting after multipart form submit. + test "does not raise EOFError on GET request with multipart content-type" do + with_routing do |set| + set.draw do + get ':action', to: 'multipart_params_parsing_test/test' + end + headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" } + get "/parse", {}, headers + assert_response :ok + end + end + private def fixture(name) File.open(File.join(FIXTURE_PATH, name), 'rb') do |file| diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index f2bacf3e20..263853fb6c 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -650,6 +650,13 @@ class RequestTest < ActiveSupport::TestCase assert_equal Mime::XML, request.negotiate_mime([Mime::XML, Mime::CSV]) end + test "raw_post rewinds rack.input if RAW_POST_DATA is nil" do + request = stub_request('rack.input' => StringIO.new("foo"), + 'CONTENT_LENGTH' => 3) + assert_equal "foo", request.raw_post + assert_equal "foo", request.env['rack.input'].read + end + test "process parameter filter" do test_hashes = [ [{'foo'=>'bar'},{'foo'=>'bar'},%w'food'], diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb new file mode 100644 index 0000000000..d57b1a5637 --- /dev/null +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -0,0 +1,86 @@ +require 'abstract_unit' + +module ActionDispatch + module Routing + class RouteSetTest < ActiveSupport::TestCase + class SimpleApp + def initialize(response) + @response = response + end + + def call(env) + [ 200, { 'Content-Type' => 'text/plain' }, [response] ] + end + end + + setup do + @set = RouteSet.new + end + + test "url helpers are added when route is added" do + draw do + get 'foo', to: SimpleApp.new('foo#index') + end + + assert_equal '/foo', url_helpers.foo_path + assert_raises NoMethodError do + assert_equal '/bar', url_helpers.bar_path + end + + draw do + get 'foo', to: SimpleApp.new('foo#index') + get 'bar', to: SimpleApp.new('bar#index') + end + + assert_equal '/foo', url_helpers.foo_path + assert_equal '/bar', url_helpers.bar_path + end + + test "url helpers are updated when route is updated" do + draw do + get 'bar', to: SimpleApp.new('bar#index'), as: :bar + end + + assert_equal '/bar', url_helpers.bar_path + + draw do + get 'baz', to: SimpleApp.new('baz#index'), as: :bar + end + + assert_equal '/baz', url_helpers.bar_path + end + + test "url helpers are removed when route is removed" do + draw do + get 'foo', to: SimpleApp.new('foo#index') + get 'bar', to: SimpleApp.new('bar#index') + end + + assert_equal '/foo', url_helpers.foo_path + assert_equal '/bar', url_helpers.bar_path + + draw do + get 'foo', to: SimpleApp.new('foo#index') + end + + assert_equal '/foo', url_helpers.foo_path + assert_raises NoMethodError do + assert_equal '/bar', url_helpers.bar_path + end + end + + private + def clear! + @set.clear! + end + + def draw(&block) + @set.draw(&block) + end + + def url_helpers + @set.url_helpers + end + end + end +end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index b955e98835..014500fa28 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,4 +1,23 @@ ## Rails 4.0.0 (unreleased) ## +* Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the + absence of attributes. + + class Person < ActiveRecord::Base + validates_absence_of :first_name + end + + person = Person.new + person.first_name = "John" + person.valid? + => false + # first_name must be blank + + * Roberto Vasquez Angel* + +* Added `ActiveModel::Errors#add_on_present` method. Adds error messages to + present attributes. + + *Roberto Vasquez Angel* * `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!` @@ -19,7 +38,7 @@ *Yves Senn* -* Use BCrypt's MIN_COST in the test environment for speedier tests when using `has_secure_pasword`. +* Use BCrypt's `MIN_COST` in the test environment for speedier tests when using `has_secure_pasword`. *Brian Cardarella + Jeremy Kemper + Trevor Turk* diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index af11da1351..db5759ada9 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -1,3 +1,4 @@ +require 'thread_safe' module ActiveModel # Raised when an attribute is not defined. @@ -337,17 +338,17 @@ module ActiveModel # significantly (in our case our test suite finishes 10% faster with # this cache). def attribute_method_matchers_cache #:nodoc: - @attribute_method_matchers_cache ||= {} + @attribute_method_matchers_cache ||= ThreadSafe::Cache.new(:initial_capacity => 4) end def attribute_method_matcher(method_name) #:nodoc: - attribute_method_matchers_cache.fetch(method_name) do |name| + attribute_method_matchers_cache.compute_if_absent(method_name) do # Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix # will match every time. matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1) match = nil - matchers.detect { |method| match = method.match(name) } - attribute_method_matchers_cache[name] = match + matchers.detect { |method| match = method.match(method_name) } + match end end diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 963e52bff3..b713e99e25 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -328,6 +328,19 @@ module ActiveModel end end + # Will add an error message to each of the attributes in +attributes+ that + # is present (using Object#present?). + # + # person.errors.add_on_present(:name) + # person.errors.messages + # # => { :name => ["must be blank"] } + def add_on_present(attributes, options = {}) + Array(attributes).flatten.each do |attribute| + value = @base.send(:read_attribute_for_validation, attribute) + add(attribute, :not_blank, options) if value.present? + end + end + # Returns +true+ if an error on the attribute with the given message is # present, +false+ otherwise. +message+ is treated the same as for +add+. # diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml index d17848c861..8ea34b84f5 100644 --- a/activemodel/lib/active_model/locale/en.yml +++ b/activemodel/lib/active_model/locale/en.yml @@ -13,6 +13,7 @@ en: accepted: "must be accepted" empty: "can't be empty" blank: "can't be blank" + not_blank: "must be blank" too_long: "is too long (maximum is %{count} characters)" too_short: "is too short (minimum is %{count} characters)" wrong_length: "is the wrong length (should be %{count} characters)" diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 4a17a63e20..648ae7ce3d 100755 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/hash/conversions' require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/time/acts_like' module ActiveModel module Serializers @@ -20,7 +21,11 @@ module ActiveModel def initialize(name, serializable, value) @name, @serializable = name, serializable - value = value.in_time_zone if value.respond_to?(:in_time_zone) + + if value.acts_like?(:time) && value.respond_to?(:in_time_zone) + value = value.in_time_zone + end + @value = value @type = compute_type end diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb new file mode 100644 index 0000000000..6790554907 --- /dev/null +++ b/activemodel/lib/active_model/validations/absence.rb @@ -0,0 +1,31 @@ +module ActiveModel + module Validations + # == Active Model Absence Validator + class AbsenceValidator < EachValidator #:nodoc: + def validate(record) + record.errors.add_on_present(attributes, options) + end + end + + module HelperMethods + # Validates that the specified attributes are blank (as defined by + # Object#blank?). Happens by default on save. + # + # class Person < ActiveRecord::Base + # validates_absence_of :first_name + # end + # + # The first_name attribute must be in the object and it must be blank. + # + # Configuration options: + # * <tt>:message</tt> - A custom error message (default is: "must be blank"). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+ and +:strict+. + # See <tt>ActiveModel::Validation#validates</tt> for more information + def validates_absence_of(*attr_names) + validates_with AbsenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb new file mode 100644 index 0000000000..c05d71de5a --- /dev/null +++ b/activemodel/test/cases/validations/absence_validation_test.rb @@ -0,0 +1,67 @@ +# encoding: utf-8 +require 'cases/helper' +require 'models/topic' +require 'models/person' +require 'models/custom_reader' + +class AbsenceValidationTest < ActiveModel::TestCase + teardown do + Topic.reset_callbacks(:validate) + Person.reset_callbacks(:validate) + CustomReader.reset_callbacks(:validate) + end + + def test_validate_absences + Topic.validates_absence_of(:title, :content) + t = Topic.new + t.title = "foo" + t.content = "bar" + assert t.invalid? + assert_equal ["must be blank"], t.errors[:title] + assert_equal ["must be blank"], t.errors[:content] + t.title = "" + t.content = "something" + assert t.invalid? + assert_equal ["must be blank"], t.errors[:content] + t.content = "" + assert t.valid? + end + + def test_accepts_array_arguments + Topic.validates_absence_of %w(title content) + t = Topic.new + t.title = "foo" + t.content = "bar" + assert t.invalid? + assert_equal ["must be blank"], t.errors[:title] + assert_equal ["must be blank"], t.errors[:content] + end + + def test_validates_acceptance_of_with_custom_error_using_quotes + Person.validates_absence_of :karma, message: "This string contains 'single' and \"double\" quotes" + p = Person.new + p.karma = "good" + assert p.invalid? + assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last + end + + def test_validates_absence_of_for_ruby_class + Person.validates_absence_of :karma + p = Person.new + p.karma = "good" + assert p.invalid? + assert_equal ["must be blank"], p.errors[:karma] + p.karma = nil + assert p.valid? + end + + def test_validates_absence_of_for_ruby_class_with_custom_reader + CustomReader.validates_absence_of :karma + p = CustomReader.new + p[:karma] = "excellent" + assert p.invalid? + assert_equal ["must be blank"], p.errors[:karma] + p[:karma] = "" + assert p.valid? + end +end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 57612bd3b8..97b7b8cd1e 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,25 +1,78 @@ ## Rails 4.0.0 (unreleased) ## -* Add migration history to schema.rb dump. - Loading schema.rb with full migration history - restores the exact list of migrations that created - that schema (including names and fingerprints). This - avoids possible mistakes caused by assuming all - migrations with a lower version have been run when - loading schema.rb. Old schema.rb files without migration - history but with the :version setting still work as before. - - *Josh Susser* - -* Add metadata columns to schema_migrations table. - New columns are: migrated_at (timestamp), - fingerprint (md5 hash of migration source), and - name (filename minus version and extension) - - *Josh Susser* - -* 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. +* 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'. + Fix #8492 + + *Yves Senn* + +* Add `ActiveRecord::Base.cache_timestamp_format` class attribute to control + the format of the timestamp value in the cache key. + This allows users to improve the precision of the cache key. + Fixes #8195. + + *Rafael Mendonça França* + +* Add `:nsec` date format. This can be used to improve the precision of cache key. + + *Jamie Gaskins* + +* Session variables can be set for the `mysql`, `mysql2`, and `postgresql` adapters + in the `variables: <hash>` parameter in `database.yml`. The key-value pairs of this + 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. Fix #8414 *kennyj* @@ -31,8 +84,8 @@ *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 + 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. @@ -63,7 +116,7 @@ * Fix postgresql adapter to handle BC timestamps correctly - HistoryEvent.create!(:name => "something", :occured_at => Date.new(0) - 5.years) + HistoryEvent.create!(name: "something", occured_at: Date.new(0) - 5.years) *Bogdan Gusiev* @@ -694,7 +747,7 @@ *kennyj* -* Changed validates_presence_of on an association so that children objects +* 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. @@ -711,7 +764,7 @@ def change create_table :foobars do |t| - t.timestamps :precision => 0 + t.timestamps precision: 0 end end @@ -1077,11 +1130,11 @@ Note that you do not need to explicitly specify references in the following cases, as they can be automatically inferred: - Post.where(comments: { name: 'foo' }) - Post.where('comments.name' => 'foo') - Post.order('comments.name') + Post.includes(:comments).where(comments: { name: 'foo' }) + Post.includes(:comments).where('comments.name' => 'foo') + Post.includes(:comments).order('comments.name') - You also do not need to worry about this unless you are doing eager + 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. diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index c7d8a84a7e..c3266f2bb4 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -153,6 +153,11 @@ module ActiveRecord delete_through_records(records) + if source_reflection.options[:counter_cache] + counter = source_reflection.counter_cache_column + klass.decrement_counter counter, records.map(&:id) + end + if through_reflection.macro == :has_many && update_through_counter?(method) update_counter(-count, through_reflection) end diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index 6c5e2ac05d..ecfa556ab4 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -132,7 +132,7 @@ module ActiveRecord if object.class.send(:create_time_zone_conversion_attribute?, name, column) Time.zone.local(*set_values) else - Time.time_with_datetime_fallback(object.class.default_timezone, *set_values) + Time.send(object.class.default_timezone, *set_values) end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index b5a8011ca4..82d0cf7e2e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -1,4 +1,5 @@ require 'thread' +require 'thread_safe' require 'monitor' require 'set' require 'active_support/deprecation' @@ -236,9 +237,6 @@ module ActiveRecord @spec = spec - # The cache of reserved connections mapped to threads - @reserved_connections = {} - @checkout_timeout = spec.config[:checkout_timeout] || 5 @dead_connection_timeout = spec.config[:dead_connection_timeout] @reaper = Reaper.new self, spec.config[:reaping_frequency] @@ -247,6 +245,9 @@ module ActiveRecord # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 + # The cache of reserved connections mapped to threads + @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size) + @connections = [] @automatic_reconnect = true @@ -267,7 +268,9 @@ module ActiveRecord # #connection can be called any number of times; the connection is # held in a hash keyed by the thread id. def connection - synchronize do + # this is correctly done double-checked locking + # (ThreadSafe::Cache's lookups have volatile semantics) + @reserved_connections[current_connection_id] || synchronize do @reserved_connections[current_connection_id] ||= checkout end end @@ -310,7 +313,7 @@ module ActiveRecord # Disconnects all connections in the pool, and clears the pool. def disconnect! synchronize do - @reserved_connections = {} + @reserved_connections.clear @connections.each do |conn| checkin conn conn.disconnect! @@ -323,7 +326,7 @@ module ActiveRecord # Clears the cache which maps classes. def clear_reloadable_connections! synchronize do - @reserved_connections = {} + @reserved_connections.clear @connections.each do |conn| checkin conn conn.disconnect! if conn.requires_reloading? @@ -490,11 +493,15 @@ module ActiveRecord # determine the connection pool that they should use. class ConnectionHandler def initialize - # These hashes are keyed by klass.name, NOT klass. Keying them by klass + # These caches are keyed by klass.name, NOT klass. Keying them by klass # alone would lead to memory leaks in development mode as all previous # instances of the class would stay in memory. - @owner_to_pool = Hash.new { |h,k| h[k] = {} } - @class_to_pool = Hash.new { |h,k| h[k] = {} } + @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k| + h[k] = ThreadSafe::Cache.new(:initial_capacity => 2) + end + @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k| + h[k] = ThreadSafe::Cache.new + end end def connection_pool_list diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 7ec6abbc45..73012834c9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -190,16 +190,16 @@ module ActiveRecord # # What can be written like this with the regular calls to column: # - # create_table "products", force: true do |t| - # t.column "shop_id", :integer - # t.column "creator_id", :integer - # t.column "name", :string, default: "Untitled" - # t.column "value", :string, default: "Untitled" - # t.column "created_at", :datetime - # t.column "updated_at", :datetime + # create_table :products do |t| + # t.column :shop_id, :integer + # t.column :creator_id, :integer + # t.column :name, :string, default: "Untitled" + # t.column :value, :string, default: "Untitled" + # t.column :created_at, :datetime + # t.column :updated_at, :datetime # end # - # Can also be written as follows using the short-hand: + # can also be written as follows using the short-hand: # # create_table :products do |t| # t.integer :shop_id, :creator_id diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index a470e8de07..f1e42dfbbe 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -490,8 +490,8 @@ module ActiveRecord sm_table = ActiveRecord::Migrator.schema_migrations_table_name ActiveRecord::SchemaMigration.order('version').map { |sm| - "INSERT INTO #{sm_table} (version, migrated_at, fingerprint, name) VALUES ('#{sm.version}',LOCALTIMESTAMP,'#{sm.fingerprint}','#{sm.name}');" - }.join("\n\n") + "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');" + }.join "\n\n" end # Should not be called normally, but this operation is non-destructive. @@ -512,7 +512,7 @@ module ActiveRecord end unless migrated.include?(version) - ActiveRecord::SchemaMigration.create!(:version => version, :migrated_at => Time.now) + execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" end inserted = Set.new @@ -520,7 +520,7 @@ module ActiveRecord if inserted.include?(v) raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict." elsif v < version - ActiveRecord::SchemaMigration.create!(:version => v, :migrated_at => Time.now) + execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" inserted << v 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 84e73e6f0f..d37e489f5c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -704,6 +704,45 @@ module ActiveRecord end column end + + def configure_connection + variables = @config[:variables] || {} + + # By default, MySQL 'where id is null' selects the last inserted id. + # Turn this off. http://dev.rubyonrails.org/ticket/6778 + variables[:sql_auto_is_null] = 0 + + # Increase timeout so the server doesn't disconnect us. + wait_timeout = @config[:wait_timeout] + wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum) + variables[:wait_timeout] = wait_timeout + + # Make MySQL reject illegal values rather than truncating or blanking them, see + # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables + # If the user has provided another value for sql_mode, don't replace it. + if strict_mode? && !variables.has_key?(:sql_mode) + variables[:sql_mode] = 'STRICT_ALL_TABLES' + end + + # NAMES does not have an equals sign, see + # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430 + # (trailing comma because variable_assignments will always have content) + encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding] + + # Gather up all of the SET variables... + variable_assignments = variables.map do |k, v| + if v == ':default' || v == :default + "@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default + elsif !v.nil? + "@@SESSION.#{k.to_s} = #{quote(v)}" + end + # or else nil; compact to clear nils out + end.compact.join(', ') + + # ...and send them all in one query + execute("SET #{encoding} #{variable_assignments}", :skip_logging) + end + end end end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 80984f39c9..df23dbfb60 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -240,7 +240,7 @@ module ActiveRecord # Treat 0000-00-00 00:00:00 as nil. return nil if year.nil? || (year == 0 && mon == 0 && mday == 0) - Time.time_with_datetime_fallback(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil + Time.send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil end def fast_string_to_date(string) diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index f55d19393c..a6013f754a 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -251,27 +251,7 @@ module ActiveRecord def configure_connection @connection.query_options.merge!(:as => :array) - - # By default, MySQL 'where id is null' selects the last inserted id. - # Turn this off. http://dev.rubyonrails.org/ticket/6778 - variable_assignments = ['SQL_AUTO_IS_NULL=0'] - - # Make MySQL reject illegal values rather than truncating or - # blanking them. See - # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables - variable_assignments << "SQL_MODE='STRICT_ALL_TABLES'" if strict_mode? - - encoding = @config[:encoding] - - # make sure we set the encoding - variable_assignments << "NAMES '#{encoding}'" if encoding - - # increase timeout so mysql server doesn't disconnect us - wait_timeout = @config[:wait_timeout] - wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum) - variable_assignments << "@@wait_timeout = #{wait_timeout}" - - execute("SET #{variable_assignments.join(', ')}", :skip_logging) + super end def version diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index e9677415cc..631f646f58 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -51,7 +51,8 @@ module ActiveRecord # * <tt>:database</tt> - The name of the database. No default, must be provided. # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection. # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html). - # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html) + # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html) + # * <tt>:variables</tt> - (Optional) A hash session variables to send as `SET @@SESSION.key = value` on each database connection. Use the value `:default` to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html). # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection. # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection. # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection. @@ -535,18 +536,10 @@ module ActiveRecord configure_connection end + # Many Rails applications monkey-patch a replacement of the configure_connection method + # and don't call 'super', so leave this here even though it looks superfluous. def configure_connection - encoding = @config[:encoding] - execute("SET NAMES '#{encoding}'", :skip_logging) if encoding - - # By default, MySQL 'where id is null' selects the last inserted id. - # Turn this off. http://dev.rubyonrails.org/ticket/6778 - execute("SET SQL_AUTO_IS_NULL=0", :skip_logging) - - # Make MySQL reject illegal values rather than truncating or - # blanking them. See - # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables - execute("SET SQL_MODE='STRICT_ALL_TABLES'", :skip_logging) if strict_mode? + super end def select(sql, name = nil, binds = []) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e18464fa35..e24ee1efdd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -24,7 +24,7 @@ module ActiveRecord # Forward any unused config params to PGconn.connect. [:statement_limit, :encoding, :min_messages, :schema_search_path, :schema_order, :adapter, :pool, :checkout_timeout, :template, - :reaping_frequency, :insert_returning].each do |key| + :reaping_frequency, :insert_returning, :variables].each do |key| conn_params.delete key end conn_params.delete_if { |k,v| v.nil? } @@ -238,6 +238,8 @@ module ActiveRecord # <encoding></tt> call on the connection. # * <tt>:min_messages</tt> - An optional client min messages that is used in a # <tt>SET client_min_messages TO <min_messages></tt> call on the connection. + # * <tt>:variables</tt> - An optional hash of additional parameters that + # will be used in <tt>SET SESSION key = val</tt> calls on the connection. # * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements # defaults to true. # @@ -706,11 +708,24 @@ module ActiveRecord # If using Active Record's time zone support configure the connection to return # TIMESTAMP WITH ZONE types in UTC. + # (SET TIME ZONE does not use an equals sign like other SET variables) if ActiveRecord::Base.default_timezone == :utc execute("SET time zone 'UTC'", 'SCHEMA') elsif @local_tz execute("SET time zone '#{@local_tz}'", 'SCHEMA') end + + # SET statements from :variables config hash + # http://www.postgresql.org/docs/8.3/static/sql-set.html + variables = @config[:variables] || {} + variables.map do |k, v| + if v == ':default' || v == :default + # Sets the value to the global or compile default + execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA') + elsif !v.nil? + execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA') + end + end end # Returns the current ID of a table's sequence. diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb index 7bdc1bd4c6..7f877a6471 100644 --- a/activerecord/lib/active_record/integration.rb +++ b/activerecord/lib/active_record/integration.rb @@ -1,5 +1,16 @@ module ActiveRecord module Integration + extend ActiveSupport::Concern + + included do + ## + # :singleton-method: + # Indicates the format used to generate the timestamp format in the cache key. + # This is +:number+, by default. + class_attribute :cache_timestamp_format, :instance_writer => false + self.cache_timestamp_format = :nsec + end + # Returns a String, which Action Pack uses for constructing an URL to this # object. The default implementation returns this record's id as a String, # or nil if this record's unsaved. @@ -37,7 +48,7 @@ module ActiveRecord when new_record? "#{self.class.model_name.cache_key}/new" when timestamp = self[:updated_at] - timestamp = timestamp.utc.to_s(:nsec) + timestamp = timestamp.utc.to_s(cache_timestamp_format) "#{self.class.model_name.cache_key}/#{id}-#{timestamp}" else "#{self.class.model_name.cache_key}/#{id}" diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index ca79950049..2366a91bb5 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -1,7 +1,7 @@ module ActiveRecord class LogSubscriber < ActiveSupport::LogSubscriber IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"] - + def self.runtime=(value) Thread.current[:active_record_sql_runtime] = value end @@ -20,6 +20,16 @@ module ActiveRecord @odd_or_even = false end + def render_bind(column, value) + if column.type == :binary + rendered_value = "<#{value.bytesize} bytes of binary data>" + else + rendered_value = value + end + + [column.name, rendered_value] + end + def sql(event) self.class.runtime += event.duration return unless logger.debug? @@ -34,7 +44,7 @@ module ActiveRecord unless (payload[:binds] || []).empty? binds = " " + payload[:binds].map { |col,v| - [col.name, v] + render_bind(col, v) }.inspect end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 4ce276d4bf..ef2107ad24 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1,6 +1,5 @@ require "active_support/core_ext/class/attribute_accessors" require 'set' -require 'digest/md5' module ActiveRecord # Exception that can be raised to stop migrations from going backwards. @@ -555,10 +554,6 @@ module ActiveRecord delegate :migrate, :announce, :write, :to => :migration - def fingerprint - @fingerprint ||= Digest::MD5.hexdigest(File.read(filename)) - end - private def migration @@ -670,7 +665,7 @@ module ActiveRecord files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }] migrations = files.map do |file| - version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first + version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/).first raise IllegalMigrationNameError.new(file) unless version version = version.to_i @@ -729,7 +724,7 @@ module ActiveRecord 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) + record_version_state_after_migrating(target.version) end end @@ -752,7 +747,7 @@ module ActiveRecord begin ddl_transaction do migration.migrate(@direction) - record_version_state_after_migrating(migration) + record_version_state_after_migrating(migration.version) end rescue => e canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : "" @@ -810,18 +805,13 @@ module ActiveRecord raise DuplicateMigrationVersionError.new(version) if version end - def record_version_state_after_migrating(target) + def record_version_state_after_migrating(version) if down? - migrated.delete(target.version) - ActiveRecord::SchemaMigration.where(:version => target.version.to_s).delete_all + migrated.delete(version) + ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all else - migrated << target.version - ActiveRecord::SchemaMigration.create!( - :version => target.version.to_s, - :migrated_at => Time.now, - :fingerprint => target.fingerprint, - :name => File.basename(target.filename,'.rb').gsub(/^\d+_/,'') - ) + migrated << version + ActiveRecord::SchemaMigration.create!(:version => version.to_s) end end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 628ab0f566..85fb4be992 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -224,11 +224,10 @@ module ActiveRecord def decorate_columns(columns_hash) # :nodoc: return if columns_hash.empty? - serialized_attributes.each_key do |key| - columns_hash[key] = AttributeMethods::Serialization::Type.new(columns_hash[key]) - end - columns_hash.each do |name, col| + if serialized_attributes.key?(name) + columns_hash[name] = AttributeMethods::Serialization::Type.new(col) + end if create_time_zone_conversion_attribute?(name, col) columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(col) end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 94c109e72b..4d1a9c94b7 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -259,7 +259,7 @@ module ActiveRecord verify_readonly_attribute(key.to_s) end - updated_count = self.class.where(self.class.primary_key => id).update_all(attributes) + updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes) attributes.each do |k, v| raw_write_attribute(k, v) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 0a9caa25b2..b25c0270c2 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -167,7 +167,7 @@ db_namespace = namespace :db do # desc "Raises an error if there are pending migrations" task :abort_if_pending_migrations => [:environment, :load_config] do - pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_paths).pending_migrations + pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations if pending_migrations.any? puts "You have #{pending_migrations.size} pending migrations:" diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 2184625e22..431d083f21 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -1,5 +1,6 @@ require 'active_support/concern' -require 'mutex_m' +require 'thread' +require 'thread_safe' module ActiveRecord module Delegation # :nodoc: @@ -73,8 +74,7 @@ module ActiveRecord end module ClassMethods - # This hash is keyed by klass.name to avoid memory leaks in development mode - @@subclasses = Hash.new { |h, k| h[k] = {} }.extend(Mutex_m) + @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2) def new(klass, *args) relation = relation_class_for(klass).allocate @@ -82,33 +82,27 @@ module ActiveRecord relation end + # This doesn't have to be thread-safe. relation_class_for guarantees that this will only be + # called exactly once for a given const name. + def const_missing(name) + const_set(name, Class.new(self) { include ClassSpecificRelation }) + end + + private # Cache the constants in @@subclasses because looking them up via const_get # make instantiation significantly slower. def relation_class_for(klass) - if klass && klass.name - if subclass = @@subclasses.synchronize { @@subclasses[self][klass.name] } - subclass - else - subclass = const_get("#{name.gsub('::', '_')}_#{klass.name.gsub('::', '_')}", false) - @@subclasses.synchronize { @@subclasses[self][klass.name] = subclass } - subclass + if klass && (klass_name = klass.name) + my_cache = @@subclasses.compute_if_absent(self) { ThreadSafe::Cache.new } + # This hash is keyed by klass.name to avoid memory leaks in development mode + my_cache.compute_if_absent(klass_name) do + # Cache#compute_if_absent guarantees that the block will only executed once for the given klass_name + const_get("#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}", false) end else ActiveRecord::Relation end end - - # Check const_defined? in case another thread has already defined the constant. - # I am not sure whether this is strictly necessary. - def const_missing(name) - @@subclasses.synchronize { - if const_defined?(name) - const_get(name) - else - const_set(name, Class.new(self) { include ClassSpecificRelation }) - end - } - end end def respond_to?(method, include_private = false) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index a480ddec9e..46c0d6206f 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -4,6 +4,51 @@ module ActiveRecord module QueryMethods extend ActiveSupport::Concern + # WhereChain objects act as placeholder for queries in which #where does not have any parameter. + # In this case, #where must be chained with either #not, #like, or #not_like to return a new relation. + class WhereChain + def initialize(scope) + @scope = scope + end + + # Returns a new relation expressing WHERE + NOT condition + # according to the conditions in the arguments. + # + # #not accepts conditions in one of these formats: String, Array, Hash. + # See #where for more details on each format. + # + # User.where.not("name = 'Jon'") + # # SELECT * FROM users WHERE NOT (name = 'Jon') + # + # User.where.not(["name = ?", "Jon"]) + # # SELECT * FROM users WHERE NOT (name = 'Jon') + # + # User.where.not(name: "Jon") + # # SELECT * FROM users WHERE name != 'Jon' + # + # User.where.not(name: nil) + # # SELECT * FROM users WHERE name IS NOT NULL + # + # User.where.not(name: %w(Ko1 Nobu)) + # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu') + def not(opts, *rest) + where_value = @scope.send(:build_where, opts, rest).map do |rel| + case rel + when Arel::Nodes::In + Arel::Nodes::NotIn.new(rel.left, rel.right) + when Arel::Nodes::Equality + Arel::Nodes::NotEqual.new(rel.left, rel.right) + when String + Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel)) + else + Arel::Nodes::Not.new(rel) + end + end + @scope.where_values += where_value + @scope + end + end + Relation::MULTI_VALUE_METHODS.each do |name| class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}_values # def select_values @@ -370,18 +415,41 @@ module ActiveRecord # User.joins(:posts).where({ "posts.published" => true }) # User.joins(:posts).where({ posts: { published: true } }) # - # === empty condition + # === no argument + # + # If no argument is passed, #where returns a new instance of WhereChain, that + # can be chained with #not to return a new relation that negates the where clause. + # + # User.where.not(name: "Jon") + # # SELECT * FROM users WHERE name != 'Jon' + # + # See WhereChain for more details on #not. # - # If the condition returns true for blank?, then where is a no-op and returns the current relation. - def where(opts, *rest) - opts.blank? ? self : spawn.where!(opts, *rest) + # === blank condition + # + # If the condition is any blank-ish object, then #where is a no-op and returns + # the current relation. + def where(opts = :chain, *rest) + if opts == :chain + WhereChain.new(spawn) + elsif opts.blank? + self + else + spawn.where!(opts, *rest) + end end - def where!(opts, *rest) # :nodoc: - references!(PredicateBuilder.references(opts)) if Hash === opts + # #where! is identical to #where, except that instead of returning a new relation, it adds + # the condition to the existing relation. + def where!(opts = :chain, *rest) # :nodoc: + if opts == :chain + WhereChain.new(self) + else + references!(PredicateBuilder.references(opts)) if Hash === opts - self.where_values += build_where(opts, rest) - self + self.where_values += build_where(opts, rest) + self + end end # Allows to specify a HAVING clause. Note that you can't use HAVING diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index 44b7eb424b..3259dbbd80 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -39,45 +39,27 @@ module ActiveRecord end def define(info, &block) # :nodoc: - @using_deprecated_version_setting = info[:version].present? - SchemaMigration.drop_table - initialize_schema_migrations_table - instance_eval(&block) - # handle files from pre-4.0 that used :version option instead of dumping migration table - assume_migrated_upto_version(info[:version], migrations_paths) if @using_deprecated_version_setting + unless info[:version].blank? + initialize_schema_migrations_table + assume_migrated_upto_version(info[:version], migrations_paths) + end end # Eval the given block. All methods available to the current connection # adapter are available within the block, so you can easily use the # database definition DSL to build up your schema (+create_table+, # +add_index+, etc.). - def self.define(info={}, &block) - new.define(info, &block) - end - - # Create schema migration history. Include migration statements in a block to this method. - # - # migrations do - # migration 20121128235959, "44f1397e3b92442ca7488a029068a5ad", "add_horn_color_to_unicorns" - # migration 20121129235959, "4a1eb3965d94406b00002b370854eae8", "add_magic_power_to_unicorns" - # end - def migrations - raise(ArgumentError, "Can't set migrations while using :version option") if @using_deprecated_version_setting - yield - end - - # Add a migration to the ActiveRecord::SchemaMigration table. # - # The +version+ argument is an integer. - # The +fingerprint+ and +name+ arguments are required but may be empty strings. - # The migration's +migrated_at+ attribute is set to the current time, - # instead of being set explicitly as an argument to the method. + # The +info+ hash is optional, and if given is used to define metadata + # about the current schema (currently, only the schema's version): # - # migration 20121129235959, "4a1eb3965d94406b00002b370854eae8", "add_magic_power_to_unicorns" - def migration(version, fingerprint, name) - SchemaMigration.create!(version: version, migrated_at: Time.now, fingerprint: fingerprint, name: name) + # ActiveRecord::Schema.define(version: 20380119000001) do + # ... + # end + def self.define(info={}, &block) + new.define(info, &block) end end end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 73c0f5b9eb..36bde44e7c 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -24,7 +24,6 @@ module ActiveRecord def dump(stream) header(stream) - migrations(stream) tables(stream) trailer(stream) stream @@ -45,7 +44,7 @@ module ActiveRecord stream.puts "# encoding: #{stream.external_encoding.name}" end - header_text = <<HEADER_RUBY + stream.puts <<HEADER # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. @@ -60,25 +59,13 @@ module ActiveRecord ActiveRecord::Schema.define(#{define_params}) do -HEADER_RUBY - stream.puts header_text +HEADER end def trailer(stream) stream.puts "end" end - def migrations(stream) - all_migrations = ActiveRecord::SchemaMigration.all.to_a - if all_migrations.any? - stream.puts(" migrations do") - all_migrations.each do |migration| - stream.puts(migration.schema_line(" ")) - end - stream.puts(" end") - end - end - def tables(stream) @connection.tables.sort.each do |tbl| next if ['schema_migrations', ignore_tables].flatten.any? do |ignored| diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index b2ae369eb6..9830abe7d8 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -14,38 +14,17 @@ module ActiveRecord end def self.create_table - if connection.table_exists?(table_name) - cols = connection.columns(table_name).collect { |col| col.name } - unless cols.include?("migrated_at") - connection.add_column(table_name, "migrated_at", :datetime) - q_table_name = connection.quote_table_name(table_name) - q_timestamp = connection.quoted_date(Time.now) - connection.update("UPDATE #{q_table_name} SET migrated_at = '#{q_timestamp}' WHERE migrated_at IS NULL") - connection.change_column(table_name, "migrated_at", :datetime, :null => false) - end - unless cols.include?("fingerprint") - connection.add_column(table_name, "fingerprint", :string, :limit => 32) - end - unless cols.include?("name") - connection.add_column(table_name, "name", :string) - end - else + unless connection.table_exists?(table_name) connection.create_table(table_name, :id => false) do |t| t.column :version, :string, :null => false - t.column :migrated_at, :datetime, :null => false - t.column :fingerprint, :string, :limit => 32 - t.column :name, :string end - connection.add_index(table_name, "version", :unique => true, :name => index_name) + connection.add_index table_name, :version, :unique => true, :name => index_name end - reset_column_information end def self.drop_table - if connection.index_exists?(table_name, "version", :unique => true, :name => index_name) - connection.remove_index(table_name, :name => index_name) - end if connection.table_exists?(table_name) + connection.remove_index table_name, :name => index_name connection.drop_table(table_name) end end @@ -53,17 +32,5 @@ module ActiveRecord def version super.to_i end - - # Construct ruby source to include in schema.rb dump for this migration. - # Pass a string of spaces as +indent+ to allow calling code to control how deeply indented the line is. - # The generated line includes the migration version, fingerprint, and name. Either fingerprint or name - # can be an empty string. - # - # Example output: - # - # migration 20121129235959, "ee4be703f9e6e2fc0f4baddebe6eb8f7", "add_magic_power_to_unicorns" - def schema_line(indent) - %Q(#{indent}migration %s, "%s", "%s") % [version, fingerprint, name] - end end end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 534dc2c2df..ffd6904aec 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -137,6 +137,23 @@ class MysqlConnectionTest < ActiveRecord::TestCase end end + def test_mysql_set_session_variable + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}})) + session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" + assert_equal 3, session_mode.rows.first.first.to_i + end + end + + def test_mysql_set_session_variable_to_default + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}})) + global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT" + session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" + assert_equal global_mode.rows, session_mode.rows + end + end + private def run_without_connection diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 14c22d2519..1265cb927e 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -53,6 +53,23 @@ class MysqlConnectionTest < ActiveRecord::TestCase end end + def test_mysql_set_session_variable + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}})) + session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" + assert_equal 3, session_mode.rows.first.first.to_i + end + end + + def test_mysql_set_session_variable_to_default + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}})) + global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT" + session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" + assert_equal global_mode.rows, session_mode.rows + end + end + def test_logs_name_structure_dump @connection.structure_dump assert_equal "SCHEMA", @connection.logged[0][1] diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 1ff307c735..fa8f339f00 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -154,5 +154,46 @@ module ActiveRecord end end + def test_set_session_variable_true + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => true}})) + set_true = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN" + assert_equal set_true.rows, [["on"]] + end + end + + def test_set_session_variable_false + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => false}})) + set_false = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN" + assert_equal set_false.rows, [["off"]] + end + end + + def test_set_session_variable_nil + run_without_connection do |orig_connection| + # This should be a no-op that does not raise an error + ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => nil}})) + end + end + + def test_set_session_variable_default + run_without_connection do |orig_connection| + # This should execute a query that does not raise an error + ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}})) + end + end + + private + + def run_without_connection + original_connection = ActiveRecord::Base.remove_connection + begin + yield original_connection + ensure + ActiveRecord::Base.establish_connection(original_connection) + end + end + end end diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index bd47ba8741..b2eac0349b 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -46,55 +46,4 @@ if ActiveRecord::Base.connection.supports_migrations? end end - class ActiveRecordSchemaMigrationsTest < ActiveRecordSchemaTest - def setup - super - ActiveRecord::SchemaMigration.delete_all - end - - def test_migration_adds_row_to_migrations_table - schema = ActiveRecord::Schema.new - schema.migration(1001, "", "") - schema.migration(1002, "123456789012345678901234567890ab", "add_magic_power_to_unicorns") - - migrations = ActiveRecord::SchemaMigration.all.to_a - assert_equal 2, migrations.length - - assert_equal 1001, migrations[0].version - assert_match %r{^2\d\d\d-}, migrations[0].migrated_at.to_s(:db) - assert_equal "", migrations[0].fingerprint - assert_equal "", migrations[0].name - - assert_equal 1002, migrations[1].version - assert_match %r{^2\d\d\d-}, migrations[1].migrated_at.to_s(:db) - assert_equal "123456789012345678901234567890ab", migrations[1].fingerprint - assert_equal "add_magic_power_to_unicorns", migrations[1].name - end - - def test_define_clears_schema_migrations - assert_nothing_raised do - ActiveRecord::Schema.define do - migrations do - migration(123001, "", "") - end - end - ActiveRecord::Schema.define do - migrations do - migration(123001, "", "") - end - end - end - end - - def test_define_raises_if_both_version_and_explicit_migrations - assert_raise(ArgumentError) do - ActiveRecord::Schema.define(version: 123001) do - migrations do - migration(123001, "", "") - end - end - 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 d25aca760f..7e6c7d5862 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -298,12 +298,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 2, Firm.order(:id).find{|f| f.id > 0}.clients.length end - def test_find_with_blank_conditions - [[], {}, nil, ""].each do |blank| - assert_equal 2, Firm.all.merge!(:order => "id").first.clients.where(blank).to_a.size - end - end - def test_find_many_with_merged_options assert_equal 1, companies(:first_firm).limited_clients.size assert_equal 1, companies(:first_firm).limited_clients.to_a.size diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 8e52ce1d91..2b96b42032 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -330,6 +330,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end + def test_update_counter_caches_on_replace_association + post = posts(:welcome) + tag = post.tags.create!(:name => 'doomed') + tag.tagged_posts << posts(:thinking) + + tag.tagged_posts = [] + post.reload + + assert_equal(post.taggings.count, post.taggings_count) + end + def test_replace_association assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)} diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 8644f2f496..d326ed7863 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -23,6 +23,8 @@ require 'models/edge' require 'models/joke' require 'models/bulb' require 'models/bird' +require 'models/car' +require 'models/bulb' require 'rexml/document' require 'active_support/core_ext/exception' @@ -1442,6 +1444,21 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key end + def test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format + dev = CachedDeveloper.first + assert_equal "cached_developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key + end + + def test_cache_key_changes_when_child_touched + car = Car.create + Bulb.create(car: car) + + key = car.cache_key + car.bulb.touch + car.reload + assert_not_equal key, car.cache_key + end + def test_cache_key_format_for_existing_record_with_nil_updated_at dev = Developer.first dev.update_columns(updated_at: nil) diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index 3deb0dac99..427076bd80 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -8,15 +8,15 @@ class DateTimeTest < ActiveRecord::TestCase with_active_record_default_timezone :utc do time_values = [1807, 2, 10, 15, 30, 45] # create DateTime value with local time zone offset - local_offset = Rational(Time.local_time(*time_values).utc_offset, 86400) + local_offset = Rational(Time.local(*time_values).utc_offset, 86400) now = DateTime.civil(*(time_values + [local_offset])) task = Task.new task.starting = now task.save! - # check against Time.local_time, since some platforms will return a Time instead of a DateTime - assert_equal Time.local_time(*time_values), Task.find(task.id).starting + # check against Time.local, since some platforms will return a Time instead of a DateTime + assert_equal Time.local(*time_values), Task.find(task.id).starting end end end diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 70d00aecf9..9fee219753 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require "models/binary" require "models/developer" require "models/post" require "active_support/log_subscriber/test_helper" @@ -100,4 +101,13 @@ class LogSubscriberTest < ActiveRecord::TestCase def test_initializes_runtime Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join end + + def test_binary_data_is_not_logged + skip if current_adapter?(:Mysql2Adapter) + + Binary.create(:data => 'some binary data') + wait + assert_equal 3, @logger.logged(:debug).size + assert_match(/<16 bytes of binary data>/, @logger.logged(:debug)[-2]) + end end diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb index 6e62c1fcbe..ee0c20747e 100644 --- a/activerecord/test/cases/migration/logger_test.rb +++ b/activerecord/test/cases/migration/logger_test.rb @@ -6,12 +6,10 @@ module ActiveRecord # mysql can't roll back ddl changes self.use_transactional_fixtures = false - Migration = Struct.new(:name, :version, :filename, :fingerprint) do + Migration = Struct.new(:name, :version) do def migrate direction # do nothing end - def filename; "anon.rb"; end - def fingerprint; "123456789012345678901234567890ab"; end end def setup diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb new file mode 100644 index 0000000000..8fd770abd1 --- /dev/null +++ b/activerecord/test/cases/migration/table_and_index_test.rb @@ -0,0 +1,24 @@ +require "cases/helper" + +module ActiveRecord + class Migration + class TableAndIndexTest < ActiveRecord::TestCase + def test_add_schema_info_respects_prefix_and_suffix + conn = ActiveRecord::Base.connection + + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters + ActiveRecord::Base.table_name_prefix = 'p_' + ActiveRecord::Base.table_name_suffix = '_s' + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + + conn.initialize_schema_migrations_table + + assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name] + ensure + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + end + end + end +end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 60e5a29965..c155f29973 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -59,21 +59,12 @@ class MigrationTest < ActiveRecord::TestCase def test_migrator_versions migrations_path = MIGRATIONS_ROOT + "/valid" ActiveRecord::Migrator.migrations_paths = migrations_path - m0_path = File.join(migrations_path, "1_valid_people_have_last_names.rb") - m0_fingerprint = Digest::MD5.hexdigest(File.read(m0_path)) ActiveRecord::Migrator.up(migrations_path) assert_equal 3, ActiveRecord::Migrator.current_version assert_equal 3, ActiveRecord::Migrator.last_version assert_equal false, ActiveRecord::Migrator.needs_migration? - rows = connection.select_all("SELECT * FROM #{connection.quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)}") - assert_equal m0_fingerprint, rows[0]["fingerprint"] - assert_equal "valid_people_have_last_names", rows[0]["name"] - rows.each do |row| - assert_match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/, row["migrated_at"], "missing migrated_at") - end - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid") assert_equal 0, ActiveRecord::Migrator.current_version assert_equal 3, ActiveRecord::Migrator.last_version @@ -346,7 +337,7 @@ class MigrationTest < ActiveRecord::TestCase assert_nothing_raised { Person.connection.create_table :binary_testings do |t| - t.column :data, :binary, :null => false + t.column "data", :binary, :null => false end } diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb index 0f0384382f..199d0c584b 100644 --- a/activerecord/test/cases/migrator_test.rb +++ b/activerecord/test/cases/migrator_test.rb @@ -18,9 +18,6 @@ module ActiveRecord def up; @went_up = true; end def down; @went_down = true; end - # also used in place of a MigrationProxy - def filename; "anon.rb"; end - def fingerprint; "123456789012345678901234567890ab"; end end def setup @@ -87,6 +84,12 @@ module ActiveRecord end end + def test_finds_migrations_in_numbered_directory + migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + '/10_urban'] + assert_equal 9, migrations[0].version + assert_equal 'AddExpressions', migrations[0].name + end + def test_deprecated_constructor assert_deprecated do ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid") @@ -105,7 +108,7 @@ module ActiveRecord end def test_finds_pending_migrations - ActiveRecord::SchemaMigration.create!(:version => '1', :name => "anon", :migrated_at => Time.now) + ActiveRecord::SchemaMigration.create!(:version => '1') migration_list = [ Migration.new('foo', 1), Migration.new('bar', 3) ] migrations = ActiveRecord::Migrator.new(:up, migration_list).pending_migrations @@ -155,7 +158,7 @@ module ActiveRecord end def test_current_version - ActiveRecord::SchemaMigration.create!(:version => '1000', :name => "anon", :migrated_at => Time.now) + ActiveRecord::SchemaMigration.create!(:version => '1000') assert_equal 1000, ActiveRecord::Migrator.current_version end @@ -323,7 +326,7 @@ module ActiveRecord def test_only_loads_pending_migrations # migrate up to 1 - ActiveRecord::SchemaMigration.create!(:version => '1', :name => "anon", :migrated_at => Time.now) + ActiveRecord::SchemaMigration.create!(:version => '1') calls, migrator = migrator_class(3) migrator.migrate("valid", nil) diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 02034c87b4..9e0423ab52 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -512,6 +512,14 @@ class PersistencesTest < ActiveRecord::TestCase assert_equal 'super_title', t.title end + def test_update_column_with_default_scope + developer = DeveloperCalledDavid.first + developer.name = 'John' + developer.save! + + assert developer.update_column(:name, 'Will'), 'did not update record due to default scope' + end + def test_update_columns topic = Topic.find(1) topic.update_columns({ "approved" => true, title: "Sebastian Topic" }) @@ -616,6 +624,14 @@ class PersistencesTest < ActiveRecord::TestCase assert_equal true, topic.update_columns(title: "New title") end + def test_update_columns_with_default_scope + developer = DeveloperCalledDavid.first + developer.name = 'John' + developer.save! + + assert developer.update_columns(name: 'Will'), 'did not update record due to default scope' + end + def test_update_attributes topic = Topic.find(1) assert !topic.approved? diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb new file mode 100644 index 0000000000..8ce44636b4 --- /dev/null +++ b/activerecord/test/cases/relation/where_chain_test.rb @@ -0,0 +1,75 @@ +require 'cases/helper' +require 'models/post' +require 'models/comment' + +module ActiveRecord + class WhereChainTest < ActiveRecord::TestCase + fixtures :posts + + def test_not_eq + expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'hello') + relation = Post.where.not(title: 'hello') + assert_equal([expected], relation.where_values) + end + + def test_not_null + expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], nil) + relation = Post.where.not(title: nil) + assert_equal([expected], relation.where_values) + end + + def test_not_in + expected = Arel::Nodes::NotIn.new(Post.arel_table[:title], %w[hello goodbye]) + relation = Post.where.not(title: %w[hello goodbye]) + assert_equal([expected], relation.where_values) + end + + def test_association_not_eq + expected = Arel::Nodes::NotEqual.new(Comment.arel_table[:title], 'hello') + relation = Post.joins(:comments).where.not(comments: {title: 'hello'}) + assert_equal(expected.to_sql, relation.where_values.first.to_sql) + end + + def test_not_eq_with_preceding_where + relation = Post.where(title: 'hello').where.not(title: 'world') + + expected = Arel::Nodes::Equality.new(Post.arel_table[:title], 'hello') + assert_equal(expected, relation.where_values.first) + + expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'world') + assert_equal(expected, relation.where_values.last) + end + + def test_not_eq_with_succeeding_where + relation = Post.where.not(title: 'hello').where(title: 'world') + + expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'hello') + assert_equal(expected, relation.where_values.first) + + expected = Arel::Nodes::Equality.new(Post.arel_table[:title], 'world') + assert_equal(expected, relation.where_values.last) + end + + def test_not_eq_with_string_parameter + expected = Arel::Nodes::Not.new("title = 'hello'") + relation = Post.where.not("title = 'hello'") + assert_equal([expected], relation.where_values) + end + + def test_not_eq_with_array_parameter + expected = Arel::Nodes::Not.new("title = 'hello'") + relation = Post.where.not(['title = ?', 'hello']) + assert_equal([expected], relation.where_values) + end + + def test_chaining_multiple + relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails') + + expected = Arel::Nodes::NotIn.new(Post.arel_table[:author_id], [1, 2]) + assert_equal(expected, relation.where_values[0]) + + expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'ruby on rails') + assert_equal(expected, relation.where_values[1]) + end + end +end diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index 9c0b139dbf..297e865308 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -85,5 +85,11 @@ module ActiveRecord def test_where_with_empty_hash_and_no_foreign_key assert_equal 0, Edge.where(:sink => {}).count end + + def test_where_with_blank_conditions + [[], {}, nil, ""].each do |blank| + assert_equal 4, Edge.where(blank).order("sink_id").to_a.size + end + end end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 9056b92d65..7ff0044bd4 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -1,5 +1,6 @@ require "cases/helper" + class SchemaDumperTest < ActiveRecord::TestCase def setup super @@ -17,15 +18,11 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_dump_schema_information_outputs_lexically_ordered_versions versions = %w{ 20100101010101 20100201010101 20100301010101 } versions.reverse.each do |v| - ActiveRecord::SchemaMigration.create!( - :version => v, :migrated_at => Time.now, - :fingerprint => "123456789012345678901234567890ab", :name => "anon") + ActiveRecord::SchemaMigration.create!(:version => v) end schema_info = ActiveRecord::Base.connection.dump_schema_information assert_match(/20100201010101.*20100301010101/m, schema_info) - target_line = %q{INSERT INTO schema_migrations (version, migrated_at, fingerprint, name) VALUES ('20100101010101',LOCALTIMESTAMP,'123456789012345678901234567890ab','anon');} - assert_match target_line, schema_info end def test_magic_comment @@ -39,16 +36,6 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "schema_migrations"}, output end - def test_schema_dump_includes_migrations - ActiveRecord::SchemaMigration.delete_all - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/always_safe") - - output = standard_dump - assert_match %r{migrations do}, output, "Missing migrations block" - assert_match %r{migration 1001, "[0-9a-f]{32}", "always_safe"}, output, "Missing migration line" - assert_match %r{migration 1002, "[0-9a-f]{32}", "still_safe"}, output, "Missing migration line" - end - def test_schema_dump_excludes_sqlite_sequence output = standard_dump assert_no_match %r{create_table "sqlite_sequence"}, output diff --git a/activerecord/test/cases/schema_migration_test.rb b/activerecord/test/cases/schema_migration_test.rb deleted file mode 100644 index 34b9fa520a..0000000000 --- a/activerecord/test/cases/schema_migration_test.rb +++ /dev/null @@ -1,54 +0,0 @@ -require "cases/helper" - -class SchemaMigrationTest < ActiveRecord::TestCase - def sm_table_name - ActiveRecord::SchemaMigration.table_name - end - - def connection - ActiveRecord::Base.connection - end - - def test_add_schema_info_respects_prefix_and_suffix - connection.drop_table(sm_table_name) if connection.table_exists?(sm_table_name) - # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters - ActiveRecord::Base.table_name_prefix = 'p_' - ActiveRecord::Base.table_name_suffix = '_s' - connection.drop_table(sm_table_name) if connection.table_exists?(sm_table_name) - - ActiveRecord::SchemaMigration.create_table - - assert_equal "p_unique_schema_migrations_s", connection.indexes(sm_table_name)[0][:name] - ensure - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" - end - - def test_add_metadata_columns_to_exisiting_schema_migrations - # creates the old table schema from pre-Rails4.0, so we can test adding to it below - if connection.table_exists?(sm_table_name) - connection.drop_table(sm_table_name) - end - connection.create_table(sm_table_name, :id => false) do |schema_migrations_table| - schema_migrations_table.column("version", :string, :null => false) - end - - connection.insert "INSERT INTO #{connection.quote_table_name(sm_table_name)} (version) VALUES (100)" - connection.insert "INSERT INTO #{connection.quote_table_name(sm_table_name)} (version) VALUES (200)" - - ActiveRecord::SchemaMigration.create_table - - rows = connection.select_all("SELECT * FROM #{connection.quote_table_name(sm_table_name)}") - assert rows[0].has_key?("migrated_at"), "missing column `migrated_at`" - assert_match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/, rows[0]["migrated_at"]) - assert rows[0].has_key?("fingerprint"), "missing column `fingerprint`" - assert rows[0].has_key?("name"), "missing column `name`" - end - - def test_schema_migrations_columns - ActiveRecord::SchemaMigration.create_table - - columns = connection.columns(sm_table_name).collect(&:name) - %w[version migrated_at fingerprint name].each { |col| assert columns.include?(col), "missing column `#{col}`" } - end -end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 068f3cf3cd..6962da298e 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -212,4 +212,17 @@ class SerializedAttributeTest < ActiveRecord::TestCase assert_kind_of BCrypt::Password, topic.content assert_equal(true, topic.content == password, 'password should equal') end + + def test_serialize_attribute_via_select_method_when_time_zone_available + ActiveRecord::Base.time_zone_aware_attributes = true + Topic.serialize(:content, MyObject) + + myobj = MyObject.new('value1', 'value2') + topic = Topic.create(content: myobj) + + assert_equal(myobj, Topic.select(:content).find(topic.id).content) + assert_raise(ActiveModel::MissingAttributeError) { Topic.select(:id).find(topic.id).content } + ensure + ActiveRecord::Base.time_zone_aware_attributes = false + end end diff --git a/activerecord/test/migrations/10_urban/9_add_expressions.rb b/activerecord/test/migrations/10_urban/9_add_expressions.rb new file mode 100644 index 0000000000..79a342e574 --- /dev/null +++ b/activerecord/test/migrations/10_urban/9_add_expressions.rb @@ -0,0 +1,11 @@ +class AddExpressions < ActiveRecord::Migration + def self.up + create_table("expressions") do |t| + t.column :expression, :string + end + end + + def self.down + drop_table "expressions" + end +end diff --git a/activerecord/test/migrations/always_safe/1001_always_safe.rb b/activerecord/test/migrations/always_safe/1001_always_safe.rb deleted file mode 100644 index 454b972507..0000000000 --- a/activerecord/test/migrations/always_safe/1001_always_safe.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AlwaysSafe < ActiveRecord::Migration - def change - # do nothing to avoid side-effect conflicts from running multiple times - end -end diff --git a/activerecord/test/migrations/always_safe/1002_still_safe.rb b/activerecord/test/migrations/always_safe/1002_still_safe.rb deleted file mode 100644 index 7398ae27a2..0000000000 --- a/activerecord/test/migrations/always_safe/1002_still_safe.rb +++ /dev/null @@ -1,5 +0,0 @@ -class StillSafe < ActiveRecord::Migration - def change - # do nothing to avoid side-effect conflicts from running multiple times - end -end diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index e4c0278c0d..0109ef4f83 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -1,6 +1,6 @@ class Bulb < ActiveRecord::Base default_scope { where(:name => 'defaulty') } - belongs_to :car + belongs_to :car, :touch => true attr_reader :scope_after_initialize, :attributes_after_initialize diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 1c048bb6b4..683cb54a10 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -234,3 +234,8 @@ class ThreadsafeDeveloper < ActiveRecord::Base limit(1) end end + +class CachedDeveloper < ActiveRecord::Base + self.table_name = "developers" + self.cache_timestamp_format = :number +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 35778d008a..af14bc7bd5 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -115,6 +115,7 @@ ActiveRecord::Schema.define do t.integer :engines_count t.integer :wheels_count t.column :lock_version, :integer, :null => false, :default => 0 + t.timestamps end create_table :categories, :force => true do |t| diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 450669968b..5e7121181d 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,4 +1,27 @@ ## Rails 4.0.0 (unreleased) ## +* 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 relay on Marshal(MemCacheStore and FileStore). [fixes #8167] @@ -63,7 +86,7 @@ * Hash#extract! returns only those keys that present in the receiver. - {:a => 1, :b => 2}.extract!(:a, :x) # => {:a => 1} + {a: 1, b: 2}.extract!(:a, :x) # => {:a => 1} *Mikhail Dieterle* @@ -141,7 +164,7 @@ You can choose which instance of the deprecator will be used. - deprecate :method_name, :deprecator => deprecator_instance + deprecate :method_name, deprecator: deprecator_instance You can use ActiveSupport::Deprecation in your gem. @@ -159,7 +182,7 @@ def new_method end - deprecate :old_method => :new_method, :deprecator => deprecator + deprecate old_method: :new_method, deprecator: deprecator end MyGem.new.old_method diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index a4216d2cb4..4c9e59dbd2 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -24,4 +24,5 @@ Gem::Specification.new do |s| s.add_dependency 'multi_json', '~> 1.3' s.add_dependency 'tzinfo', '~> 0.3.33' s.add_dependency 'minitest', '~> 4.1' + s.add_dependency 'thread_safe','~> 0.1' end diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 4e397ea110..b602686114 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -40,6 +40,7 @@ module ActiveSupport eager_autoload do autoload :BacktraceCleaner autoload :BasicObject + autoload :ProxyObject autoload :Benchmarkable autoload :Cache autoload :Callbacks diff --git a/activesupport/lib/active_support/basic_object.rb b/activesupport/lib/active_support/basic_object.rb index 6ccb0cd525..d4d06b2aa4 100644 --- a/activesupport/lib/active_support/basic_object.rb +++ b/activesupport/lib/active_support/basic_object.rb @@ -1,13 +1,12 @@ -module ActiveSupport - # A class with no predefined methods that behaves similarly to Builder's - # BlankSlate. Used for proxy classes. - class BasicObject < ::BasicObject - undef_method :== - undef_method :equal? +require 'active_support/deprecation' +require 'active_support/proxy_object' - # Let ActiveSupport::BasicObject at least raise exceptions. - def raise(*args) - ::Object.send(:raise, *args) +module ActiveSupport + # :nodoc: + class BasicObject < ProxyObject + def self.inherited(*) + ::ActiveSupport::Deprecation.warn 'ActiveSupport::BasicObject is deprecated! Use ActiveSupport::ProxyObject instead.' + super end end end diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 1c3d26ead4..1504e18839 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -72,6 +72,9 @@ class Class instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true) + # We use class_eval here rather than define_method because class_attribute + # may be used in a performance sensitive context therefore the overhead that + # define_method introduces may become significant. attrs.each do |name| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def self.#{name}() nil end diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 439d380af7..421aa12100 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -53,19 +53,19 @@ class Date # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) # and then subtracts the specified number of seconds. def ago(seconds) - to_time_in_current_zone.since(-seconds) + in_time_zone.since(-seconds) end # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) # and then adds the specified number of seconds def since(seconds) - to_time_in_current_zone.since(seconds) + in_time_zone.since(seconds) end alias :in :since # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) def beginning_of_day - to_time_in_current_zone + in_time_zone end alias :midnight :beginning_of_day alias :at_midnight :beginning_of_day @@ -73,8 +73,9 @@ class Date # Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59) def end_of_day - to_time_in_current_zone.end_of_day + in_time_zone.end_of_day end + alias :at_end_of_day :end_of_day def plus_with_duration(other) #:nodoc: if ActiveSupport::Duration === other diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 9120b0ba49..fe08ade7e0 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -75,10 +75,10 @@ class Date # # date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007 def to_time(form = :local) - ::Time.send("#{form}_time", year, month, day) + ::Time.send(form, year, month, day) end def xmlschema - to_time_in_current_zone.xmlschema + in_time_zone.xmlschema end end diff --git a/activesupport/lib/active_support/core_ext/date/zones.rb b/activesupport/lib/active_support/core_ext/date/zones.rb index c1b3934722..b4548671bf 100644 --- a/activesupport/lib/active_support/core_ext/date/zones.rb +++ b/activesupport/lib/active_support/core_ext/date/zones.rb @@ -2,14 +2,36 @@ require 'date' require 'active_support/core_ext/time/zones' class Date + # *DEPRECATED*: Use +Date#in_time_zone+ instead. + # # Converts Date to a TimeWithZone in the current zone if <tt>Time.zone</tt> or # <tt>Time.zone_default</tt> is set, otherwise converts Date to a Time via # Date#to_time. def to_time_in_current_zone + ActiveSupport::Deprecation.warn 'Date#to_time_in_current_zone is deprecated. Use Date#in_time_zone instead', caller + if ::Time.zone ::Time.zone.local(year, month, day) else to_time end end + + # Converts Date to a TimeWithZone in the current zone if Time.zone or Time.zone_default + # is set, otherwise converts Date to a Time via Date#to_time + # + # Time.zone = 'Hawaii' # => 'Hawaii' + # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00 + # + # You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument, + # and the conversion will be based on that zone instead of <tt>Time.zone</tt>. + # + # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00 + def in_time_zone(zone = ::Time.zone) + if zone + ::Time.find_zone!(zone).local(year, month, day) + else + to_time + end + end end diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index f77d444479..fca5d4d679 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -110,6 +110,7 @@ class DateTime def end_of_day change(:hour => 23, :min => 59, :sec => 59) end + alias :at_end_of_day :end_of_day # Returns a new DateTime representing the start of the hour (hh:00:00). def beginning_of_hour @@ -121,6 +122,7 @@ class DateTime def end_of_hour change(:min => 59, :sec => 59) end + alias :at_end_of_hour :end_of_hour # Adjusts DateTime to UTC by adding its offset value; offset is set to 0. # diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb index ad864765a3..5d7cb81e38 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -11,3 +11,4 @@ require 'active_support/core_ext/string/exclude' require 'active_support/core_ext/string/strip' require 'active_support/core_ext/string/inquiry' require 'active_support/core_ext/string/indent' +require 'active_support/core_ext/string/zones' diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index 9b9d83932e..9d3b81cf38 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -21,7 +21,7 @@ class String date_values[6] *= 1000000 offset = date_values.pop - ::Time.send("#{form}_time", *date_values) - offset + ::Time.send(form, *date_values) - offset end end diff --git a/activesupport/lib/active_support/core_ext/string/zones.rb b/activesupport/lib/active_support/core_ext/string/zones.rb new file mode 100644 index 0000000000..e3f20eee29 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/string/zones.rb @@ -0,0 +1,13 @@ +require 'active_support/core_ext/time/zones' + +class String + # Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default + # is set, otherwise converts String to a Time via String#to_time + def in_time_zone(zone = ::Time.zone) + if zone + ::Time.find_zone!(zone).parse(self) + else + to_time + end + end +end diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb new file mode 100644 index 0000000000..6ad0b2d69c --- /dev/null +++ b/activesupport/lib/active_support/core_ext/thread.rb @@ -0,0 +1,70 @@ +class Thread + LOCK = Mutex.new # :nodoc: + + # Returns the value of a thread local variable that has been set. Note that + # these are different than fiber local values. + # + # Thread local values are carried along with threads, and do not respect + # fibers. For example: + # + # Thread.new { + # Thread.current.thread_variable_set("foo", "bar") # set a thread local + # Thread.current["foo"] = "bar" # set a fiber local + # + # Fiber.new { + # Fiber.yield [ + # Thread.current.thread_variable_get("foo"), # get the thread local + # Thread.current["foo"], # get the fiber local + # ] + # }.resume + # }.join.value # => ['bar', nil] + # + # The value <tt>"bar"</tt> is returned for the thread local, where +nil+ is returned + # for the fiber local. The fiber is executed in the same thread, so the + # thread local values are available. + def thread_variable_get(key) + locals[key.to_sym] + end + + # Sets a thread local with +key+ to +value+. Note that these are local to + # threads, and not to fibers. Please see Thread#thread_variable_get for + # more information. + def thread_variable_set(key, value) + locals[key.to_sym] = value + end + + # Returns an an array of the names of the thread-local variables (as Symbols). + # + # thr = Thread.new do + # Thread.current.thread_variable_set(:cat, 'meow') + # Thread.current.thread_variable_set("dog", 'woof') + # end + # thr.join #=> #<Thread:0x401b3f10 dead> + # thr.thread_variables #=> [:dog, :cat] + # + # Note that these are not fiber local variables. Please see Thread#thread_variable_get + # for more details. + def thread_variables + locals.keys + end + + # Returns <tt>true</tt> if the given string (or symbol) exists as a + # thread-local variable. + # + # me = Thread.current + # me.thread_variable_set(:oliver, "a") + # me.thread_variable?(:oliver) #=> true + # me.thread_variable?(:stanley) #=> false + # + # Note that these are not fiber local variables. Please see Thread#thread_variable_get + # for more details. + def thread_variable?(key) + locals.has_key?(key.to_sym) + end + + private + + def locals + @locals || LOCK.synchronize { @locals ||= {} } + end +end unless Thread.instance_methods.include?(:thread_variable_set) diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 46c9f05c15..1f95f62229 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/time/conversions' require 'active_support/time_with_zone' require 'active_support/core_ext/time/zones' require 'active_support/core_ext/date_and_time/calculations' +require 'active_support/deprecation' class Time include DateAndTime::Calculations @@ -25,10 +26,13 @@ class Time end end + # *DEPRECATED*: Use +Time#utc+ or +Time#local+ instead. + # # Returns a new Time if requested year can be accommodated by Ruby's Time class # (i.e., if year is within either 1970..2038 or 1902..2038, depending on system architecture); # otherwise returns a DateTime. def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0) + ActiveSupport::Deprecation.warn 'time_with_datetime_fallback is deprecated. Use Time#utc or Time#local instead', caller time = ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec) # This check is needed because Time.utc(y) returns a time object in the 2000s for 0 <= y <= 138. @@ -41,13 +45,19 @@ class Time ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec) end + # *DEPRECATED*: Use +Time#utc+ instead. + # # Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:utc</tt>. def utc_time(*args) + ActiveSupport::Deprecation.warn 'utc_time is deprecated. Use Time#utc instead', caller time_with_datetime_fallback(:utc, *args) end + # *DEPRECATED*: Use +Time#local+ instead. + # # Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:local</tt>. def local_time(*args) + ActiveSupport::Deprecation.warn 'local_time is deprecated. Use Time#local instead', caller time_with_datetime_fallback(:local, *args) end @@ -160,6 +170,7 @@ class Time :usec => Rational(999999999, 1000) ) end + alias :at_end_of_day :end_of_day # Returns a new Time representing the start of the hour (x:00) def beginning_of_hour @@ -175,6 +186,7 @@ class Time :usec => Rational(999999999, 1000) ) end + alias :at_end_of_hour :end_of_hour # Returns a Range representing the whole day of the current time. def all_day diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index efd351d741..fff4c776a9 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -1,5 +1,6 @@ require 'set' require 'thread' +require 'thread_safe' require 'pathname' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/attribute_accessors' @@ -517,7 +518,7 @@ module ActiveSupport #:nodoc: class ClassCache def initialize - @store = Hash.new + @store = ThreadSafe::Cache.new end def empty? diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 17e69c34a5..485dc91063 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -30,7 +30,7 @@ module ActiveSupport # @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) # - # When someone execute any method expect +inspect+ on proxy object this will + # When someone executes any method except +inspect+ on proxy object this will # trigger +warn+ method on +deprecator_instance+. # # Default deprecator is <tt>ActiveSupport::Deprecation</tt> diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 7e99646117..2cb1f408b6 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -1,4 +1,4 @@ -require 'active_support/basic_object' +require 'active_support/proxy_object' require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/object/acts_like' @@ -7,7 +7,7 @@ module ActiveSupport # Time#advance, respectively. It mainly supports the methods on Numeric. # # 1.month.ago # equivalent to Time.now.advance(months: -1) - class Duration < BasicObject + class Duration < ProxyObject attr_accessor :value, :parts def initialize(value, parts) #:nodoc: diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 6f259a093b..9cf4b2b2ba 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -1,3 +1,4 @@ +require 'thread_safe' require 'active_support/core_ext/array/prepend_and_append' require 'active_support/i18n' @@ -24,9 +25,10 @@ module ActiveSupport # singularization rules that is runs. This guarantees that your rules run # before any of the rules that may already have been loaded. class Inflections + @__instance__ = ThreadSafe::Cache.new + def self.instance(locale = :en) - @__instance__ ||= Hash.new { |h, k| h[k] = new } - @__instance__[locale] + @__instance__[locale] ||= new end attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 7a5c351ca8..832d1ce6d5 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -129,13 +129,7 @@ module ActiveSupport def escape(string) string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY) - json = string. - gsub(escape_regex) { |s| ESCAPED_CHARS[s] }. - gsub(/([\xC0-\xDF][\x80-\xBF]| - [\xE0-\xEF][\x80-\xBF]{2}| - [\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s| - s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&') - } + json = string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] } json = %("#{json}") json.force_encoding(::Encoding::UTF_8) json diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb index 6beb2b6afa..71654dbb87 100644 --- a/activesupport/lib/active_support/key_generator.rb +++ b/activesupport/lib/active_support/key_generator.rb @@ -1,4 +1,4 @@ -require 'mutex_m' +require 'thread_safe' require 'openssl' module ActiveSupport @@ -28,16 +28,14 @@ module ActiveSupport class CachingKeyGenerator def initialize(key_generator) @key_generator = key_generator - @cache_keys = {}.extend(Mutex_m) + @cache_keys = ThreadSafe::Cache.new end # Returns a derived key suitable for use. The default key_size is chosen # to be compatible with the default settings of ActiveSupport::MessageVerifier. # i.e. OpenSSL::Digest::SHA1#block_length def generate_key(salt, key_size=64) - @cache_keys.synchronize do - @cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size) - end + @cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size) end end diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 2e5bcf4639..7588fdb67c 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -1,4 +1,5 @@ require 'mutex_m' +require 'thread_safe' module ActiveSupport module Notifications @@ -11,7 +12,7 @@ module ActiveSupport def initialize @subscribers = [] - @listeners_for = {} + @listeners_for = ThreadSafe::Cache.new super end @@ -44,7 +45,9 @@ module ActiveSupport end def listeners_for(name) - synchronize do + # this is correctly done double-checked locking (ThreadSafe::Cache's lookups have volatile semantics) + @listeners_for[name] || synchronize do + # use synchronisation when accessing @subscribers @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) } end end diff --git a/activesupport/lib/active_support/proxy_object.rb b/activesupport/lib/active_support/proxy_object.rb new file mode 100644 index 0000000000..a2bdf1d790 --- /dev/null +++ b/activesupport/lib/active_support/proxy_object.rb @@ -0,0 +1,13 @@ +module ActiveSupport + # A class with no predefined methods that behaves similarly to Builder's + # BlankSlate. Used for proxy classes. + class ProxyObject < ::BasicObject + undef_method :== + undef_method :equal? + + # Let ActiveSupport::BasicObject at least raise exceptions. + def raise(*args) + ::Object.send(:raise, *args) + end + end +end diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb index bcd5d78b54..92a593965e 100644 --- a/activesupport/lib/active_support/time.rb +++ b/activesupport/lib/active_support/time.rb @@ -9,21 +9,12 @@ end require 'date' require 'time' -require 'active_support/core_ext/time/marshal' -require 'active_support/core_ext/time/acts_like' -require 'active_support/core_ext/time/calculations' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/time/zones' - -require 'active_support/core_ext/date/acts_like' -require 'active_support/core_ext/date/calculations' -require 'active_support/core_ext/date/conversions' -require 'active_support/core_ext/date/zones' - -require 'active_support/core_ext/date_time/acts_like' -require 'active_support/core_ext/date_time/calculations' -require 'active_support/core_ext/date_time/conversions' -require 'active_support/core_ext/date_time/zones' +require 'active_support/core_ext/time' +require 'active_support/core_ext/date' +require 'active_support/core_ext/date_time' require 'active_support/core_ext/integer/time' require 'active_support/core_ext/numeric/time' + +require 'active_support/core_ext/string/conversions' +require 'active_support/core_ext/string/zones' diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index b931de3fac..0dbc198ea2 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -344,7 +344,7 @@ module ActiveSupport end def transfer_time_values_to_utc_constructor(time) - ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:nsec) ? Rational(time.nsec, 1000) : 0) + ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec, Rational(time.nsec, 1000)) end def duration_of_variable_length?(obj) diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index d087955587..c5fbddcb5f 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -252,7 +252,7 @@ module ActiveSupport # Time.zone = 'Hawaii' # => "Hawaii" # Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00 def local(*args) - time = Time.utc_time(*args) + time = Time.utc(*args) ActiveSupport::TimeWithZone.new(nil, self, time) end diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 7ae1f67785..5e89a6e00b 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -34,7 +34,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase def test_to_time assert_equal Time.local(2005, 2, 21), Date.new(2005, 2, 21).to_time - assert_equal Time.local_time(2039, 2, 21), Date.new(2039, 2, 21).to_time + assert_equal Time.local(2039, 2, 21), Date.new(2039, 2, 21).to_time silence_warnings do 0.upto(138) do |year| [:utc, :local].each do |format| @@ -354,3 +354,11 @@ class DateExtBehaviorTest < ActiveSupport::TestCase end end end + +class DateExtConversionsTest < ActiveSupport::TestCase + def test_to_time_in_current_zone_is_deprecated + assert_deprecated(/to_time_in_current_zone/) do + Date.new(2012,6,7).to_time_in_current_zone + end + end +end diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 3353465c1c..dfad3a90f8 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -42,7 +42,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase def test_to_time assert_equal Time.utc(2005, 2, 21, 10, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time - assert_equal Time.utc_time(2039, 2, 21, 10, 11, 12), DateTime.new(2039, 2, 21, 10, 11, 12, 0).to_time + assert_equal Time.utc(2039, 2, 21, 10, 11, 12), DateTime.new(2039, 2, 21, 10, 11, 12, 0).to_time # DateTimes with offsets other than 0 are returned unaltered assert_equal DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)), DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).to_time # Fractional seconds are preserved diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index c8312aa653..2826f51f2d 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -21,7 +21,7 @@ class DurationTest < ActiveSupport::TestCase assert ActiveSupport::Duration === 1.day assert !(ActiveSupport::Duration === 1.day.to_i) assert !(ActiveSupport::Duration === 'foo') - assert !(ActiveSupport::Duration === ActiveSupport::BasicObject.new) + assert !(ActiveSupport::Duration === ActiveSupport::ProxyObject.new) end def test_equals @@ -131,7 +131,7 @@ class DurationTest < ActiveSupport::TestCase assert_equal Time.local(2009,3,29,0,0,0) + 1.day, Time.local(2009,3,30,0,0,0) end end - + def test_delegation_with_block_works counter = 0 assert_nothing_raised do diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 6720ed42f0..fa8839bcb3 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -161,30 +161,6 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal 97, 'abc'.ord end - def test_string_to_time - assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time - assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:local) - assert_equal Time.utc(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time - assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:local) - assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time - assert_equal Time.local_time(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:local) - assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time - assert_nil "".to_time - 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 - assert_equal ::Date::ITALY, "2039-02-27 23:50".to_datetime.start # use Ruby's default start value - assert_equal DateTime.civil(2039, 2, 27, 23, 50, 19 + Rational(275038, 1000000), "-04:00"), "2039-02-27T23:50:19.275038-04:00".to_datetime - assert_nil "".to_datetime - end - - def test_string_to_date - assert_equal Date.new(2005, 2, 27), "2005-02-27".to_date - assert_nil "".to_date - end - def test_access s = "hello" assert_equal "h", s.at(0) @@ -308,6 +284,32 @@ class StringInflectionsTest < ActiveSupport::TestCase end end +class StringConversionsTest < ActiveSupport::TestCase + def test_string_to_time + assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time + assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:local) + assert_equal Time.utc(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time + assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:local) + assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time + assert_equal Time.local(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:local) + assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time + assert_nil "".to_time + 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 + assert_equal ::Date::ITALY, "2039-02-27 23:50".to_datetime.start # use Ruby's default start value + assert_equal DateTime.civil(2039, 2, 27, 23, 50, 19 + Rational(275038, 1000000), "-04:00"), "2039-02-27T23:50:19.275038-04:00".to_datetime + assert_nil "".to_datetime + end + + def test_string_to_date + assert_equal Date.new(2005, 2, 27), "2005-02-27".to_date + assert_nil "".to_date + end +end + class StringBehaviourTest < ActiveSupport::TestCase def test_acts_like_string assert 'Bambi'.acts_like_string? diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb new file mode 100644 index 0000000000..b58f59a8d4 --- /dev/null +++ b/activesupport/test/core_ext/thread_test.rb @@ -0,0 +1,77 @@ +require 'abstract_unit' +require 'active_support/core_ext/thread' + +class ThreadExt < ActiveSupport::TestCase + def test_main_thread_variable_in_enumerator + assert_equal Thread.main, Thread.current + + Thread.current.thread_variable_set :foo, "bar" + + thread, value = Fiber.new { + Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] + }.resume + + assert_equal Thread.current, thread + assert_equal Thread.current.thread_variable_get(:foo), value + end + + def test_thread_variable_in_enumerator + Thread.new { + Thread.current.thread_variable_set :foo, "bar" + + thread, value = Fiber.new { + Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] + }.resume + + assert_equal Thread.current, thread + assert_equal Thread.current.thread_variable_get(:foo), value + }.join + end + + def test_thread_variables + assert_equal [], Thread.new { Thread.current.thread_variables }.join.value + + t = Thread.new { + Thread.current.thread_variable_set(:foo, "bar") + Thread.current.thread_variables + } + assert_equal [:foo], t.join.value + end + + def test_thread_variable? + refute Thread.new { Thread.current.thread_variable?("foo") }.join.value + t = Thread.new { + Thread.current.thread_variable_set("foo", "bar") + }.join + + assert t.thread_variable?("foo") + assert t.thread_variable?(:foo) + refute t.thread_variable?(:bar) + end + + def test_thread_variable_strings_and_symbols_are_the_same_key + t = Thread.new {}.join + t.thread_variable_set("foo", "bar") + assert_equal "bar", t.thread_variable_get(:foo) + end + + def test_thread_variable_frozen + t = Thread.new { }.join + t.freeze + assert_raises(RuntimeError) do + t.thread_variable_set(:foo, "bar") + end + end + + def test_thread_variable_security + t = Thread.new { sleep } + + assert_raises(SecurityError) do + Thread.new { $SAFE = 4; t.thread_variable_get(:foo) }.join + end + + assert_raises(SecurityError) do + Thread.new { $SAFE = 4; t.thread_variable_set(:foo, :baz) }.join + end + end +end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 0e75104fc6..70e07cefd1 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -567,45 +567,57 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_time_with_datetime_fallback - assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30) - assert_equal Time.time_with_datetime_fallback(:local, 2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30) - assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0) - assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30) - assert_equal Time.time_with_datetime_fallback(:utc, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, 0) - assert_equal Time.time_with_datetime_fallback(:utc, 2005), Time.utc(2005) - assert_equal Time.time_with_datetime_fallback(:utc, 2039), DateTime.civil(2039, 1, 1, 0, 0, 0, 0) - assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30, 1), Time.utc(2005, 2, 21, 17, 44, 30, 1) #with usec - # This won't overflow on 64bit linux - unless time_is_64bits? - assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1900, 2, 21, 17, 44, 30) - assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1), - DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0) - assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value - end - silence_warnings do - 0.upto(138) do |year| - [:utc, :local].each do |format| - assert_equal year, Time.time_with_datetime_fallback(format, year).year + ActiveSupport::Deprecation.silence do + assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30) + assert_equal Time.time_with_datetime_fallback(:local, 2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30) + assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0) + assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30) + assert_equal Time.time_with_datetime_fallback(:utc, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, 0) + assert_equal Time.time_with_datetime_fallback(:utc, 2005), Time.utc(2005) + assert_equal Time.time_with_datetime_fallback(:utc, 2039), DateTime.civil(2039, 1, 1, 0, 0, 0, 0) + assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30, 1), Time.utc(2005, 2, 21, 17, 44, 30, 1) #with usec + # This won't overflow on 64bit linux + unless time_is_64bits? + assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1900, 2, 21, 17, 44, 30) + assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1), + DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0) + assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value + end + silence_warnings do + 0.upto(138) do |year| + [:utc, :local].each do |format| + assert_equal year, Time.time_with_datetime_fallback(format, year).year + end end end end end def test_utc_time - assert_equal Time.utc_time(2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30) - assert_equal Time.utc_time(2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0) - assert_equal Time.utc_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, 0) + ActiveSupport::Deprecation.silence do + assert_equal Time.utc_time(2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30) + assert_equal Time.utc_time(2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0) + assert_equal Time.utc_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, 0) + end end def test_local_time - assert_equal Time.local_time(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30) - assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30) + ActiveSupport::Deprecation.silence do + assert_equal Time.local_time(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30) + assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30) - unless time_is_64bits? - assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1901, 2, 21, 17, 44, 30) + unless time_is_64bits? + assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1901, 2, 21, 17, 44, 30) + end end end + def test_time_with_datetime_fallback_deprecations + assert_deprecated(/time_with_datetime_fallback/) { Time.time_with_datetime_fallback(:utc, 2012, 6, 7) } + assert_deprecated(/utc_time/) { Time.utc_time(2012, 6, 7) } + assert_deprecated(/local_time/) { Time.local_time(2012, 6, 7) } + end + def test_last_month_on_31st assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 1293f104e5..56020da035 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -953,3 +953,131 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end end + +class TimeWithZoneMethodsForDate < ActiveSupport::TestCase + def setup + @d = Date.civil(2000) + end + + def teardown + Time.zone = nil + end + + def test_in_time_zone + with_tz_default 'Alaska' do + assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone.inspect + end + with_tz_default 'Hawaii' do + assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @d.in_time_zone.inspect + end + with_tz_default nil do + assert_equal @d.to_time, @d.in_time_zone + end + end + + def test_nil_time_zone + with_tz_default nil do + assert !@d.in_time_zone.respond_to?(:period), 'no period method' + end + end + + def test_in_time_zone_with_argument + with_tz_default 'Eastern Time (US & Canada)' do # Time.zone will not affect #in_time_zone(zone) + assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone('Alaska').inspect + assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @d.in_time_zone('Hawaii').inspect + assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @d.in_time_zone('UTC').inspect + assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone(-9.hours).inspect + end + end + + def test_in_time_zone_with_invalid_argument + assert_raise(ArgumentError) { @d.in_time_zone("No such timezone exists") } + assert_raise(ArgumentError) { @d.in_time_zone(-15.hours) } + assert_raise(ArgumentError) { @d.in_time_zone(Object.new) } + end + + protected + def with_tz_default(tz = nil) + old_tz = Time.zone + Time.zone = tz + yield + ensure + Time.zone = old_tz + end +end + +class TimeWithZoneMethodsForString < ActiveSupport::TestCase + def setup + @s = "Sat, 01 Jan 2000 00:00:00" + @u = "Sat, 01 Jan 2000 00:00:00 UTC +00:00" + @z = "Fri, 31 Dec 1999 19:00:00 EST -05:00" + end + + def teardown + Time.zone = nil + end + + def test_in_time_zone + with_tz_default 'Alaska' do + assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone.inspect + assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone.inspect + assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone.inspect + end + with_tz_default 'Hawaii' do + assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @s.in_time_zone.inspect + assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @u.in_time_zone.inspect + assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @z.in_time_zone.inspect + end + with_tz_default nil do + assert_equal @s.to_time, @s.in_time_zone + assert_equal @u.to_time, @u.in_time_zone + assert_equal @z.to_time, @z.in_time_zone + end + end + + def test_nil_time_zone + with_tz_default nil do + assert !@s.in_time_zone.respond_to?(:period), 'no period method' + assert !@u.in_time_zone.respond_to?(:period), 'no period method' + assert !@z.in_time_zone.respond_to?(:period), 'no period method' + end + end + + def test_in_time_zone_with_argument + with_tz_default 'Eastern Time (US & Canada)' do # Time.zone will not affect #in_time_zone(zone) + assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone('Alaska').inspect + assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone('Alaska').inspect + assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone('Alaska').inspect + assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @s.in_time_zone('Hawaii').inspect + assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @u.in_time_zone('Hawaii').inspect + assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @z.in_time_zone('Hawaii').inspect + assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @s.in_time_zone('UTC').inspect + assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @u.in_time_zone('UTC').inspect + assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @z.in_time_zone('UTC').inspect + assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone(-9.hours).inspect + assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone(-9.hours).inspect + assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone(-9.hours).inspect + end + end + + def test_in_time_zone_with_invalid_argument + assert_raise(ArgumentError) { @s.in_time_zone("No such timezone exists") } + assert_raise(ArgumentError) { @u.in_time_zone("No such timezone exists") } + assert_raise(ArgumentError) { @z.in_time_zone("No such timezone exists") } + assert_raise(ArgumentError) { @s.in_time_zone(-15.hours) } + assert_raise(ArgumentError) { @u.in_time_zone(-15.hours) } + assert_raise(ArgumentError) { @z.in_time_zone(-15.hours) } + assert_raise(ArgumentError) { @s.in_time_zone(Object.new) } + assert_raise(ArgumentError) { @u.in_time_zone(Object.new) } + assert_raise(ArgumentError) { @z.in_time_zone(Object.new) } + end + + protected + def with_tz_default(tz = nil) + old_tz = Time.zone + Time.zone = tz + yield + ensure + Time.zone = old_tz + end +end diff --git a/activesupport/test/deprecation/basic_object_test.rb b/activesupport/test/deprecation/basic_object_test.rb new file mode 100644 index 0000000000..4b5bed9eb1 --- /dev/null +++ b/activesupport/test/deprecation/basic_object_test.rb @@ -0,0 +1,12 @@ +require 'abstract_unit' +require 'active_support/deprecation' +require 'active_support/basic_object' + + +class BasicObjectTest < ActiveSupport::TestCase + test 'BasicObject warns about deprecation when inherited from' do + warn = 'ActiveSupport::BasicObject is deprecated! Use ActiveSupport::ProxyObject instead.' + ActiveSupport::Deprecation.expects(:warn).with(warn).once + Class.new(ActiveSupport::BasicObject) + end +end
\ No newline at end of file diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 5bb2a45c87..b6e2cd4529 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -112,21 +112,34 @@ class TestJSONEncoding < ActiveSupport::TestCase def test_utf8_string_encoded_properly result = ActiveSupport::JSON.encode('€2.99') - assert_equal '"\\u20ac2.99"', result + assert_equal '"€2.99"', result assert_equal(Encoding::UTF_8, result.encoding) result = ActiveSupport::JSON.encode('✎☺') - assert_equal '"\\u270e\\u263a"', result + assert_equal '"✎☺"', result assert_equal(Encoding::UTF_8, result.encoding) end def test_non_utf8_string_transcodes s = '二'.encode('Shift_JIS') result = ActiveSupport::JSON.encode(s) - assert_equal '"\\u4e8c"', result + assert_equal '"二"', result assert_equal Encoding::UTF_8, result.encoding end + def test_wide_utf8_chars + w = '𠜎' + result = ActiveSupport::JSON.encode(w) + assert_equal '"𠜎"', result + end + + def test_wide_utf8_roundtrip + hash = { string: "𐒑" } + json = ActiveSupport::JSON.encode(hash) + decoded_hash = ActiveSupport::JSON.decode(json) + assert_equal "𐒑", decoded_hash['string'] + end + def test_exception_raised_when_encoding_circular_reference_in_array a = [1] a << a diff --git a/guides/Rakefile b/guides/Rakefile index 7881a3d9b3..d6dd950d01 100644 --- a/guides/Rakefile +++ b/guides/Rakefile @@ -13,6 +13,12 @@ namespace :guides do desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/kindlepublishing" task :kindle do + unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/ + abort "Please `gem install kindlerb`" + end + unless `convert` =~ /convert/ + abort "Please install ImageMagick`" + end ENV['KINDLE'] = '1' Rake::Task['guides:generate:html'].invoke end diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb index 3b124ef236..af9c5b8372 100644 --- a/guides/rails_guides/generator.rb +++ b/guides/rails_guides/generator.rb @@ -84,7 +84,7 @@ module RailsGuides @warnings = ENV['WARNINGS'] == '1' @all = ENV['ALL'] == '1' @kindle = ENV['KINDLE'] == '1' - @version = ENV['RAILS_VERSION'] || `git rev-parse --short HEAD`.chomp + @version = ENV['RAILS_VERSION'] || 'local' @lang = ENV['GUIDES_LANGUAGE'] end @@ -112,11 +112,9 @@ module RailsGuides end def generate_mobi - opf = "#{output_dir}/rails_guides.opf" + require 'rails_guides/kindle' out = "#{output_dir}/kindlegen.out" - - system "kindlegen #{opf} -o #{mobi} > #{out} 2>&1" - puts "Guides compiled as Kindle book to #{mobi}" + Kindle.generate(output_dir, mobi, out) puts "(kindlegen log at #{out})." end diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb new file mode 100644 index 0000000000..09eecd5634 --- /dev/null +++ b/guides/rails_guides/kindle.rb @@ -0,0 +1,119 @@ +#!/usr/bin/env ruby + +unless `which kindlerb` + abort "Please gem install kindlerb" +end + +require 'nokogiri' +require 'fileutils' +require 'yaml' +require 'date' + +module Kindle + extend self + + def generate(output_dir, mobi_outfile, logfile) + output_dir = File.absolute_path(output_dir) + Dir.chdir output_dir do + puts "=> Using output dir: #{output_dir}" + puts "=> Arranging html pages in document order" + toc = File.read("toc.ncx") + doc = Nokogiri::XML(toc).xpath("//ncx:content", 'ncx' => "http://www.daisy.org/z3986/2005/ncx/") + html_pages = doc.select {|c| c[:src]}.map {|c| c[:src]}.uniq + + generate_front_matter(html_pages) + + generate_sections(html_pages) + + generate_document_metadata(mobi_outfile) + + puts "Creating MOBI document with kindlegen. This make take a while." + cmd = "kindlerb . > #{File.absolute_path logfile} 2>&1" + puts cmd + system(cmd) + puts "MOBI document generated at #{File.expand_path(mobi_outfile, output_dir)}" + end + end + + def generate_front_matter(html_pages) + frontmatter = [] + html_pages.delete_if {|x| + if x =~ /(toc|welcome|credits|copyright).html/ + frontmatter << x unless x =~ /toc/ + true + end + } + html = frontmatter.map {|x| + Nokogiri::HTML(File.open(x)).at("body").inner_html + }.join("\n") + + fdoc = Nokogiri::HTML(html) + fdoc.search("h3").each do |h3| + h3.name = 'h4' + end + fdoc.search("h2").each do |h2| + h2.name = 'h3' + h2['id'] = h2.inner_text.gsub(/\s/, '-') + end + add_head_section fdoc, "Front Matter" + File.open("frontmatter.html",'w') {|f| f.puts fdoc.to_html} + html_pages.unshift "frontmatter.html" + end + + def generate_sections(html_pages) + FileUtils::rm_rf("sections/") + html_pages.each_with_index do |page, section_idx| + FileUtils::mkdir_p("sections/%03d" % section_idx) + doc = Nokogiri::HTML(File.open(page)) + title = doc.at("title").inner_text.gsub("Ruby on Rails Guides: ", '') + title = page.capitalize.gsub('.html', '') if title.strip == '' + File.open("sections/%03d/_section.txt" % section_idx, 'w') {|f| f.puts title} + doc.xpath("//h3[@id]").each_with_index do |h3,item_idx| + subsection = h3.inner_text + content = h3.xpath("./following-sibling::*").take_while {|x| x.name != "h3"}.map {|x| x.to_html} + item = Nokogiri::HTML(h3.to_html + content.join("\n")) + item_path = "sections/%03d/%03d.html" % [section_idx, item_idx] + add_head_section(item, subsection) + item.search("img").each do |img| + img['src'] = "#{Dir.pwd}/#{img['src']}" + end + item.xpath("//li/p").each {|p| p.swap(p.children); p.remove} + File.open(item_path, 'w') {|f| f.puts item.to_html} + end + end + end + + def generate_document_metadata(mobi_outfile) + puts "=> Generating _document.yml" + x = Nokogiri::XML(File.open("rails_guides.opf")).remove_namespaces! + cover_jpg = "#{Dir.pwd}/images/rails_guides_kindle_cover.jpg" + cover_gif = cover_jpg.sub(/jpg$/, 'gif') + puts `convert #{cover_jpg} #{cover_gif}` + document = { + 'doc_uuid' => x.at("package")['unique-identifier'], + 'title' => x.at("title").inner_text.gsub(/\(.*$/, " v2"), + 'publisher' => x.at("publisher").inner_text, + 'author' => x.at("creator").inner_text, + 'subject' => x.at("subject").inner_text, + 'date' => x.at("date").inner_text, + 'cover' => cover_gif, + 'masthead' => nil, + 'mobi_outfile' => mobi_outfile + } + puts document.to_yaml + File.open("_document.yml", 'w'){|f| f.puts document.to_yaml} + end + + def add_head_section(doc, title) + head = Nokogiri::XML::Node.new "head", doc + title_node = Nokogiri::XML::Node.new "title", doc + title_node.content = title + title_node.parent = head + css = Nokogiri::XML::Node.new "link", doc + css['rel'] = 'stylesheet' + css['type'] = 'text/css' + css['href'] = "#{Dir.pwd}/stylesheets/kindle.css" + css.parent = head + doc.at("body").before head + end +end diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 42794b180e..dd57787111 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -143,7 +143,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ * `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and `#encode_json` methods for custom JSON string literals. -* Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols). +* Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols). * BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby stdlib. @@ -165,8 +165,48 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt ### Notable changes +* Adds some metadata columns to `schema_migrations` table. + + * `migrated_at` + * `fingerprint` - an md5 hash of the migration. + * `name` - the filename minus version and extension. + +* Adds PostgreSQL array type support. Any datatype can be used to create an array column, with full migration and schema dumper support. + +* Add `Relation#load` to explicitly load the record and return `self`. + +* `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. + +* Added `ActiveRecord::Migration.check_pending!` that raises an error if migrations are pending. + +* Added custom coders support for `ActiveRecord::Store`. Now you can set your custom coder like this: + + store :settings, accessors: [ :color, :homepage ], coder: JSON + +* `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`. + +* Remove IdentityMap. + +* Adds `ActiveRecord::NullRelation` and `ActiveRecord::Relation#none` implementing the null object pattern for the Relation class. + +* Added `create_join_table` migration helper to create HABTM join tables. + +* Allows PostgreSQL hstore records to be created. + ### Deprecations +* Deprecated the old-style hash based finder API. This means that methods which previously accepted "finder options" no longer do. + +* 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!`. + Credits ------- diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index 9d2e9c1d68..a50961a0c7 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -1,4 +1,4 @@ -<h2>Ruby on Rails Guides (<%= @version %>)</h2> +<h2>Ruby on Rails Guides (<%= @edge ? @version[0, 7] : @version %>)</h2> <% if @edge %> <p> diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 8bd28d5246..46ff9027fd 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -5,13 +5,13 @@ In this guide you will learn how controllers work and how they fit into the requ After reading this guide, you will know: -* Follow the flow of a request through a controller. -* Understand why and how to store data in the session or cookies. -* Work with filters to execute code during request processing. -* Use Action Controller's built-in HTTP authentication. -* Stream data directly to the user's browser. -* Filter sensitive parameters so they do not appear in the application's log. -* Deal with exceptions that may be raised during request processing. +* How to follow the flow of a request through a controller. +* Why and how to store data in the session or cookies. +* How to work with filters to execute code during request processing. +* How to use Action Controller's built-in HTTP authentication. +* How to stream data directly to the user's browser. +* How to filter sensitive parameters so they do not appear in the application's log. +* How to deal with exceptions that may be raised during request processing. -------------------------------------------------------------------------------- @@ -434,7 +434,7 @@ Filters are inherited, so if you set a filter on `ApplicationController`, it wil ```ruby class ApplicationController < ActionController::Base - before_filter :require_login + before_action :require_login private @@ -458,11 +458,11 @@ end The method simply stores an error message in the flash and redirects to the login form if the user is not logged in. If a "before" filter renders or redirects, the action will not run. If there are additional filters scheduled to run after that filter, they are also cancelled. -In this example the filter is added to `ApplicationController` and thus all controllers in the application inherit it. This will make everything in the application require the user to be logged in in order to use it. For obvious reasons (the user wouldn't be able to log in in the first place!), not all controllers or actions should require this. You can prevent this filter from running before particular actions with `skip_before_filter`: +In this example the filter is added to `ApplicationController` and thus all controllers in the application inherit it. This will make everything in the application require the user to be logged in in order to use it. For obvious reasons (the user wouldn't be able to log in in the first place!), not all controllers or actions should require this. You can prevent this filter from running before particular actions with `skip_before_action`: ```ruby class LoginsController < ApplicationController - skip_before_filter :require_login, only: [:new, :create] + skip_before_action :require_login, only: [:new, :create] end ``` @@ -480,7 +480,7 @@ For example, in a website where changes have an approval workflow an administrat ```ruby class ChangesController < ActionController::Base - around_filter :wrap_in_transaction, only: :show + around_action :wrap_in_transaction, only: :show private @@ -502,13 +502,13 @@ You can choose not to yield and build the response yourself, in which case the a ### Other Ways to Use Filters -While the most common way to use filters is by creating private methods and using *_filter to add them, there are two other ways to do the same thing. +While the most common way to use filters is by creating private methods and using *_action to add them, there are two other ways to do the same thing. -The first is to use a block directly with the *_filter methods. The block receives the controller as an argument, and the `require_login` filter from above could be rewritten to use a block: +The first is to use a block directly with the *_action methods. The block receives the controller as an argument, and the `require_login` filter from above could be rewritten to use a block: ```ruby class ApplicationController < ActionController::Base - before_filter do |controller| + before_action do |controller| redirect_to new_login_url unless controller.send(:logged_in?) end end @@ -520,7 +520,7 @@ The second way is to use a class (actually, any object that responds to the righ ```ruby class ApplicationController < ActionController::Base - before_filter LoginFilter + before_action LoginFilter end class LoginFilter @@ -648,7 +648,7 @@ HTTP digest authentication is superior to the basic authentication as it does no class AdminController < ApplicationController USERS = { "lifo" => "world" } - before_filter :authenticate + before_action :authenticate private @@ -828,7 +828,7 @@ end class ClientsController < ApplicationController # Check that the user has the right authorization to access clients. - before_filter :check_authorization + before_action :check_authorization # Note how the actions don't have to worry about all the auth stuff. def edit @@ -849,7 +849,7 @@ NOTE: Certain exceptions are only rescuable from the `ApplicationController` cla Force HTTPS protocol -------------------- -Sometime you might want to force a particular controller to only be accessible via an HTTPS protocol for security reasons. Since Rails 3.1 you can now use the `force_ssl` method in your controller to enforce that: +Sometime you might want to force a particular controller to only be accessible via an HTTPS protocol for security reasons. You can use the `force_ssl` method in your controller to enforce that: ```ruby class DinnerController @@ -857,7 +857,7 @@ class DinnerController end ``` -Just like the filter, you could also passing `:only` and `:except` to enforce the secure connection only to specific actions: +Just like the filter, you could also pass `:only` and `:except` to enforce the secure connection only to specific actions: ```ruby class DinnerController diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index ddb0e438c9..6bb3439e5e 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -5,6 +5,10 @@ This guide should provide you with all you need to get started in sending and re After reading this guide, you will know: +* How to send and receive email within a Rails application. +* How to generate and edit an Action Mailer class and mailer view. +* How to configure Action Mailer for your environment. +* How to test your Action Mailer classes. -------------------------------------------------------------------------------- Introduction @@ -105,7 +109,7 @@ When you call the `mail` method now, Action Mailer will detect the two templates #### Wire It Up So That the System Sends the Email When a User Signs Up -There are several ways to do this, some people create Rails Observers to fire off emails, others do it inside of the User Model. However, in Rails 3, mailers are really just another way to render a view. Instead of rendering a view and sending out the HTTP protocol, they are just sending it out through the Email protocols instead. Due to this, it makes sense to just have your controller tell the mailer to send an email when a user is successfully created. +There are several ways to do this, some people create Rails Observers to fire off emails, others do it inside of the User Model. However, mailers are really just another way to render a view. Instead of rendering a view and sending out the HTTP protocol, they are just sending it out through the Email protocols instead. Due to this, it makes sense to just have your controller tell the mailer to send an email when a user is successfully created. Setting this up is painfully simple. @@ -145,10 +149,6 @@ This provides a much simpler implementation that does not require the registerin The method `welcome_email` returns a `Mail::Message` object which can then just be told `deliver` to send itself out. -NOTE: In previous versions of Rails, you would call `deliver_welcome_email` or `create_welcome_email`. This has been deprecated in Rails 3.0 in favour of just calling the method name itself. - -WARNING: Sending out an email should only take a fraction of a second. If you are planning on sending out many emails, or you have a slow domain resolution service, you might want to investigate using a background process like Delayed Job. - ### Auto encoding header values Action Mailer now handles the auto encoding of multibyte characters inside of headers and bodies. @@ -447,17 +447,17 @@ end Action Mailer Callbacks --------------------------- -Action Mailer allows for you to specify a `before_filter`, `after_filter` and 'around_filter'. +Action Mailer allows for you to specify a `before_action`, `after_action` and 'around_action'. * Filters can be specified with a block or a symbol to a method in the mailer class similar to controllers. -* You could use a `before_filter` to prepopulate the mail object with defaults, delivery_method_options or insert default headers and attachments. +* You could use a `before_action` to prepopulate the mail object with defaults, delivery_method_options or insert default headers and attachments. -* You could use an `after_filter` to do similar setup as a `before_filter` but using instance variables set in your mailer action. +* You could use an `after_action` to do similar setup as a `before_action` but using instance variables set in your mailer action. ```ruby class UserMailer < ActionMailer::Base - after_filter :set_delivery_options, :prevent_delivery_to_guests, :set_business_headers + after_action :set_delivery_options, :prevent_delivery_to_guests, :set_business_headers def feedback_message(business, user) @business = business @@ -516,7 +516,6 @@ The following configuration options are best made in one of the environment file |`perform_deliveries`|Determines whether deliveries are actually carried out when the `deliver` method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.| |`deliveries`|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.| |`default_options`|Allows you to set default values for the `mail` method options (`:from`, `:reply_to`, etc.).| -|`async`|Setting this flag will turn on asynchronous message sending, message rendering and delivery will be pushed to `Rails.queue` for processing.| ### Example Action Mailer Configuration diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index c931b30bd3..4cdac43a7e 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -1263,8 +1263,6 @@ Creates a field set for grouping HTML form elements. Creates a file upload field. -Prior to Rails 3.1, if you are using file uploads, then you will need to set the multipart option for the form tag. Rails 3.1+ does this automatically. - ```html+erb <%= form_tag {action: "post"}, {multipart: true} do %> <label for="file">File to Upload</label> <%= file_field_tag "file" %> @@ -1486,7 +1484,7 @@ You can use the same technique to localize the rescue files in your public direc Since Rails doesn't restrict the symbols that you use to set I18n.locale, you can leverage this system to display different content depending on anything you like. For example, suppose you have some "expert" users that should see different pages from "normal" users. You could add the following to `app/controllers/application.rb`: ```ruby -before_filter :set_expert_locale +before_action :set_expert_locale def set_expert_locale I18n.locale = :expert if current_user.expert? diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 68c6416e89..883c2dda4a 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -5,9 +5,11 @@ This guide is an introduction to Active Record. After reading this guide, you will know: -* What Object Relational Mapping and Active Record are and how they are used in Rails. +* What Object Relational Mapping and Active Record are and how they are used in + Rails. * How Active Record fits into the Model-View-Controller paradigm. -* How to use Active Record models to manipulate data stored in a relational database. +* How to use Active Record models to manipulate data stored in a relational + database. * Active Record schema naming conventions. * The concepts of database migrations, validations and callbacks. @@ -16,19 +18,34 @@ After reading this guide, you will know: What is Active Record? ---------------------- -Active Record is the M in [MVC](getting_started.html#the-mvc-architecture) - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system. +Active Record is the M in [MVC](getting_started.html#the-mvc-architecture) - the +model - which is the layer of the system responsible for representing business +data and logic. Active Record facilitates the creation and use of business +objects whose data requires persistent storage to a database. It is an +implementation of the Active Record pattern which itself is a description of an +Object Relational Mapping system. ### The Active Record Pattern -Active Record was described by Martin Fowler in his book _Patterns of Enterprise Application Architecture_. In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring data access logic is part of the object will educate users of that object on how to write to and read from the database. +Active Record was described by Martin Fowler in his book _Patterns of Enterprise +Application Architecture_. In Active Record, objects carry both persistent data +and behavior which operates on that data. Active Record takes the opinion that +ensuring data access logic is part of the object will educate users of that +object on how to write to and read from the database. ### Object Relational Mapping -Object-Relational Mapping, commonly referred to as its abbreviation ORM, is a technique that connects the rich objects of an application to tables in a relational database management system. Using ORM, the properties and relationships of the objects in an application can be easily stored and retrieved from a database without writing SQL statements directly and with less overall database access code. +Object-Relational Mapping, commonly referred to as its abbreviation ORM, is +a technique that connects the rich objects of an application to tables in +a relational database management system. Using ORM, the properties and +relationships of the objects in an application can be easily stored and +retrieved from a database without writing SQL statements directly and with less +overall database access code. ### Active Record as an ORM Framework -Active Record gives us several mechanisms, the most important being the ability to: +Active Record gives us several mechanisms, the most important being the ability +to: * Represent models and their data * Represent associations between these models @@ -39,14 +56,30 @@ Active Record gives us several mechanisms, the most important being the ability Convention over Configuration in Active Record ---------------------------------------------- -When writing applications using other programming languages or frameworks, it may be necessary to write a lot of configuration code. This is particularly true for ORM frameworks in general. However, if you follow the conventions adopted by Rails, you'll need to write very little configuration (in some case no configuration at all) when creating Active Record models. The idea is that if you configure your applications in the very same way most of the times then this should be the default way. In this cases, explicit configuration would be needed only in those cases where you can't follow the conventions for any reason. +When writing applications using other programming languages or frameworks, it +may be necessary to write a lot of configuration code. This is particularly true +for ORM frameworks in general. However, if you follow the conventions adopted by +Rails, you'll need to write very little configuration (in some case no +configuration at all) when creating Active Record models. The idea is that if +you configure your applications in the very same way most of the times then this +should be the default way. In this cases, explicit configuration would be needed +only in those cases where you can't follow the conventions for any reason. ### Naming Conventions -By default, Active Record uses some naming conventions to find out how the mapping between models and database tables should be created. Rails will pluralize your class names to find the respective database table. So, for a class `Book`, you should have a database table called **books**. The Rails pluralization mechanisms are very powerful, being capable to pluralize (and singularize) both regular and irregular words. When using class names composed of two or more words, the model class name should follow the Ruby conventions, using the CamelCase form, while the table name must contain the words separated by underscores. Examples: +By default, Active Record uses some naming conventions to find out how the +mapping between models and database tables should be created. Rails will +pluralize your class names to find the respective database table. So, for +a class `Book`, you should have a database table called **books**. The Rails +pluralization mechanisms are very powerful, being capable to pluralize (and +singularize) both regular and irregular words. When using class names composed +of two or more words, the model class name should follow the Ruby conventions, +using the CamelCase form, while the table name must contain the words separated +by underscores. Examples: * Database Table - Plural with underscores separating words (e.g., `book_clubs`) -* Model Class - Singular with the first letter of each word capitalized (e.g., `BookClub`) +* Model Class - Singular with the first letter of each word capitalized (e.g., +`BookClub`) | Model / Class | Table / Schema | | ------------- | -------------- | @@ -59,34 +92,52 @@ By default, Active Record uses some naming conventions to find out how the mappi ### Schema Conventions -Active Record uses naming conventions for the columns in database tables, depending on the purpose of these columns. - -* **Foreign keys** - These fields should be named following the pattern `singularized_table_name_id` (e.g., `item_id`, `order_id`). These are the fields that Active Record will look for when you create associations between your models. -* **Primary keys** - By default, Active Record will use an integer column named `id` as the table's primary key. When using [Rails Migrations](migrations.html) to create your tables, this column will be automatically created. - -There are also some optional column names that will create additional features to Active Record instances: - -* `created_at` - Automatically gets set to the current date and time when the record is first created. -* `created_on` - Automatically gets set to the current date when the record is first created. -* `updated_at` - Automatically gets set to the current date and time whenever the record is updated. -* `updated_on` - Automatically gets set to the current date whenever the record is updated. -* `lock_version` - Adds [optimistic locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to a model. -* `type` - Specifies that the model uses [Single Table Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html) -* `(table_name)_count` - Used to cache the number of belonging objects on associations. For example, a `comments_count` column in a `Post` class that has many instances of `Comment` will cache the number of existent comments for each post. +Active Record uses naming conventions for the columns in database tables, +depending on the purpose of these columns. + +* **Foreign keys** - These fields should be named following the pattern + `singularized_table_name_id` (e.g., `item_id`, `order_id`). These are the + fields that Active Record will look for when you create associations between + your models. +* **Primary keys** - By default, Active Record will use an integer column named + `id` as the table's primary key. When using [Rails + Migrations](migrations.html) to create your tables, this column will be + automatically created. + +There are also some optional column names that will create additional features +to Active Record instances: + +* `created_at` - Automatically gets set to the current date and time when the + record is first created. +* `updated_at` - Automatically gets set to the current date and time whenever + the record is updated. +* `lock_version` - Adds [optimistic + locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to + a model. +* `type` - Specifies that the model uses [Single Table + Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html) +* `(table_name)_count` - Used to cache the number of belonging objects on + associations. For example, a `comments_count` column in a `Post` class that + has many instances of `Comment` will cache the number of existent comments + for each post. NOTE: While these column names are optional, they are in fact reserved by Active Record. Steer clear of reserved keywords unless you want the extra functionality. For example, `type` is a reserved keyword used to designate a table using Single Table Inheritance (STI). If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling. Creating Active Record Models ----------------------------- -It is very easy to create Active Record models. All you have to do is to subclass the `ActiveRecord::Base` class and you're good to go: +It is very easy to create Active Record models. All you have to do is to +subclass the `ActiveRecord::Base` class and you're good to go: ```ruby class Product < ActiveRecord::Base end ``` -This will create a `Product` model, mapped to a `products` table at the database. By doing this you'll also have the ability to map the columns of each row in that table with the attributes of the instances of your model. Suppose that the `products` table was created using an SQL sentence like: +This will create a `Product` model, mapped to a `products` table at the +database. By doing this you'll also have the ability to map the columns of each +row in that table with the attributes of the instances of your model. Suppose +that the `products` table was created using an SQL sentence like: ```sql CREATE TABLE products ( @@ -96,7 +147,8 @@ CREATE TABLE products ( ); ``` -Following the table schema above, you would be able to write code like the following: +Following the table schema above, you would be able to write code like the +following: ```ruby p = Product.new @@ -107,9 +159,12 @@ puts p.name # "Some Book" Overriding the Naming Conventions --------------------------------- -What if you need to follow a different naming convention or need to use your Rails application with a legacy database? No problem, you can easily override the default conventions. +What if you need to follow a different naming convention or need to use your +Rails application with a legacy database? No problem, you can easily override +the default conventions. -You can use the `ActiveRecord::Base.table_name=` method to specify the table name that should be used: +You can use the `ActiveRecord::Base.table_name=` method to specify the table +name that should be used: ```ruby class Product < ActiveRecord::Base @@ -117,7 +172,9 @@ class Product < ActiveRecord::Base end ``` -If you do so, you will have to define manually the class name that is hosting the fixtures (class_name.yml) using the `set_fixture_class` method in your test definition: +If you do so, you will have to define manually the class name that is hosting +the fixtures (class_name.yml) using the `set_fixture_class` method in your test +definition: ```ruby class FunnyJoke < ActiveSupport::TestCase @@ -127,7 +184,8 @@ class FunnyJoke < ActiveSupport::TestCase end ``` -It's also possible to override the column that should be used as the table's primary key using the `ActiveRecord::Base.set_primary_key` method: +It's also possible to override the column that should be used as the table's +primary key using the `ActiveRecord::Base.set_primary_key` method: ```ruby class Product < ActiveRecord::Base @@ -138,19 +196,24 @@ end CRUD: Reading and Writing Data ------------------------------ -CRUD is an acronym for the four verbs we use to operate on data: **C**reate, **R**ead, **U**pdate and **D**elete. Active Record automatically creates methods to allow an application to read and manipulate data stored within its tables. +CRUD is an acronym for the four verbs we use to operate on data: **C**reate, +**R**ead, **U**pdate and **D**elete. Active Record automatically creates methods +to allow an application to read and manipulate data stored within its tables. ### Create -Active Record objects can be created from a hash, a block or have their attributes manually set after creation. The `new` method will return a new object while `create` will return the object and save it to the database. +Active Record objects can be created from a hash, a block or have their +attributes manually set after creation. The `new` method will return a new +object while `create` will return the object and save it to the database. -For example, given a model `User` with attributes of `name` and `occupation`, the `create` method call will create and save a new record into the database: +For example, given a model `User` with attributes of `name` and `occupation`, +the `create` method call will create and save a new record into the database: ```ruby user = User.create(name: "David", occupation: "Code Artist") ``` -Using the `new` method, an object can be created without being saved: +Using the `new` method, an object can be instantiated without being saved: ```ruby user = User.new @@ -160,7 +223,8 @@ user.occupation = "Code Artist" A call to `user.save` will commit the record to the database. -Finally, if a block is provided, both `create` and `new` will yield the new object to that block for initialization: +Finally, if a block is provided, both `create` and `new` will yield the new +object to that block for initialization: ```ruby user = User.new do |u| @@ -171,7 +235,8 @@ end ### Read -Active Record provides a rich API for accessing data within a database. Below are a few examples of different data access methods provided by Active Record. +Active Record provides a rich API for accessing data within a database. Below +are a few examples of different data access methods provided by Active Record. ```ruby # return array with all records @@ -193,11 +258,13 @@ david = User.find_by_name('David') users = User.where(name: 'David', occupation: 'Code Artist').order('created_at DESC') ``` -You can learn more about querying an Active Record model in the [Active Record Query Interface](active_record_querying.html) guide. +You can learn more about querying an Active Record model in the [Active Record +Query Interface](active_record_querying.html) guide. ### Update -Once an Active Record object has been retrieved, its attributes can be modified and it can be saved to the database. +Once an Active Record object has been retrieved, its attributes can be modified +and it can be saved to the database. ```ruby user = User.find_by_name('David') @@ -205,9 +272,26 @@ user.name = 'Dave' user.save ``` +A shorthand for this is to use a hash mapping attribute names to the desired +value, like so: + +```ruby +user = User.find_by_name('David') +user.update_attributes(name: 'Dave') +``` + +This is most useful when updating several attributes at once. If, on the other +hand, you'd like to update several records in bulk, you may find the +`update_all` class method useful: + +```ruby +User.update_all "max_login_attempts = 3, must_change_password = 'true'" +``` + ### Delete -Likewise, once retrieved an Active Record object can be destroyed which removes it from the database. +Likewise, once retrieved an Active Record object can be destroyed which removes +it from the database. ```ruby user = User.find_by_name('David') @@ -217,14 +301,70 @@ user.destroy Validations ----------- -Active Record allows you to validate the state of a model before it gets written into the database. There are several methods that you can use to check your models and validate that an attribute value is not empty, is unique and not already in the database, follows a specific format and many more. You can learn more about validations in the [Active Record Validations and Callbacks guide](active_record_validations_callbacks.html#validations-overview). +Active Record allows you to validate the state of a model before it gets written +into the database. There are several methods that you can use to check your +models and validate that an attribute value is not empty, is unique and not +already in the database, follows a specific format and many more. + +Validation is a very important issue to consider when persisting to database, so +the methods `create`, `save` and `update_attributes` take it into account when +running: they return `false` when validation fails and they didn't actually +perform any operation on database. All of these have a bang counterpart (that +is, `create!`, `save!` and `update_attributes!`), which are stricter in that +they raise the exception `ActiveRecord::RecordInvalid` if validation fails. +A quick example to illustrate: + +```ruby +class User < ActiveRecord::Base + validates_presence_of :name +end + +User.create # => false +User.create! # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank +``` + +You can learn more about validations in the [Active Record Validations +guide](active_record_validations.html). Callbacks --------- -Active Record callbacks allow you to attach code to certain events in the life-cycle of your models. This enables you to add behavior to your models by transparently executing code when those events occur, like when you create a new record, update it, destroy it and so on. You can learn more about callbacks in the [Active Record Validations and Callbacks guide](active_record_validations_callbacks.html#callbacks-overview). +Active Record callbacks allow you to attach code to certain events in the +life-cycle of your models. This enables you to add behavior to your models by +transparently executing code when those events occur, like when you create a new +record, update it, destroy it and so on. You can learn more about callbacks in +the [Active Record Callbacks guide](active_record_callbacks.html). Migrations ---------- -Rails provides a domain-specific language for managing a database schema called migrations. Migrations are stored in files which are executed against any database that Active Record support using rake. Rails keeps track of which files have been committed to the database and provides rollback features. You can learn more about migrations in the [Active Record Migrations guide](migrations.html) +Rails provides a domain-specific language for managing a database schema called +migrations. Migrations are stored in files which are executed against any +database that Active Record support using `rake`. Here's a migration that +creates a table: + +```ruby +class CreatePublications < ActiveRecord::Migration + def change + create_table :publications do |t| + t.string :title + t.text :description + t.references :publication_type + t.integer :publisher_id + t.string :publisher_type + t.boolean :single_issue + + t.timestamps + end + add_index :publications, :publication_type_id + end +end +``` + +Rails keeps track of which files have been committed to the database and +provides rollback features. To actually create the table, you'd run `rake db:migrate` +and to roll it back, `rake db:rollback`. + +Note that the above code is database-agnostic: it will run in MySQL, postgresql, +Oracle and others. You can learn more about migrations in the [Active Record +Migrations guide](migrations.html) diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 02c1c46a5a..0a93f61f6d 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -4,11 +4,11 @@ Active Record Callbacks This guide teaches you how to hook into the life cycle of your Active Record objects. -After reading this guide and trying out the presented concepts, we hope that you'll be able to: +After reading this guide, you will know: -* Understand the life cycle of Active Record objects -* Create callback methods that respond to events in the object life cycle -* Create special classes that encapsulate common behavior for your callbacks +* The life cycle of Active Record objects. +* How to create callback methods that respond to events in the object life cycle. +* How to create special classes that encapsulate common behavior for your callbacks. -------------------------------------------------------------------------------- @@ -150,6 +150,7 @@ The following methods trigger callbacks: * `create!` * `decrement!` * `destroy` +* `destroy!` * `destroy_all` * `increment!` * `save` diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 9620270141..24f98f68ca 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -5,13 +5,13 @@ This guide covers different ways to retrieve data from the database using Active After reading this guide, you will know: -* Find records using a variety of methods and conditions. -* Specify the order, retrieved attributes, grouping, and other properties of the found records. -* Use eager loading to reduce the number of database queries needed for data retrieval. -* Use dynamic finders methods. -* Check for the existence of particular records. -* Perform various calculations on Active Record models. -* Run EXPLAIN on relations. +* How to find records using a variety of methods and conditions. +* How to specify the order, retrieved attributes, grouping, and other properties of the found records. +* How to use eager loading to reduce the number of database queries needed for data retrieval. +* How to use dynamic finders methods. +* How to check for the existence of particular records. +* How to perform various calculations on Active Record models. +* How to run EXPLAIN on relations. -------------------------------------------------------------------------------- @@ -505,6 +505,20 @@ This code will generate SQL like this: SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) ``` +### NOT, LIKE, and NOT LIKE Conditions + +`NOT`, `LIKE`, and `NOT LIKE` SQL queries can be built by `where.not`, `where.like`, and `where.not_like` respectively. + +```ruby +Post.where.not(author: author) + +Author.where.like(name: 'Nari%') + +Developer.where.not_like(name: 'Tenderl%') +``` + +In other words, these sort of queries can be generated by calling `where` with no argument, then immediately chain with `not`, `like`, or `not_like` passing `where` conditions. + Ordering -------- @@ -1190,7 +1204,7 @@ class Client < ActiveRecord::Base end ``` -### Removing all scoping +### Removing All Scoping If we wish to remove scoping for any reason we can use the `unscoped` method. This is especially useful if a `default_scope` is specified in the model and should not be @@ -1222,9 +1236,7 @@ You can specify an exclamation point (`!`) on the end of the dynamic finders to If you want to find both by name and locked, you can chain these finders together by simply typing "`and`" between the fields. For example, `Client.find_by_first_name_and_locked("Ryan", true)`. -WARNING: Up to and including Rails 3.1, when the number of arguments passed to a dynamic finder method is lesser than the number of fields, say `Client.find_by_name_and_locked("Ryan")`, the behavior is to pass `nil` as the missing argument. This is **unintentional** and this behavior will be changed in Rails 3.2 to throw an `ArgumentError`. - -Find or build a new object +Find or Build a New Object -------------------------- It's common that you need to find a record or create it if it doesn't exist. You can do that with the `find_or_create_by` and `find_or_create_by!` methods. diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 4642ef82f0..822d12aa3a 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -6,9 +6,9 @@ the database using Active Record's validations feature. After reading this guide, you will know: -* Use the built-in Active Record validation helpers -* Create your own custom validation methods -* Work with the error messages generated by the validation process +* How to use the built-in Active Record validation helpers. +* How to create your own custom validation methods. +* How to work with the error messages generated by the validation process. -------------------------------------------------------------------------------- @@ -503,8 +503,8 @@ end ``` If you want to be sure that an association is present, you'll need to test -whether the foreign key used to map the association is present, and not the -associated object itself. +whether the associated object itself is present, and not the foreign key used +to map the association. ```ruby class LineItem < ActiveRecord::Base @@ -513,7 +513,8 @@ class LineItem < ActiveRecord::Base end ``` -You should also be sure to have a proper `:inverse_of` as well: +In order to validate associated records whose presence is required, you must +specify the `:inverse_of` option for the association: ```ruby class Order < ActiveRecord::Base @@ -779,7 +780,7 @@ class Account < ActiveRecord::Base end ``` -### Grouping conditional validations +### Grouping Conditional validations Sometimes it is useful to have multiple validations use one condition, it can be easily achieved using `with_options`. @@ -796,7 +797,7 @@ end All validations inside of `with_options` block will have automatically passed the condition `if: :is_admin?` -### Combining validation conditions +### Combining Validation Conditions On the other hand, when multiple conditions define whether or not a validation should happen, an `Array` can be used. Moreover, you can apply both `:if` and @@ -908,6 +909,146 @@ class Invoice < ActiveRecord::Base end ``` +Working with Validation Errors +------------------------------ + +In addition to the `valid?` and `invalid?` methods covered earlier, Rails provides a number of methods for working with the `errors` collection and inquiring about the validity of objects. + +The following is a list of the most commonly used methods. Please refer to the `ActiveModel::Errors` documentation for a list of all the available methods. + +### `errors` + +Returns an instance of the class `ActiveModel::Errors` containing all errors. Each key is the attribute name and the value is an array of strings with all errors. + +```ruby +class Person < ActiveRecord::Base + validates :name, presence: true, length: { minimum: 3 } +end + +person = Person.new +person.valid? # => false +person.errors + # => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]} + +person = Person.new(name: "John Doe") +person.valid? # => true +person.errors # => [] +``` + +### `errors[]` + +`errors[]` is used when you want to check the error messages for a specific attribute. It returns an array of strings with all error messages for the given attribute, each string with one error message. If there are no errors related to the attribute, it returns an empty array. + +```ruby +class Person < ActiveRecord::Base + validates :name, presence: true, length: { minimum: 3 } +end + +person = Person.new(name: "John Doe") +person.valid? # => true +person.errors[:name] # => [] + +person = Person.new(name: "JD") +person.valid? # => false +person.errors[:name] # => ["is too short (minimum is 3 characters)"] + +person = Person.new +person.valid? # => false +person.errors[:name] + # => ["can't be blank", "is too short (minimum is 3 characters)"] +``` + +### `errors.add` + +The `add` method lets you manually add messages that are related to particular attributes. You can use the `errors.full_messages` or `errors.to_a` methods to view the messages in the form they might be displayed to a user. Those particular messages get the attribute name prepended (and capitalized). `add` receives the name of the attribute you want to add the message to, and the message itself. + +```ruby +class Person < ActiveRecord::Base + def a_method_used_for_validation_purposes + errors.add(:name, "cannot contain the characters !@#%*()_-+=") + end +end + +person = Person.create(name: "!@#") + +person.errors[:name] + # => ["cannot contain the characters !@#%*()_-+="] + +person.errors.full_messages + # => ["Name cannot contain the characters !@#%*()_-+="] +``` + +Another way to do this is using `[]=` setter + +```ruby + class Person < ActiveRecord::Base + def a_method_used_for_validation_purposes + errors[:name] = "cannot contain the characters !@#%*()_-+=" + end + end + + person = Person.create(name: "!@#") + + person.errors[:name] + # => ["cannot contain the characters !@#%*()_-+="] + + person.errors.to_a + # => ["Name cannot contain the characters !@#%*()_-+="] +``` + +### `errors[:base]` + +You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message. + +```ruby +class Person < ActiveRecord::Base + def a_method_used_for_validation_purposes + errors[:base] << "This person is invalid because ..." + end +end +``` + +### `errors.clear` + +The `clear` method is used when you intentionally want to clear all the messages in the `errors` collection. Of course, calling `errors.clear` upon an invalid object won't actually make it valid: the `errors` collection will now be empty, but the next time you call `valid?` or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the `errors` collection will be filled again. + +```ruby +class Person < ActiveRecord::Base + validates :name, presence: true, length: { minimum: 3 } +end + +person = Person.new +person.valid? # => false +person.errors[:name] + # => ["can't be blank", "is too short (minimum is 3 characters)"] + +person.errors.clear +person.errors.empty? # => true + +p.save # => false + +p.errors[:name] +# => ["can't be blank", "is too short (minimum is 3 characters)"] +``` + +### `errors.size` + +The `size` method returns the total number of error messages for the object. + +```ruby +class Person < ActiveRecord::Base + validates :name, presence: true, length: { minimum: 3 } +end + +person = Person.new +person.valid? # => false +person.errors.size # => 2 + +person = Person.new(name: "Andrea", email: "andrea@example.com") +person.valid? # => true +person.errors.size # => 0 +``` + Displaying Validation Errors in Views ------------------------------------- diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 775da2a85c..23736020ec 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -7,6 +7,11 @@ It offers a richer bottom-line at the language level, targeted both at the devel After reading this guide, you will know: +* What Core Extensions are. +* How to load all extensions. +* How to cherry-pick just the extensions you want. +* What extensions ActiveSupport provides. + -------------------------------------------------------------------------------- How to Load Core Extensions @@ -1120,8 +1125,6 @@ C.subclasses # => [B, D] The order in which these classes are returned is unspecified. -WARNING: This method is redefined in some Rails core classes but should be all compatible in Rails 3.1. - NOTE: Defined in `active_support/core_ext/class/subclasses.rb`. #### `descendants` @@ -1157,7 +1160,7 @@ Inserting data into HTML templates needs extra care. For example, you can't just #### Safe Strings -Active Support has the concept of <i>(html) safe</i> strings since Rails 3. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not. +Active Support has the concept of <i>(html) safe</i> strings. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not. Strings are considered to be <i>unsafe</i> by default: @@ -1194,10 +1197,10 @@ Safe arguments are directly appended: "".html_safe + "<".html_safe # => "<" ``` -These methods should not be used in ordinary views. In Rails 3 unsafe values are automatically escaped: +These methods should not be used in ordinary views. Unsafe values are automatically escaped: ```erb -<%= @review.title %> <%# fine in Rails 3, escaped if needed %> +<%= @review.title %> <%# fine, escaped if needed %> ``` To insert something verbatim use the `raw` helper rather than calling `html_safe`: @@ -2675,13 +2678,6 @@ If the receiver responds to `convert_key`, the method is called on each of the a {a: 1}.with_indifferent_access.except("a") # => {} ``` -The method `except` may come in handy for example when you want to protect some parameter that can't be globally protected with `attr_protected`: - -```ruby -params[:account] = params[:account].except(:plan_id) unless admin? -@account.update_attributes(params[:account]) -``` - There's also the bang variant `except!` that removes keys in the very receiver. NOTE: Defined in `active_support/core_ext/hash/except.rb`. @@ -3596,7 +3592,7 @@ Time.zone_default # => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...> # In Barcelona, 2010/03/28 02:00 +0100 becomes 2010/03/28 03:00 +0200 due to DST. -t = Time.local_time(2010, 3, 28, 1, 59, 59) +t = Time.local(2010, 3, 28, 1, 59, 59) # => Sun Mar 28 01:59:59 +0100 2010 t.advance(seconds: 1) # => Sun Mar 28 03:00:00 +0200 2010 @@ -3651,26 +3647,6 @@ Time.current Analogously to `DateTime`, the predicates `past?`, and `future?` are relative to `Time.current`. -Use the `local_time` class method to create time objects honoring the user time zone: - -```ruby -Time.zone_default -# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...> -Time.local_time(2010, 8, 15) -# => Sun Aug 15 00:00:00 +0200 2010 -``` - -The `utc_time` class method returns a time in UTC: - -```ruby -Time.zone_default -# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...> -Time.utc_time(2010, 8, 15) -# => Sun Aug 15 00:00:00 UTC 2010 -``` - -Both `local_time` and `utc_time` accept up to seven positional arguments: year, month, day, hour, min, sec, usec. Year is mandatory, month and day default to 1, and the rest default to 0. - If the time to be constructed lies beyond the range supported by `Time` in the runtime platform, usecs are discarded and a `DateTime` object is returned instead. #### Durations @@ -3689,7 +3665,7 @@ now - 1.week They translate to calls to `since` or `advance`. For example here we get the correct jump in the calendar reform: ```ruby -Time.utc_time(1582, 10, 3) + 5.days +Time.utc(1582, 10, 3) + 5.days # => Mon Oct 18 00:00:00 UTC 1582 ``` diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 0126fc94d1..d0499878da 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -5,6 +5,9 @@ This guide documents the Ruby on Rails API documentation guidelines. After reading this guide, you will know: +* How to write effective prose for documentation purposes. +* Style guidelines for documenting different kinds of Ruby code. + -------------------------------------------------------------------------------- RDoc diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index fa4e714950..743a04ed42 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -1,15 +1,15 @@ The Asset Pipeline ================== -This guide covers the asset pipeline introduced in Rails 3.1. +This guide covers the asset pipeline. After reading this guide, you will know: -* Understand what the asset pipeline is and what it does. -* Properly organize your application assets. -* Understand the benefits of the asset pipeline. -* Add a pre-processor to the pipeline. -* Package assets with a gem. +* How to understand what the asset pipeline is and what it does. +* How to properly organize your application assets. +* How to understand the benefits of the asset pipeline. +* How to add a pre-processor to the pipeline. +* How to package assets with a gem. -------------------------------------------------------------------------------- @@ -18,11 +18,9 @@ What is the Asset Pipeline? The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages such as CoffeeScript, Sass and ERB. -Prior to Rails 3.1 these features were added through third-party Ruby libraries such as Jammit and Sprockets. Rails 3.1 is integrated with Sprockets through Action Pack which depends on the `sprockets` gem, by default. - Making the asset pipeline a core feature of Rails means that all developers can benefit from the power of having their assets pre-processed, compressed and minified by one central library, Sprockets. This is part of Rails' "fast by default" strategy as outlined by DHH in his keynote at RailsConf 2011. -In Rails 3.1, the asset pipeline is enabled by default. It can be disabled in `config/application.rb` by putting this line inside the application class definition: +The asset pipeline is enabled by default. It can be disabled in `config/application.rb` by putting this line inside the application class definition: ```ruby config.assets.enabled = false @@ -100,7 +98,24 @@ In production, Rails precompiles these files to `public/assets` by default. The When you generate a scaffold or a controller, Rails also generates a JavaScript file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`) for that controller. -For example, if you generate a `ProjectsController`, Rails will also add a new file at `app/assets/javascripts/projects.js.coffee` and another at `app/assets/stylesheets/projects.css.scss`. You should put any JavaScript or CSS unique to a controller inside their respective asset files, as these files can then be loaded just for these controllers with lines such as `<%= javascript_include_tag params[:controller] %>` or `<%= stylesheet_link_tag params[:controller] %>`. +For example, if you generate a `ProjectsController`, Rails will also add a new file at `app/assets/javascripts/projects.js.coffee` and another at `app/assets/stylesheets/projects.css.scss`. You should put any JavaScript or CSS unique to a controller inside their respective asset files, as these files can then be loaded just for these controllers with lines such as `<%= javascript_include_tag params[:controller] %>` or `<%= stylesheet_link_tag params[:controller] %>`. Note that you have to set `config.assets.precompile` in `config/environments/production.rb` if you want to precomepile them and use in production mode. You can append them one by one or do something like this: + + # config/environments/production.rb + config.assets.precompile << Proc.new { |path| + if path =~ /\.(css|js)\z/ + full_path = Rails.application.assets.resolve(path).to_path + app_assets_path = Rails.root.join('app', 'assets').to_path + if full_path.starts_with? app_assets_path + puts "including asset: " + full_path + true + else + puts "excluding asset: " + full_path + false + end + else + false + end + } NOTE: You must have an [ExecJS](https://github.com/sstephenson/execjs#readme) supported runtime in order to use CoffeeScript. If you are using Mac OS X or Windows you have a JavaScript runtime installed in your operating system. Check [ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all supported JavaScript runtimes. @@ -114,7 +129,7 @@ Pipeline assets can be placed inside an application in one of three locations: ` * `vendor/assets` is for assets that are owned by outside entities, such as code for JavaScript plugins and CSS frameworks. -#### Search paths +#### Search Paths When a file is referenced from a manifest or a helper, Sprockets searches the three default asset locations for it. @@ -162,7 +177,7 @@ Paths are traversed in the order that they occur in the search path. By default, It is important to note that files you want to reference outside a manifest must be added to the precompile array or they will not be available in the production environment. -#### Using index files +#### Using Index Files Sprockets uses files named `index` (with the relevant extensions) for a special purpose. @@ -257,7 +272,8 @@ $('#logo').attr src: "<%= asset_path('logo.png') %>" ### Manifest Files and Directives -Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ — instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets loads the files specified, processes them if necessary, concatenates them into one single file and then compresses them (if `Rails.application.config.assets.compress` is true). By serving one file rather than many, the load time of pages can be greatly reduced because the browser makes fewer requests. +Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ — instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets loads the files specified, processes them if necessary, concatenates them into one single file and then compresses them (if `Rails.application.config.assets.compress` is true). By serving one file rather than many, the load time of pages can be greatly reduced because the browser makes fewer requests. Compression also reduces the file size enabling the browser to download it faster. + For example, a new Rails application includes a default `app/assets/javascripts/application.js` file which contains the following lines: @@ -270,8 +286,6 @@ For example, a new Rails application includes a default `app/assets/javascripts/ In JavaScript files, the directives begin with `//=`. In this case, the file is using the `require` and the `require_tree` directives. The `require` directive is used to tell Sprockets the files that you wish to require. Here, you are requiring the files `jquery.js` and `jquery_ujs.js` that are available somewhere in the search path for Sprockets. You need not supply the extensions explicitly. Sprockets assumes you are requiring a `.js` file when done from within a `.js` file. -NOTE. In Rails 3.1 the `jquery-rails` gem provides the `jquery.js` and `jquery_ujs.js` files via the asset pipeline. You won't see them in the application tree. - The `require_tree` directive tells Sprockets to recursively include _all_ JavaScript files in the specified directory into the output. These paths must be specified relative to the manifest file. You can also use the `require_directory` directive which includes all JavaScript files only in the directory specified, without recursion. Directives are processed top to bottom, but the order in which files are included by `require_tree` is unspecified. You should not rely on any particular order among those. If you need to ensure some particular JavaScript ends up above some other in the concatenated file, require the prerequisite file first in the manifest. Note that the family of `require` directives prevents files from being included twice in the output. @@ -337,7 +351,7 @@ would generate this HTML: The `body` param is required by Sprockets. -### Turning Debugging off +### Turning Debugging Off You can turn off debug mode by updating `config/environments/development.rb` to include: @@ -462,7 +476,7 @@ The default location for the manifest is the root of the location specified in ` NOTE: If there are missing precompiled files in production you will get an `Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError` exception indicating the name of the missing file(s). -#### Far-future Expires header +#### Far-future Expires Header Precompiled assets exist on the filesystem and are served directly by your web server. They do not have far-future headers by default, so to get the benefit of fingerprinting you'll have to update your server configuration to add them. @@ -492,7 +506,7 @@ location ~ ^/assets/ { } ``` -#### GZip compression +#### GZip Compression When files are precompiled, Sprockets also creates a [gzipped](http://en.wikipedia.org/wiki/Gzip) (.gz) version of your assets. Web servers are typically configured to use a moderate compression ratio as a compromise, but since precompilation happens once, Sprockets uses the maximum compression ratio, thus reducing the size of the data transfer to the minimum. On the other hand, web servers can be configured to serve compressed content directly from disk, rather than deflating non-compressed files themselves. @@ -648,7 +662,7 @@ This can be changed to something else: config.assets.prefix = "/some_other_path" ``` -This is a handy option if you are updating an existing project (pre Rails 3.1) that already uses this path or you wish to use this path for a new resource. +This is a handy option if you are updating an older project that didn't use the asset pipeline and that already uses this path or you wish to use this path for a new resource. ### X-Sendfile Headers diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 43d3b20165..dd59e2a8df 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -5,9 +5,9 @@ This guide covers the association features of Active Record. After reading this guide, you will know: -* Declare associations between Active Record models. -* Understand the various types of Active Record associations. -* Use the methods added to your models by creating associations. +* How to declare associations between Active Record models. +* How to understand the various types of Active Record associations. +* How to use the methods added to your models by creating associations. -------------------------------------------------------------------------------- @@ -94,6 +94,25 @@ end NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `customer` association in the `Order` model, you would be told that there was an "uninitialized constant Order::Customers". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too. +The corresponding migration might look like this: + +```ruby +class CreateOrders < ActiveRecord::Migration + def change + create_table :customers do |t| + t.string :name + t.timestamps + end + + create_table :orders do |t| + t.belongs_to :customer + t.datetime :order_date + t.timestamps + end + end +end +``` + ### The `has_one` Association A `has_one` association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model. For example, if each supplier in your application has only one account, you'd declare the supplier model like this: @@ -106,6 +125,25 @@ end  +The corresponding migration might look like this: + +```ruby +class CreateSuppliers < ActiveRecord::Migration + def change + create_table :suppliers do |t| + t.string :name + t.timestamps + end + + create_table :accounts do |t| + t.belongs_to :supplier + t.string :account_number + t.timestamps + end + end +end +``` + ### The `has_many` Association A `has_many` association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a `belongs_to` association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this: @@ -120,6 +158,25 @@ NOTE: The name of the other model is pluralized when declaring a `has_many` asso  +The corresponding migration might look like this: + +```ruby +class CreateCustomers < ActiveRecord::Migration + def change + create_table :customers do |t| + t.string :name + t.timestamps + end + + create_table :orders do |t| + t.belongs_to :customer + t.datetime :order_date + t.timestamps + end + end +end +``` + ### The `has_many :through` Association A `has_many :through` association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding _through_ a third model. For example, consider a medical practice where patients make appointments to see physicians. The relevant association declarations could look like this: @@ -143,6 +200,31 @@ end  +The corresponding migration might look like this: + +```ruby +class CreateAppointments < ActiveRecord::Migration + def change + create_table :physicians do |t| + t.string :name + t.timestamps + end + + create_table :patients do |t| + t.string :name + t.timestamps + end + + create_table :appointments do |t| + t.belongs_to :physician + t.belongs_to :patient + t.datetime :appointment_date + t.timestamps + end + end +end +``` + The collection of join models can be managed via the API. For example, if you assign ```ruby @@ -199,6 +281,31 @@ end  +The corresponding migration might look like this: + +```ruby +class CreateAccountHistories < ActiveRecord::Migration + def change + create_table :suppliers do |t| + t.string :name + t.timestamps + end + + create_table :accounts do |t| + t.belongs_to :supplier + t.string :account_number + t.timestamps + end + + create_table :account_histories do |t| + t.belongs_to :account + t.integer :credit_rating + t.timestamps + end + end +end +``` + ### The `has_and_belongs_to_many` Association A `has_and_belongs_to_many` association creates a direct many-to-many connection with another model, with no intervening model. For example, if your application includes assemblies and parts, with each assembly having many parts and each part appearing in many assemblies, you could declare the models this way: @@ -215,6 +322,29 @@ end  +The corresponding migration might look like this: + +```ruby +class CreateAssembliesAndParts < ActiveRecord::Migration + def change + create_table :assemblies do |t| + t.string :name + t.timestamps + end + + create_table :parts do |t| + t.string :part_number + t.timestamps + end + + create_table :assemblies_parts do |t| + t.belongs_to :assembly + t.belongs_to :part + end + end +end +``` + ### Choosing Between `belongs_to` and `has_one` If you want to set up a one-to-one relationship between two models, you'll need to add `belongs_to` to one, and `has_one` to the other. How do you know which is which? @@ -452,7 +582,7 @@ class CreateAssemblyPartJoinTable < ActiveRecord::Migration end ``` -We pass `id: false` to `create_table` because that table does not represent a model. That's required for the association to work properly. If you observe any strange behavior in a `has_and_belongs_to_many` association like mangled models IDs, or exceptions about conflicting IDs chances are you forgot that bit. +We pass `id: false` to `create_table` because that table does not represent a model. That's required for the association to work properly. If you observe any strange behavior in a `has_and_belongs_to_many` association like mangled models IDs, or exceptions about conflicting IDs, chances are you forgot that bit. ### Controlling Association Scope diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index e737dcab83..773102400a 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -104,7 +104,7 @@ Let's say you only wanted authenticated users to call actions on `ProductsContro ```ruby class ProductsController < ActionController - before_filter :authenticate + before_action :authenticate caches_action :index def index diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 0a4a704cd9..746226fa96 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -5,11 +5,11 @@ Rails comes with every command line tool you'll need to After reading this guide, you will know: -* Create a Rails application. -* Generate models, controllers, database migrations, and unit tests. -* Start a development server. -* Experiment with objects through an interactive shell. -* Profile and benchmark your new creation. +* How to create a Rails application. +* How to generate models, controllers, database migrations, and unit tests. +* How to start a development server. +* How to experiment with objects through an interactive shell. +* How to profile and benchmark your new creation. -------------------------------------------------------------------------------- diff --git a/guides/source/configuring.md b/guides/source/configuring.md index dba2be3b71..a8e33a2956 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -5,8 +5,8 @@ This guide covers the configuration and initialization features available to Rai After reading this guide, you will know: -* Adjust the behavior of your Rails applications. -* Add additional code to be run at application start time. +* How to adjust the behavior of your Rails applications. +* How to add additional code to be run at application start time. -------------------------------------------------------------------------------- @@ -135,8 +135,6 @@ These configuration methods are to be called on a `Rails::Railtie` object, such ### Configuring Assets -Rails 3.1 and up, by default, is set up to use the `sprockets` gem to manage assets within an application. This gem concatenates and compresses assets in order to make serving them much less painful. - * `config.assets.enabled` a flag that controls whether the asset pipeline is enabled. It is explicitly initialized in `config/application.rb`. * `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/production.rb`. @@ -165,7 +163,7 @@ Rails 3.1 and up, by default, is set up to use the `sprockets` gem to manage ass ### Configuring Generators -Rails 3 allows you to alter what generators are used with the `config.generators` method. This method takes a block: +Rails allows you to alter what generators are used with the `config.generators` method. This method takes a block: ```ruby config.generators do |g| @@ -278,6 +276,8 @@ config.middleware.delete ActionDispatch::BestStandardsSupport * `config.active_record.auto_explain_threshold_in_seconds` configures the threshold for automatic EXPLAINs (`nil` disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode. +* +config.active_record.cache_timestamp_format+ controls the format of the timestamp value in the cache key. Default is +:number+. + The MySQL adapter adds one additional configuration option: * `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index fa6c335088..ce2a5a4902 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -5,11 +5,11 @@ This guide covers ways in which _you_ can become a part of the ongoing developme After reading this guide, you will know: -* Using GitHub to report issues. -* Cloning master and running the test suite. -* Helping to resolve existing issues. -* Contributing to the Ruby on Rails documentation. -* Contributing to the Ruby on Rails code. +* How to use GitHub to report issues. +* How to clone master and run the test suite. +* How to help resolve existing issues. +* How to contribute to the Ruby on Rails documentation. +* How to contribute to the Ruby on Rails code. Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation — all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. @@ -91,7 +91,7 @@ You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` als The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings. -As of this writing (December, 2010) they are specially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag: +As of this writing (December, 2010) they are especially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag: ```bash $ RUBYOPT=-W0 bundle exec rake test @@ -205,7 +205,7 @@ TIP: Changes that are cosmetic in nature and do not add anything substantial to ### Follow the Coding Conventions -Rails follows a simple set of coding style conventions. +Rails follows a simple set of coding style conventions: * Two spaces, no tabs (for indentation). * No trailing whitespace. Blank lines should not have any spaces. diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 96112da50f..addc3a63a8 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -5,10 +5,10 @@ This guide introduces techniques for debugging Ruby on Rails applications. After reading this guide, you will know: -* Understand the purpose of debugging. -* Track down problems and issues in your application that your tests aren't identifying. -* Learn the different ways of debugging. -* Analyze the stack trace. +* The purpose of debugging. +* How to track down problems and issues in your application that your tests aren't identifying. +* The different ways of debugging. +* How to analyze the stack trace. -------------------------------------------------------------------------------- diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 79d59859c8..db43d62fcf 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -145,6 +145,9 @@ We need first to delete `.bundle/config` because Bundler remembers in that file In order to be able to run the test suite against MySQL you need to create a user named `rails` with privileges on the test databases: ```bash +$ mysql -uroot -p + +mysql> CREATE USER 'rails'@'localhost'; mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost'; mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index d7c648681d..e779407fab 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -9,6 +9,10 @@ name: Models documents: - + name: Active Record Basics + url: active_record_basics.html + description: This guide will get you started with models, persistence to database and the Active Record pattern and library. + - name: Rails Database Migrations url: migrations.html description: This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner. diff --git a/guides/source/engines.md b/guides/source/engines.md index f1e2780eae..116a7e67cd 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -35,7 +35,7 @@ Finally, engines would not have been possible without the work of James Adam, Pi Generating an engine -------------------- -To generate an engine with Rails 3.2, you will need to run the plugin generator and pass it options as appropriate to the need. For the "blorgh" example, you will need to create a "mountable" engine, running this command in a terminal: +To generate an engine, you will need to run the plugin generator and pass it options as appropriate to the need. For the "blorgh" example, you will need to create a "mountable" engine, running this command in a terminal: ```bash $ rails plugin new blorgh --mountable diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index b89d776d87..8ab44ea0bb 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -5,13 +5,13 @@ Forms in web applications are an essential interface for user input. However, fo After reading this guide, you will know: -* Create search forms and similar kind of generic forms not representing any specific model in your application. -* Make model-centric forms for creation and editing of specific database records. -* Generate select boxes from multiple types of data. -* Understand the date and time helpers Rails provides. -* Learn what makes a file upload form different. -* Learn some cases of building forms to external resources. -* Find out how to build complex forms. +* How to create search forms and similar kind of generic forms not representing any specific model in your application. +* How to make model-centric forms for creation and editing of specific database records. +* How to generate select boxes from multiple types of data. +* The date and time helpers Rails provides. +* What makes a file upload form different. +* Some cases of building forms to external resources. +* How to build complex forms. -------------------------------------------------------------------------------- @@ -458,7 +458,7 @@ As with other helpers, if you were to use the `select` helper on a form builder <%= f.select(:city_id, ...) %> ``` -WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of ` ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) ` when you pass the `params` hash to `Person.new` or `update_attributes`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. You may wish to consider the use of `attr_protected` and `attr_accessible`. For further details on this, see the [Ruby On Rails Security Guide](security.html#mass-assignment). +WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of ` ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) ` when you pass the `params` hash to `Person.new` or `update_attributes`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. ### Option Tags from a Collection of Arbitrary Objects @@ -594,8 +594,6 @@ The following two forms both upload a file. <% end %> ``` -NOTE: Since Rails 3.1, forms rendered using `form_for` have their encoding set to `multipart/form-data` automatically once a `file_field` is used inside the block. Previous versions required you to set this explicitly. - Rails provides the usual pair of helpers: the barebones `file_field_tag` and the model oriented `file_field`. The only difference with other helpers is that you cannot set a default value for file inputs as this would have no meaning. As you would expect in the first case the uploaded file is in `params[:picture]` and in the second case in `params[:person][:picture]`. ### What Gets Uploaded diff --git a/guides/source/generators.md b/guides/source/generators.md index f83f5c6691..62de5a70bb 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -5,18 +5,16 @@ Rails generators are an essential tool if you plan to improve your workflow. Wit After reading this guide, you will know: -* Learn how to see which generators are available in your application. -* Create a generator using templates. -* Learn how Rails searches for generators before invoking them. -* Customize your scaffold by creating new generators. -* Customize your scaffold by changing generator templates. -* Learn how to use fallbacks to avoid overwriting a huge set of generators. -* Learn how to create an application template. +* How to see which generators are available in your application. +* How to create a generator using templates. +* How Rails searches for generators before invoking them. +* How to customize your scaffold by creating new generators. +* How to customize your scaffold by changing generator templates. +* How to use fallbacks to avoid overwriting a huge set of generators. +* How to create an application template. -------------------------------------------------------------------------------- -NOTE: This guide is about generators in Rails 3, previous versions are not covered. - First Contact ------------- diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index b1ca8da292..02ec024e5b 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -5,7 +5,7 @@ This guide covers getting up and running with Ruby on Rails. After reading this guide, you will know: -* Installing Rails, creating a new Rails application, and connecting your +* How to install Rails, create a new Rails application, and connect your application to a database. * The general layout of a Rails application. * The basic principles of MVC (Model, View, Controller) and RESTful design. @@ -77,7 +77,7 @@ TIP: The examples below use # and $ to denote superuser and regular user termina Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose "Run" from your Start menu and type 'cmd.exe'. Any commands prefaced with a -dollar sign `$` should be run in the command line. Verify sure you have a +dollar sign `$` should be run in the command line. Verify that you have a current version of Ruby installed: ```bash @@ -101,11 +101,11 @@ To verify that you have everything installed correctly, you should be able to ru $ rails --version ``` -If it says something like "Rails 3.2.9" you are ready to continue. +If it says something like "Rails 3.2.9", you are ready to continue. ### Creating the Blog Application -Rails comes with a number of generators that are designed to make your development life easier. One of these is the new application generator, which will provide you with the foundation of a Rails application so that you don't have to write it yourself. +Rails comes with a number of scripts called generators that are designed to make your development life easier by creating everything that's necessary to start working on a particular task. One of these is the new application generator, which will provide you with the foundation of a fresh Rails application so that you don't have to write it yourself. To use this generator, open a terminal, navigate to a directory where you have rights to create files, and type: @@ -215,11 +215,7 @@ Open the `app/views/welcome/index.html.erb` file in your text editor and edit it ### Setting the Application Home Page -Now that we have made the controller and view, we need to tell Rails when we want Hello Rails! to show up. In our case, we want it to show up when we navigate to the root URL of our site, <http://localhost:3000>. At the moment, however, the "Welcome Aboard" smoke test is occupying that spot. - -To fix this, delete the `index.html` file located inside the `public` directory of the application. - -You need to do this because Rails will serve any static file in the `public` directory that matches a route in preference to any dynamic content you generate from the controllers. The `index.html` file is special: it will be served if a request comes in at the root route, e.g. <http://localhost:3000>. If another request such as <http://localhost:3000/welcome> happened, a static file at `public/welcome.html` would be served first, but only if it existed. +Now that we have made the controller and view, we need to tell Rails when we want Hello Rails! to show up. In our case, we want it to show up when we navigate to the root URL of our site, <http://localhost:3000>. At the moment, "Welcome Aboard" is occupying that spot. Next, you have to tell Rails where your actual home page is located. @@ -233,7 +229,6 @@ Blog::Application.routes.draw do # first created -> highest priority. # ... # You can have the root of your site routed with "root" - # just remember to delete public/index.html. # root to: "welcome#index" ``` @@ -558,7 +553,7 @@ parameter, which in our case will be the id of the post. Note that this time we had to specify the actual mapping, `posts#show` because otherwise Rails would not know which action to render. -As we did before, we need to add the `show` action in +As we did before, we need to add the `show` action in `app/controllers/posts_controller.rb` and its respective view. ```ruby diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 1131b7f245..2e61bea5ea 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -96,7 +96,7 @@ This means, that in the `:en` locale, the key _hello_ will map to the _Hello wor The I18n library will use **English** as a **default locale**, i.e. if you don't set a different locale, `:en` will be used for looking up translations. -NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Various [Rails I18n plugins](http://rails-i18n.org/wiki) such as [Globalize2](https://github.com/joshmh/globalize2/tree/master) may help you implement it. +NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Various [Rails I18n plugins](http://rails-i18n.org/wiki) such as [Globalize3](https://github.com/svenfuchs/globalize3) may help you implement it. The **translations load path** (`I18n.load_path`) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you. @@ -134,10 +134,10 @@ However, you would probably like to **provide support for more locales** in your WARNING: You may be tempted to store the chosen locale in a _session_ or a <em>cookie</em>, however **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [<em>RESTful</em>](http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below. -The _setting part_ is easy. You can set the locale in a `before_filter` in the `ApplicationController` like this: +The _setting part_ is easy. You can set the locale in a `before_action` in the `ApplicationController` like this: ```ruby -before_filter :set_locale +before_action :set_locale def set_locale I18n.locale = params[:locale] || I18n.default_locale @@ -160,7 +160,7 @@ One option you have is to set the locale from the domain name where your applica You can implement it like this in your `ApplicationController`: ```ruby -before_filter :set_locale +before_action :set_locale def set_locale I18n.locale = extract_locale_from_tld || I18n.default_locale @@ -203,7 +203,7 @@ This solution has aforementioned advantages, however, you may not be able or may ### Setting the Locale from the URL Params -The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.locale = params[:locale]` _before_filter_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case. +The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.locale = params[:locale]` _before_action_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case. This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the rest of the World Wide Web. It does require a little bit more work to implement, though. diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb index 71fe94a870..a8e4525c67 100644 --- a/guides/source/index.html.erb +++ b/guides/source/index.html.erb @@ -9,9 +9,7 @@ Ruby on Rails Guides <% content_for :index_section do %> <div id="subCol"> <dl> - <dd class="kindle">Rails Guides are also available for Kindle and <%= link_to 'Free Kindle Reading Apps', 'http://www.amazon.com/gp/kindle/kcp' %> for the iPad, -iPhone, Mac, Android, etc. Download them from <%= link_to 'here', @mobi %>. - </dd> + <dd class="kindle">Rails Guides are also available for <%= link_to 'Kindle', @mobi %>.</dd> <dd class="work-in-progress">Guides marked with this icon are currently being worked on and will not be available in the Guides Index menu. While still useful, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections.</dd> </dl> </div> diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 6a34125654..32df508f9c 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -6,7 +6,7 @@ as of Rails 4. It is an extremely in-depth guide and recommended for advanced Ra After reading this guide, you will know: -* Using `rails server`. +* How to use `rails server`. -------------------------------------------------------------------------------- diff --git a/guides/source/kindle/rails_guides.opf.erb b/guides/source/kindle/rails_guides.opf.erb index 4e07664fd0..547abcbc19 100644 --- a/guides/source/kindle/rails_guides.opf.erb +++ b/guides/source/kindle/rails_guides.opf.erb @@ -32,7 +32,7 @@ <item id="toc" media-type="application/x-dtbncx+xml" href="toc.ncx" /> - <item id="cover" media-type="image/jpeg" href="images/rails_guides_kindle_cover.jpg"/> + <item id="cover" media-type="image/jpg" href="images/rails_guides_kindle_cover.jpg"/> </manifest> <spine toc="toc"> diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 394ef794d3..dbaa3ec576 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -5,10 +5,10 @@ This guide covers the basic layout features of Action Controller and Action View After reading this guide, you will know: -* Use the various rendering methods built into Rails. -* Create layouts with multiple content sections. -* Use partials to DRY up your views. -* Use nested layouts (sub-templates). +* How to use the various rendering methods built into Rails. +* How to create layouts with multiple content sections. +* How to use partials to DRY up your views. +* How to use nested layouts (sub-templates). -------------------------------------------------------------------------------- @@ -160,21 +160,6 @@ def update end ``` -To be explicit, you can use `render` with the `:action` option (though this is no longer necessary in Rails 3.0): - -```ruby -def update - @book = Book.find(params[:id]) - if @book.update_attributes(params[:book]) - redirect_to(@book) - else - render action: "edit" - end -end -``` - -WARNING: Using `render` with `:action` is a frequent source of confusion for Rails newcomers. The specified action is used to determine which view to render, but Rails does _not_ run any of the code for that action in the controller. Any instance variables that you require in the view must be set up in the current action before calling `render`. - #### Rendering an Action's Template from Another Controller What if you want to render a template from an entirely different controller from the one that contains the action code? You can also do that with `render`, which accepts the full path (relative to `app/views`) of the template to render. For example, if you're running code in an `AdminProductsController` that lives in `app/controllers/admin`, you can render the results of an action to a template in `app/views/products` this way: @@ -674,7 +659,7 @@ There are three tag options available for the `auto_discovery_link_tag`: The `javascript_include_tag` helper returns an HTML `script` tag for each source provided. -If you are using Rails with the [Asset Pipeline](asset_pipeline.html) enabled, this helper will generate a link to `/assets/javascripts/` rather than `public/javascripts` which was used in earlier versions of Rails. This link is then served by the Sprockets gem, which was introduced in Rails 3.1. +If you are using Rails with the [Asset Pipeline](asset_pipeline.html) enabled, this helper will generate a link to `/assets/javascripts/` rather than `public/javascripts` which was used in earlier versions of Rails. This link is then served by the asset pipeline. A JavaScript file within a Rails application or Rails engine goes in one of three locations: `app/assets`, `lib/assets` or `vendor/assets`. These locations are explained in detail in the [Asset Organization section in the Asset Pipeline Guide](asset_pipeline.html#asset-organization) @@ -843,7 +828,7 @@ You can even use dynamic paths such as `cache/#{current_site}/main/display`. The `image_tag` helper builds an HTML `<img />` tag to the specified file. By default, files are loaded from `public/images`. -WARNING: Note that you must specify the extension of the image. Previous versions of Rails would allow you to just use the image name and would append `.png` if no extension was given but Rails 3.0 does not. +WARNING: Note that you must specify the extension of the image. ```erb <%= image_tag "header.png" %> @@ -1091,8 +1076,6 @@ Every partial also has a local variable with the same name as the partial (minus Within the `customer` partial, the `customer` variable will refer to `@new_customer` from the parent view. -WARNING: In previous versions of Rails, the default local variable would look for an instance variable with the same name as the partial in the parent. This behavior was deprecated in 2.3 and has been removed in Rails 3.0. - If you have an instance of a model to render into a partial, you can use a shorthand syntax: ```erb @@ -1120,7 +1103,7 @@ Partials are very useful in rendering collections. When you pass a collection to When a partial is called with a pluralized collection, then the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is `_product`, and within the `_product` partial, you can refer to `product` to get the instance that is being rendered. -In Rails 3.0, there is also a shorthand for this. Assuming `@products` is a collection of `product` instances, you can simply write this in the `index.html.erb` to produce the same result: +There is also a shorthand for this. Assuming `@products` is a collection of `product` instances, you can simply write this in the `index.html.erb` to produce the same result: ```html+erb <h1>Products</h1> diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 829cbf2873..9840e7694f 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -334,7 +334,7 @@ end removes the `description` and `name` columns, creates a `part_number` string column and adds an index on it. Finally it renames the `upccode` column. -### When Helpers Aren't Enough +### When Helpers aren't Enough If the helpers provided by Active Record aren't enough you can use the `execute` method to execute arbitrary SQL: @@ -585,8 +585,8 @@ Occasionally you will make a mistake when writing a migration. If you have already run the migration then you cannot just edit the migration and run the migration again: Rails thinks it has already run the migration and so will do nothing when you run `rake db:migrate`. You must rollback the migration (for -example with `rake db:rollback`), edit your migration and then run `rake -db:migrate` to run the corrected version. +example with `rake db:rollback`), edit your migration and then run +`rake db:migrate` to run the corrected version. In general, editing existing migrations is not a good idea. You will be creating extra work for yourself and your co-workers and cause major headaches diff --git a/guides/source/performance_testing.md b/guides/source/performance_testing.md index a07f64ec29..182f7eb0fd 100644 --- a/guides/source/performance_testing.md +++ b/guides/source/performance_testing.md @@ -6,12 +6,12 @@ application. After reading this guide, you will know: -* Understand the various types of benchmarking and profiling metrics. -* Generate performance and benchmarking tests. -* Install and use a GC-patched Ruby binary to measure memory usage and object +* The various types of benchmarking and profiling metrics. +* How to generate performance and benchmarking tests. +* How to install and use a GC-patched Ruby binary to measure memory usage and object allocation. -* Understand the benchmarking information provided by Rails inside the log files. -* Learn about various tools facilitating benchmarking and profiling. +* The benchmarking information provided by Rails inside the log files. +* Various tools facilitating benchmarking and profiling. Performance testing is an integral part of the development cycle. It is very important that you don't make your end users wait for too long before the page diff --git a/guides/source/plugins.md b/guides/source/plugins.md index c7c843ec3a..f8f04c3c67 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -9,8 +9,8 @@ A Rails plugin is either an extension or a modification of the core framework. P After reading this guide, you will know: -* Creating a plugin from scratch. -* Writing and running tests for the plugin. +* How to create a plugin from scratch. +* How to write and run tests for the plugin. This guide describes how to build a test-driven plugin that will: @@ -27,16 +27,13 @@ goodness. Setup ----- -_"vendored plugins"_ were available in previous versions of Rails, but they are deprecated in -Rails 3.2, and will not be available in the future. - Currently, Rails plugins are built as gems, _gemified plugins_. They can be shared across different rails applications using RubyGems and Bundler if desired. ### Generate a gemified plugin. -Rails 3.1 ships with a `rails plugin new` command which creates a +Rails ships with a `rails plugin new` command which creates a skeleton for developing any kind of Rails extension with the ability to run integration tests using a dummy Rails application. See usage and options by asking for help: diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index 83c563ac78..9e694acb98 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -5,8 +5,8 @@ Application templates are simple Ruby files containing DSL for adding gems/initi After reading this guide, you will know: -* Use templates to generate/customize Rails applications. -* Write your own reusable application templates using the Rails template API. +* How to use templates to generate/customize Rails applications. +* How to write your own reusable application templates using the Rails template API. -------------------------------------------------------------------------------- diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 531b35a973..1fac6d9883 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -5,10 +5,10 @@ This guide covers Rails integration with Rack and interfacing with other Rack co After reading this guide, you will know: -* Create Rails Metal applications. -* Use Rack Middlewares in your Rails applications. -* Understand Action Pack's internal Middleware stack. -* Define a custom Middleware stack. +* How to create Rails Metal applications. +* How to use Rack Middlewares in your Rails applications. +* Action Pack's internal Middleware stack. +* How to define a custom Middleware stack. -------------------------------------------------------------------------------- diff --git a/guides/source/routing.md b/guides/source/routing.md index 714c0b609e..14f23d4020 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -5,11 +5,11 @@ This guide covers the user-facing features of Rails routing. After reading this guide, you will know: -* Understand the code in `routes.rb`. -* Construct your own routes, using either the preferred resourceful style or the `match` method. -* Identify what parameters to expect an action to receive. -* Automatically create paths and URLs using route helpers. -* Use advanced techniques such as constraints and Rack endpoints. +* How to interpret the code in `routes.rb`. +* How to construct your own routes, using either the preferred resourceful style or the `match` method. +* What parameters to expect an action to receive. +* How to automatically create paths and URLs using route helpers. +* Advanced techniques such as constraints and Rack endpoints. -------------------------------------------------------------------------------- @@ -471,7 +471,7 @@ resources :photos do end ``` -This will recognize `/photos/1/preview` with GET, and route to the `preview` action of `PhotosController`. It will also create the `preview_photo_url` and `preview_photo_path` helpers. +This will recognize `/photos/1/preview` with GET, and route to the `preview` action of `PhotosController`, with the resource id value passed in `params[:id]`. It will also create the `preview_photo_url` and `preview_photo_path` helpers. Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use `get`, `patch`, `put`, `post`, or `delete` here. If you don't have multiple `member` routes, you can also pass `:on` to a route, eliminating the block: @@ -481,6 +481,8 @@ resources :photos do end ``` +You can leave out the `:on` option, this will create the same member route except that the resource id value will be available in `params[:photo_id]` instead of `params[:id]`. + #### Adding Collection Routes To add a route to the collection: @@ -733,12 +735,6 @@ get '*a/foo/*b', to: 'test#index' would match `zoo/woo/foo/bar/baz` with `params[:a]` equals `'zoo/woo'`, and `params[:b]` equals `'bar/baz'`. -NOTE: Starting from Rails 3.1, wildcard segments will always match the optional format segment by default. For example if you have this route: - -```ruby -get '*pages', to: 'pages#show' -``` - NOTE: By requesting `'/foo/bar.json'`, your `params[:pages]` will be equals to `'foo/bar'` with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `format: false` like this: ```ruby @@ -797,7 +793,7 @@ root to: 'pages#main' root 'pages#main' # shortcut for the above ``` -You should put the `root` route at the top of the file, because it is the most popular route and should be matched first. You also need to delete the `public/index.html` file for the root route to take effect. +You should put the `root` route at the top of the file, because it is the most popular route and should be matched first. NOTE: The `root` route only routes `GET` requests to the action. diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md index 2c3bc686ef..a78711f4b2 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -5,6 +5,9 @@ This guide documents guidelines for writing Ruby on Rails Guides. This guide fol After reading this guide, you will know: +* About the conventions to be used in Rails documentation. +* How to generate guides locally. + -------------------------------------------------------------------------------- Markdown diff --git a/guides/source/security.md b/guides/source/security.md index 6c32a8ff5b..8096ea2383 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -374,141 +374,6 @@ The common admin interface works like this: it's located at www.example.com/admi * _Put the admin interface to a special sub-domain_ such as admin.application.com and make it a separate application with its own user management. This makes stealing an admin cookie from the usual domain, www.application.com, impossible. This is because of the same origin policy in your browser: An injected (XSS) script on www.application.com may not read the cookie for admin.application.com and vice-versa. -Mass Assignment ---------------- - -WARNING: _Without any precautions `Model.new(params[:model]`) allows attackers to set -any database column's value._ - -The mass-assignment feature may become a problem, as it allows an attacker to set -any model's attributes by manipulating the hash passed to a model's `new()` method: - -```ruby -def signup - params[:user] # => {name:"ow3ned", admin:true} - @user = User.new(params[:user]) -end -``` - -Mass-assignment saves you much work, because you don't have to set each value -individually. Simply pass a hash to the `new` method, or `assign_attributes=` -a hash value, to set the model's attributes to the values in the hash. The -problem is that it is often used in conjunction with the parameters (params) -hash available in the controller, which may be manipulated by an attacker. -He may do so by changing the URL like this: - -``` -http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1 -``` - -This will set the following parameters in the controller: - -```ruby -params[:user] # => {name:"ow3ned", admin:true} -``` - -So if you create a new user using mass-assignment, it may be too easy to become -an administrator. - -Note that this vulnerability is not restricted to database columns. Any setter -method, unless explicitly protected, is accessible via the `attributes=` method. -In fact, this vulnerability is extended even further with the introduction of -nested mass assignment (and nested object forms) in Rails 2.3. The -`accepts_nested_attributes_for` declaration provides us the ability to extend -mass assignment to model associations (`has_many`, `has_one`, -`has_and_belongs_to_many`). For example: - -```ruby - class Person < ActiveRecord::Base - has_many :children - - accepts_nested_attributes_for :children - end - - class Child < ActiveRecord::Base - belongs_to :person - end -``` - -As a result, the vulnerability is extended beyond simply exposing column -assignment, allowing attackers the ability to create entirely new records -in referenced tables (children in this case). - -### Countermeasures - -To avoid this, Rails provides an interface for protecting attributes from -end-user assignment called Strong Parameters. This makes Action Controller -parameters forbidden until they have been whitelisted, so you will have to -make a conscious choice about which attributes to allow for mass assignment -and thus prevent accidentally exposing that which shouldn’t be exposed. - -NOTE. Before Strong Parameters arrived, mass-assignment protection was a -model's task provided by Active Model. This has been extracted to the -[ProtectedAttributes](https://github.com/rails/protected_attributes) -gem. In order to use `attr_accessible` and `attr_protected` helpers in -your models, you should add `protected_attributes` to your Gemfile. - -Why we moved mass-assignment protection out of the model and into -the controller? The whole point of the controller is to control the -flow between user and application, including authentication, authorization, -and, as part of that, access control. - -Strong Parameters provides two methods to the `params` hash to control -access to your attributes: `require` and `permit`. The former is used -to mark parameters as required and the latter limits which attributes -should be allowed for mass updating using the slice pattern. For example: - -```ruby -def signup - params[:user] - # => {name:"ow3ned", admin:true} - permitted_params = params.require(:user).permit(:name) - # => {name:"ow3ned"} - - @user = User.new(permitted_params) -end -``` - -In the example above, `require` is checking whether a `user` key is present or not -in the parameters, if it's not present, it'll raise an `ActionController::MissingParameter` -exception, which will be caught by `ActionController::Base` and turned into a -400 Bad Request reply. Then `permit` whitelists the attributes that should be -allowed for mass assignment. - -A good pattern to encapsulate the permissible parameters is to use a private method -since you'll be able to reuse the same permit list between different actions. - -```ruby -def signup - @user = User.new(user_params) - # ... -end - -def update - @user = User.find(params[:id] - @user.update_attributes!(user_params) - # ... -end - -private - def user_params - params.require(:user).permit(:name) - end -``` - -Also, you can specialize this method with per-user checking of permissible -attributes. - -```ruby -def user_params - if current_user.admin? - params.require(:user).permit(:name, :admin) - else - params.require(:user).permit(:name) - end -end -``` - User Management --------------- @@ -688,8 +553,7 @@ NOTE: _When sanitizing, protecting or verifying something, whitelists over black A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _prefer to use whitelist approaches_: -* Use before_filter only: [...] instead of except: [...]. This way you don't forget to turn it off for newly added actions. -* Use attr_accessible instead of attr_protected. See the mass-assignment section for details +* Use before_action only: [...] instead of except: [...]. This way you don't forget to turn it off for newly added actions. * Allow <strong> instead of removing <script> against Cross-Site Scripting (XSS). See below for details. * Don't try to correct user input by blacklists: * This will make the attack work: "<sc<script>ript>".gsub("<script>", "") diff --git a/guides/source/testing.md b/guides/source/testing.md index 9ce94f4c53..a9bce04522 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -6,9 +6,9 @@ application. After reading this guide, you will know: -* Understand Rails testing terminology. -* Write unit, functional, and integration tests for your application. -* Identify other popular testing approaches and plugins. +* Rails testing terminology. +* How to write unit, functional, and integration tests for your application. +* Other popular testing approaches and plugins. -------------------------------------------------------------------------------- diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index bdb4730cdc..b4a59fe3da 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -3,10 +3,6 @@ A Guide for Upgrading Ruby on Rails This guide provides steps to be followed when you upgrade your applications to a newer version of Ruby on Rails. These steps are also available in individual release guides. -After reading this guide, you will know: - --------------------------------------------------------------------------------- - General Advice -------------- @@ -31,7 +27,7 @@ Upgrading from Rails 3.2 to Rails 4.0 NOTE: This section is a work in progress. -If your application is currently on any version of Rails older than 3.2.x, you should upgrade to Rails 3.2 before attempting an update to Rails 4.0. +If your application is currently on any version of Rails older than 3.2.x, you should upgrade to Rails 3.2 before attempting one to Rails 4.0. The following changes are meant for upgrading your application to Rails 4.0. @@ -39,28 +35,21 @@ The following changes are meant for upgrading your application to Rails 4.0. Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must replace any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`. -### Identity Map - -Rails 4.0 has removed the identity map from Active Record, due to [some inconsistencies with associations](https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6). If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: `config.active_record.identity_map`. - ### Active Record -The `delete` method in collection associations can now receive `Fixnum` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them. +* Rails 4.0 has removed the identity map from Active Record, due to [some inconsistencies with associations](https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6). If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: `config.active_record.identity_map`. + +* The `delete` method in collection associations can now receive `Fixnum` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them. -Rails 4.0 has changed how orders get stacked in `ActiveRecord::Relation`. In previous versions of rails new order was applied after previous defined order. But this is no long true. Check [Active Record Query guide](active_record_querying.html#ordering) for more information. +* Rails 4.0 has changed how orders get stacked in `ActiveRecord::Relation`. In previous versions of Rails, the new order was applied after the previously defined order. But this is no longer true. Check [Active Record Query guide](active_record_querying.html#ordering) for more information. -Rails 4.0 has changed `serialized_attributes` and `attr_readonly` to class methods only. Now you shouldn't use instance methods, it's deprecated. You must change them, e.g. `self.serialized_attributes` to `self.class.serialized_attributes`. +* Rails 4.0 has changed `serialized_attributes` and `attr_readonly` to class methods only. Now you shouldn't use instance methods, it's deprecated. You must change them, e.g. `self.serialized_attributes` to `self.class.serialized_attributes`. ### Active Model -Rails 4.0 has changed how errors attach with the `ActiveModel::Validations::ConfirmationValidator`. -Now when confirmation validations fail the error will be attached to -`:#{attribute}_confirmation` instead of `attribute`. +* Rails 4.0 has changed how errors attach with the `ActiveModel::Validations::ConfirmationValidator`. Now when confirmation validations fail the error will be attached to `:#{attribute}_confirmation` instead of `attribute`. -Rails 4.0 has changed `ActiveModel::Serializers::JSON.include_root_in_json` default -value to `false`. Now, Active Model Serializers and Active Record objects have the -same default behaviour. This means that you can comment or remove the following option -in the `config/initializers/wrap_parameters.rb` file: +* Rails 4.0 has changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to `false`. Now, Active Model Serializers and Active Record objects have the same default behaviour. This means that you can comment or remove the following option in the `config/initializers/wrap_parameters.rb` file: ```ruby # Disable root element in JSON by default. @@ -71,20 +60,17 @@ in the `config/initializers/wrap_parameters.rb` file: ### Action Pack -There is an upgrading cookie store `UpgradeSignatureToEncryptionCookieStore` which helps you upgrading apps that use `CookieStore` to the new default `EncryptedCookieStore`. To use this CookieStore set `Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'` in `config/initializers/session_store.rb`. Additionally, add `Myapp::Application.config.secret_key_base = 'some secret'` in config/initializers/secret_token.rb (use `rake secret` to generate a value). Do not remove `Myapp::Application.config.secret_token = 'some secret'`. +* There is an upgrading cookie store `UpgradeSignatureToEncryptionCookieStore` which helps you upgrading apps that use `CookieStore` to the new default `EncryptedCookieStore`. To use this `CookieStore` set `Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'` in `config/initializers/session_store.rb`. Additionally, add `Myapp::Application.config.secret_key_base = 'some secret'` in `config/initializers/secret_token.rb`. Do not remove `Myapp::Application.config.secret_token = 'some secret'`. -Rails 4.0 removed the `ActionController::Base.asset_path` option. Use the assets pipeline feature. +* Rails 4.0 removed the `ActionController::Base.asset_path` option. Use the assets pipeline feature. -Rails 4.0 has deprecated `ActionController::Base.page_cache_extension` option. Use -`ActionController::Base.default_static_extension` instead. +* Rails 4.0 has deprecated `ActionController::Base.page_cache_extension` option. Use `ActionController::Base.default_static_extension` instead. -Rails 4.0 has removed Action and Page caching from Action Pack. You will need to -add the `actionpack-action_caching` gem in order to use `caches_action` and -the `actionpack-page_caching` to use `caches_pages` in your controllers. +* Rails 4.0 has removed Action and Page caching from Action Pack. You will need to add the `actionpack-action_caching` gem in order to use `caches_action` and the `actionpack-page_caching` to use `caches_pages` in your controllers. -Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`. +* Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`. -Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, for example: +* Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, for example: ```ruby get Rack::Utils.escape('こんにちは'), controller: 'welcome', action: 'index' @@ -98,11 +84,11 @@ get 'こんにちは', controller: 'welcome', action: 'index' ### Active Support -Rails 4.0 Removed the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`. +Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`. ### Helpers Loading Order -The loading order of helpers from more than one directory has changed in Rails 4.0. Previously, helpers from all directories were gathered and then sorted alphabetically. After upgrade to Rails 4.0 helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use `helpers_path` parameter, this change will only impact the way of loading helpers from engines. If you rely on the fact that particular helper from engine loads before or after another helper from application or another engine, you should check if correct methods are available after upgrade. If you would like to change order in which engines are loaded, you can use `config.railties_order=` method. +The order in which helpers from more than one directory are loaded has changed in Rails 4.0. Previously, they were gathered and then sorted alphabetically. After upgrading to Rails 4.0, helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use the `helpers_path` parameter, this change will only impact the way of loading helpers from engines. If you rely on the ordering, you should check if correct methods are available after upgrade. If you would like to change the order in which engines are loaded, you can use `config.railties_order=` method. Upgrading from Rails 3.1 to Rails 3.2 ------------------------------------- diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 26e3403ff9..0f5acfba3c 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -7,10 +7,10 @@ ease! After reading this guide, you will know: -* Quick introduction to Ajax. +* The basics of Ajax. * Unobtrusive JavaScript. * How Rails' built-in helpers assist you. -* Handling Ajax on the server side. +* How to handle Ajax on the server side. * The Turbolinks gem. ------------------------------------------------------------------------------- diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 01dd86c23e..ed5dfc6446 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,9 +1,14 @@ ## Rails 4.0.0 (unreleased) ## -* Add ENV['RACK_ENV'] support to `rails runner/console/server`. +* 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. @@ -11,21 +16,21 @@ *Yves Senn* -* Add sqlserver.yml template file to satisfy '-d sqlserver' being passed to 'rails new'. +* Add `sqlserver.yml` template file to satisfy `-d sqlserver` being passed to `rails new`. Fix #6882 - *Robert Nesius* + *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. +* Add dummy app Rake tasks when `--skip-test-unit` and `--dummy-path` is passed to the plugin generator. Fix #8121 *Yves Senn* -* Ensure that RAILS_ENV is set when accessing Rails.env *Steve Klabnik* +* Ensure that `RAILS_ENV` is set when accessing Rails.env *Steve Klabnik* -* Don't eager-load app/assets and app/views *Elia Schito* +* Don't eager-load `app/assets` and `app/views` *Elia Schito* * Add `.rake` to list of file extensions included by `rake notes` and `rake notes:custom`. *Brent J. Nordquist* diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index 6bf2d8db20..2797205334 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -21,16 +21,11 @@ end module Rails autoload :Info, 'rails/info' - autoload :InfoController, 'rails/info_controller' + autoload :InfoController, 'rails/info_controller' + autoload :WelcomeController, 'rails/welcome_controller' class << self - def application - @application ||= nil - end - - def application=(application) - @application = application - end + attr_accessor :application, :cache, :logger # The Configuration instance used to configure the Rails environment def configuration @@ -64,14 +59,6 @@ module Rails application.initialized? end - def logger - @logger ||= nil - end - - def logger=(logger) - @logger = logger - end - def backtrace_cleaner @backtrace_cleaner ||= begin # Relies on Active Support, so we have to lazy load to postpone definition until AS has been loaded @@ -95,14 +82,6 @@ module Rails @_env = ActiveSupport::StringInquirer.new(environment) end - def cache - @cache ||= nil - end - - def cache=(cache) - @cache = cache - end - # Returns all rails groups for loading based on: # # * The Rails environment; diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 2d87b8594a..09902ad597 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -25,6 +25,7 @@ module Rails get '/rails/info/properties' => "rails/info#properties" get '/rails/info/routes' => "rails/info#routes" get '/rails/info' => "rails/info#index" + get '/' => "rails/welcome#index" end end end diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index 1aed2796c1..039360fcf6 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -1,6 +1,12 @@ class CodeStatistics #:nodoc: - TEST_TYPES = %w(Units Functionals Unit\ tests Functional\ tests Integration\ tests) + TEST_TYPES = ['Controller tests', + 'Helper tests', + 'Model tests', + 'Mailer tests', + 'Integration tests', + 'Functional tests (old)', + 'Unit tests (old)'] def initialize(*pairs) @pairs = pairs diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index de3127f43e..77db881b65 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -52,9 +52,6 @@ module Rails class_option :skip_javascript, type: :boolean, aliases: '-J', default: false, desc: 'Skip JavaScript files' - class_option :skip_index_html, type: :boolean, aliases: '-I', default: false, - desc: 'Skip public/index.html and app/assets/images/rails.png files' - class_option :dev, type: :boolean, default: false, desc: "Setup the #{name} with Gemfile pointing to your Rails checkout" diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index d8a4f15b4b..4ae8756ed0 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -99,13 +99,17 @@ module Rails end def index_name - @index_name ||= if reference? - polymorphic? ? %w(id type).map { |t| "#{name}_#{t}" } : "#{name}_id" + @index_name ||= if polymorphic? + %w(id type).map { |t| "#{name}_#{t}" } else - name + column_name end end + def column_name + @column_name ||= reference? ? "#{name}_id" : name + end + def foreign_key? !!(name =~ /_id$/) end diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index cc10fd9177..9965db98de 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -160,6 +160,13 @@ module Rails end end + def attributes_names + @attributes_names ||= attributes.each_with_object([]) do |a, names| + names << a.column_name + names << "#{a.name}_type" if a.polymorphic? + end + end + def pluralize_table_names? !defined?(ActiveRecord::Base) || ActiveRecord::Base.pluralize_table_names end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 18637451ac..c98f021cfe 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -97,11 +97,6 @@ module Rails def public_directory directory "public", "public", recursive: false - if options[:skip_index_html] - remove_file "public/index.html" - remove_file 'app/assets/images/rails.png' - keep_file 'app/assets/images' - end end def script diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 5b7a653a09..c4846b2c11 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -21,5 +21,7 @@ source 'https://rubygems.org' # Deploy with Capistrano # gem 'capistrano', group: :development +<% unless defined?(JRUBY_VERSION) -%> # To use debugger # gem 'debugger' +<% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index 631543c705..22a6aeb5fe 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -2,7 +2,7 @@ # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". - # You can have the root of your site routed with "root" just remember to delete public/index.html. + # You can have the root of your site routed with "root" # root to: 'welcome#index' # Example of regular route: diff --git a/railties/lib/rails/generators/rails/migration/USAGE b/railties/lib/rails/generators/rails/migration/USAGE index af74963b01..f340ed97f2 100644 --- a/railties/lib/rails/generators/rails/migration/USAGE +++ b/railties/lib/rails/generators/rails/migration/USAGE @@ -15,15 +15,8 @@ Example: `rails generate migration AddTitleBodyToPost title:string body:text published:boolean` - This will create the AddTitleBodyToPost in db/migrate/20080514090912_add_title_body_to_post.rb with - this in the Up migration: + This will create the AddTitleBodyToPost in db/migrate/20080514090912_add_title_body_to_post.rb with this in the Change migration: add_column :posts, :title, :string add_column :posts, :body, :text add_column :posts, :published, :boolean - - And this in the Down migration: - - remove_column :posts, :published - remove_column :posts, :body - remove_column :posts, :title diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index d6bce40b0c..4d08b01e60 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -4,6 +4,8 @@ require_dependency "<%= namespaced_file_path %>/application_controller" <% end -%> <% module_namespacing do -%> class <%= controller_class_name %>Controller < ApplicationController + before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy] + # GET <%= route_url %> # GET <%= route_url %>.json def index @@ -18,8 +20,6 @@ class <%= controller_class_name %>Controller < ApplicationController # GET <%= route_url %>/1 # GET <%= route_url %>/1.json def show - @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> - respond_to do |format| format.html # show.html.erb format.json { render json: <%= "@#{singular_table_name}" %> } @@ -39,7 +39,6 @@ class <%= controller_class_name %>Controller < ApplicationController # GET <%= route_url %>/1/edit def edit - @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> end # POST <%= route_url %> @@ -61,8 +60,6 @@ class <%= controller_class_name %>Controller < ApplicationController # PATCH/PUT <%= route_url %>/1 # PATCH/PUT <%= route_url %>/1.json def update - @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> - respond_to do |format| if @<%= orm_instance.update_attributes("#{singular_table_name}_params") %> format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> } @@ -77,7 +74,6 @@ class <%= controller_class_name %>Controller < ApplicationController # DELETE <%= route_url %>/1 # DELETE <%= route_url %>/1.json def destroy - @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> @<%= orm_instance.destroy %> respond_to do |format| @@ -87,14 +83,18 @@ class <%= controller_class_name %>Controller < ApplicationController end private + # Use callbacks to share common setup or constraints between actions. + def set_<%= singular_table_name %> + @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> + end # Use this method to whitelist the permissible parameters. Example: params.require(:person).permit(:name, :age) # Also, you can specialize this method with per-user checking of permissible attributes. def <%= "#{singular_table_name}_params" %> - <%- if attributes.empty? -%> + <%- if attributes_names.empty? -%> params[<%= ":#{singular_table_name}" %>] <%- else -%> - params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes.map {|a| ":#{a.name}" }.join(', ') %>) + params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>) <%- end -%> end end diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb index 24308dcf6c..85a8914ccc 100644 --- a/railties/lib/rails/generators/test_case.rb +++ b/railties/lib/rails/generators/test_case.rb @@ -163,8 +163,8 @@ module Rails # end # end def assert_instance_method(method, content) - assert content =~ /def #{method}(\(.+\))?(.*?)\n end/m, "Expected to have method #{method}" - yield $2.strip if block_given? + assert content =~ /(\s+)def #{method}(\(.+\))?(.*?)\n\1end/m, "Expected to have method #{method}" + yield $3.strip if block_given? end alias :assert_method :assert_instance_method diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml index 5c8780aa64..7625ff975c 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml +++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml @@ -3,12 +3,18 @@ <% unless attributes.empty? -%> one: <% attributes.each do |attribute| -%> - <%= attribute.name %>: <%= attribute.default %> + <%= attribute.column_name %>: <%= attribute.default %> + <%- if attribute.polymorphic? -%> + <%= "#{attribute.name}_type: #{attribute.human_name}" %> + <%- end -%> <% end -%> two: <% attributes.each do |attribute| -%> - <%= attribute.name %>: <%= attribute.default %> + <%= attribute.column_name %>: <%= attribute.default %> + <%- if attribute.polymorphic? -%> + <%= "#{attribute.name}_type: #{attribute.human_name}" %> + <%- end -%> <% end -%> <% else -%> # This model initially had no columns defined. If you add columns to the diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index 3b4fec2e83..8f3ecaadea 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -18,17 +18,12 @@ module TestUnit # :nodoc: private def attributes_hash - return if accessible_attributes.empty? + return if attributes_names.empty? - accessible_attributes.map do |a| - name = a.name + attributes_names.map do |name| "#{name}: @#{singular_table_name}.#{name}" end.sort.join(', ') end - - def accessible_attributes - attributes.reject(&:reference?) - end end end end diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index e94c6a2030..e296637f39 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -1,7 +1,7 @@ require 'action_dispatch/routing/inspector' -class Rails::InfoController < ActionController::Base - self.view_paths = File.join(File.dirname(__FILE__), 'templates') +class Rails::InfoController < ActionController::Base # :nodoc: + self.view_paths = File.expand_path('../templates', __FILE__) layout 'application' before_filter :require_local! diff --git a/railties/lib/rails/generators/rails/app/templates/public/index.html b/railties/lib/rails/templates/rails/welcome/index.html.erb index dd09a96de9..9a62d206dc 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/index.html +++ b/railties/lib/rails/templates/rails/welcome/index.html.erb @@ -223,7 +223,8 @@ </li> <li> - <h2>Set up a default route and remove <span class="filename">public/index.html</span></h2> + <h2>Set up a root route to replace this page</h2> + <p>You're seeing this page because you're running in development mode and you haven't set a root route yet.</p> <p>Routes are set up in <span class="filename">config/routes.rb</span>.</p> </li> diff --git a/railties/lib/rails/welcome_controller.rb b/railties/lib/rails/welcome_controller.rb new file mode 100644 index 0000000000..45b764fa6b --- /dev/null +++ b/railties/lib/rails/welcome_controller.rb @@ -0,0 +1,7 @@ +class Rails::WelcomeController < ActionController::Base # :nodoc: + self.view_paths = File.expand_path('../templates', __FILE__) + layout nil + + def index + end +end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index 2ea1d2aff4..ecd5e03978 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -7,7 +7,6 @@ require 'minitest/autorun' require 'fileutils' require 'active_support' - require 'action_controller' require 'rails/all' diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 03798d572a..ccb47663d4 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -55,8 +55,8 @@ module ApplicationTests def db_migrate_and_status Dir.chdir(app_path) do - `rails generate model book title:string` - `bundle exec rake db:migrate` + `rails generate model book title:string; + bundle exec rake db:migrate` output = `bundle exec rake db:migrate:status` assert_match(/database:\s+\S+#{expected[:database]}/, output) assert_match(/up\s+\d{14}\s+Create books/, output) @@ -78,9 +78,8 @@ module ApplicationTests def db_schema_dump Dir.chdir(app_path) do - `rails generate model book title:string` - `rake db:migrate` - `rake db:schema:dump` + `rails generate model book title:string; + rake db:migrate db:schema:dump` schema_dump = File.read("db/schema.rb") assert_match(/create_table \"books\"/, schema_dump) end @@ -97,9 +96,8 @@ module ApplicationTests def db_fixtures_load Dir.chdir(app_path) do - `rails generate model book title:string` - `bundle exec rake db:migrate` - `bundle exec rake db:fixtures:load` + `rails generate model book title:string; + bundle exec rake db:migrate db:fixtures:load` assert_match(/#{expected[:database]}/, ActiveRecord::Base.connection_config[:database]) require "#{app_path}/app/models/book" @@ -122,13 +120,11 @@ module ApplicationTests def db_structure_dump_and_load Dir.chdir(app_path) do - `rails generate model book title:string` - `bundle exec rake db:migrate` - `bundle exec rake db:structure:dump` + `rails generate model book title:string; + bundle exec rake db:migrate db:structure:dump` structure_dump = File.read("db/structure.sql") assert_match(/CREATE TABLE \"books\"/, structure_dump) - `bundle exec rake db:drop` - `bundle exec rake db:structure:load` + `bundle exec rake db:drop db:structure:load` assert_match(/#{expected[:database]}/, ActiveRecord::Base.connection_config[:database]) require "#{app_path}/app/models/book" @@ -152,10 +148,8 @@ module ApplicationTests def db_test_load_structure Dir.chdir(app_path) do - `rails generate model book title:string` - `bundle exec rake db:migrate` - `bundle exec rake db:structure:dump` - `bundle exec rake db:test:load_structure` + `rails generate model book title:string; + bundle exec rake db:migrate db:structure:dump db:test:load_structure` ActiveRecord::Base.configurations = Rails.application.config.database_configuration ActiveRecord::Base.establish_connection 'test' require "#{app_path}/app/models/book" @@ -178,4 +172,4 @@ module ApplicationTests end end end -end
\ No newline at end of file +end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index f2234ab111..a8275a2e76 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -195,6 +195,16 @@ module ApplicationTests assert_no_match(/Errors running/, output) end + def test_scaffold_with_references_columns_tests_pass_by_default + output = Dir.chdir(app_path) do + `rails generate scaffold LineItems product:references cart:belongs_to; + bundle exec rake db:migrate db:test:clone test` + end + + assert_match(/7 tests, 13 assertions, 0 failures, 0 errors/, output) + assert_no_match(/Errors running/, output) + end + def test_db_test_clone_when_using_sql_format add_to_config "config.active_record.schema_format = :sql" output = Dir.chdir(app_path) do diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index ffcdeac7f0..22de640236 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -15,6 +15,12 @@ module ApplicationTests teardown_app end + test "rails/welcome in development" do + app("development") + get "/" + assert_equal 200, last_response.status + end + test "rails/info/routes in development" do app("development") get "/rails/info/routes" @@ -27,6 +33,36 @@ module ApplicationTests assert_equal 200, last_response.status end + test "root takes precedence over internal welcome controller" do + app("development") + + get '/' + assert_match %r{<h1>Getting started</h1>} , last_response.body + + 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 + root to: "foo#index" + end + RUBY + + get '/' + assert_equal 'foo', last_response.body + end + + test "rails/welcome in production" do + app("production") + get "/" + assert_equal 404, last_response.status + end + test "rails/info/routes in production" do app("production") get "/rails/info/routes" @@ -241,6 +277,77 @@ module ApplicationTests end end + test 'routes are added and removed when reloading' do + app('development') + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ApplicationController + def index + render text: "bar" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + get 'foo', to: 'foo#index' + end + RUBY + + get '/foo' + assert_equal 'foo', last_response.body + assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + + get '/bar' + assert_equal 404, last_response.status + assert_raises NoMethodError do + assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + end + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + get 'foo', to: 'foo#index' + get 'bar', to: 'bar#index' + end + RUBY + + Rails.application.reload_routes! + + get '/foo' + assert_equal 'foo', last_response.body + assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + + get '/bar' + assert_equal 'bar', last_response.body + assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + get 'foo', to: 'foo#index' + end + RUBY + + Rails.application.reload_routes! + + get '/foo' + assert_equal 'foo', last_response.body + assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + + get '/bar' + assert_equal 404, last_response.status + assert_raises NoMethodError do + assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + end + end + test 'resource routing with irregular inflection' do app_file 'config/initializers/inflection.rb', <<-RUBY ActiveSupport::Inflector.inflections do |inflect| diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index be520f2534..f65b5e2f2d 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -1,8 +1,10 @@ require 'isolation/abstract_unit' +require 'env_helpers' module ApplicationTests class RunnerTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation + include EnvHelpers def setup build_app @@ -73,21 +75,15 @@ module ApplicationTests end def test_environment_with_rails_env - orig = ENV['RAILS_ENV'] - ENV['RAILS_ENV'] = "production" - assert_match "production", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.env"` } - ensure - ENV['RAILS_ENV'] = orig + with_rails_env "production" do + assert_match "production", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.env"` } + end end def test_environment_with_rack_env - rack, rails = ENV['RACK_ENV'], ENV['RAILS_ENV'] - ENV['RACK_ENV'] = "production" - ENV['RAILS_ENV'] = nil - assert_match "production", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.env"` } - ensure - ENV['RAILS_ENV'] = rails - ENV['RACK_ENV'] = rack + with_rack_env "production" do + assert_match "production", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.env"` } + end end end end diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb index 4062905c16..9e449856f4 100644 --- a/railties/test/commands/console_test.rb +++ b/railties/test/commands/console_test.rb @@ -1,14 +1,14 @@ require 'abstract_unit' +require 'env_helpers' require 'rails/commands/console' class Rails::ConsoleTest < ActiveSupport::TestCase + include EnvHelpers + class FakeConsole def self.start; end end - def setup - end - def test_sandbox_option console = Rails::Console.new(app, parse_arguments(["--sandbox"])) assert console.sandbox? @@ -85,7 +85,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase assert_match(/\sproduction\s/, output) end end - + def test_e_option start ['-e', 'special-production'] assert_match(/\sspecial-production\s/, output) @@ -133,20 +133,4 @@ class Rails::ConsoleTest < ActiveSupport::TestCase def parse_arguments(args) Rails::Console.parse_arguments(args) end - - def with_rails_env(env) - rails = ENV['RAILS_ENV'] - ENV['RAILS_ENV'] = env - yield - ensure - ENV['RAILS_ENV'] = rails - end - - def with_rack_env(env) - rack = ENV['RACK_ENV'] - ENV['RACK_ENV'] = env - with_rails_env(nil) { yield } - ensure - ENV['RACK_ENV'] = rack - end end diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index 6a75207ebb..cb57b3c0cd 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -1,7 +1,9 @@ require 'abstract_unit' +require 'env_helpers' require 'rails/commands/server' class Rails::ServerTest < ActiveSupport::TestCase + include EnvHelpers def test_environment_with_server_option args = ["thin", "-e", "production"] @@ -25,22 +27,16 @@ class Rails::ServerTest < ActiveSupport::TestCase end def test_environment_with_rails_env - rails = ENV['RAILS_ENV'] - ENV['RAILS_ENV'] = 'production' - server = Rails::Server.new - assert_equal 'production', server.options[:environment] - ensure - ENV['RAILS_ENV'] = rails + with_rails_env 'production' do + server = Rails::Server.new + assert_equal 'production', server.options[:environment] + end end def test_environment_with_rack_env - rack, rails = ENV['RACK_ENV'], ENV['RAILS_ENV'] - ENV['RAILS_ENV'] = nil - ENV['RACK_ENV'] = 'production' - server = Rails::Server.new - assert_equal 'production', server.options[:environment] - ensure - ENV['RACK_ENV'] = rack - ENV['RAILS_ENV'] = rails + with_rack_env 'production' do + server = Rails::Server.new + assert_equal 'production', server.options[:environment] + end end end diff --git a/railties/test/env_helpers.rb b/railties/test/env_helpers.rb new file mode 100644 index 0000000000..6223c85bbf --- /dev/null +++ b/railties/test/env_helpers.rb @@ -0,0 +1,26 @@ +module EnvHelpers + private + + def with_rails_env(env) + switch_env 'RAILS_ENV', env do + switch_env 'RACK_ENV', nil do + yield + end + end + end + + def with_rack_env(env) + switch_env 'RACK_ENV', env do + switch_env 'RAILS_ENV', nil do + yield + end + end + end + + def switch_env(key, value) + old, ENV[key] = ENV[key], value + yield + ensure + ENV[key] = old + end +end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 8af92479c3..54734ed260 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -1,8 +1,11 @@ require 'generators/generators_test_helper' require 'rails/generators/rails/app/app_generator' +require 'env_helpers' class ActionsTest < Rails::Generators::TestCase include GeneratorsTestHelper + include EnvHelpers + tests Rails::Generators::AppGenerator arguments [destination_root] @@ -154,10 +157,9 @@ class ActionsTest < Rails::Generators::TestCase def test_rake_should_run_rake_command_with_default_env generator.expects(:run).once.with("rake log:clear RAILS_ENV=development", verbose: false) - old_env, ENV['RAILS_ENV'] = ENV["RAILS_ENV"], nil - action :rake, 'log:clear' - ensure - ENV["RAILS_ENV"] = old_env + with_rails_env nil do + action :rake, 'log:clear' + end end def test_rake_with_env_option_should_run_rake_command_in_env @@ -167,26 +169,23 @@ class ActionsTest < Rails::Generators::TestCase def test_rake_with_rails_env_variable_should_run_rake_command_in_env generator.expects(:run).once.with('rake log:clear RAILS_ENV=production', verbose: false) - old_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "production" - action :rake, 'log:clear' - ensure - ENV["RAILS_ENV"] = old_env + with_rails_env "production" do + action :rake, 'log:clear' + end end def test_env_option_should_win_over_rails_env_variable_when_running_rake generator.expects(:run).once.with('rake log:clear RAILS_ENV=production', verbose: false) - old_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "staging" - action :rake, 'log:clear', env: 'production' - ensure - ENV["RAILS_ENV"] = old_env + with_rails_env "staging" do + action :rake, 'log:clear', env: 'production' + end end def test_rake_with_sudo_option_should_run_rake_command_with_sudo generator.expects(:run).once.with("sudo rake log:clear RAILS_ENV=development", verbose: false) - old_env, ENV['RAILS_ENV'] = ENV["RAILS_ENV"], nil - action :rake, 'log:clear', sudo: true - ensure - ENV["RAILS_ENV"] = old_env + with_rails_env nil do + action :rake, 'log:clear', sudo: true + end end def test_capify_should_run_the_capify_command diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 5ea31f2e0f..b7e366d266 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -55,7 +55,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "app/views/layouts/application.html.erb", /javascript_include_tag\s+"application"/ assert_file "app/assets/stylesheets/application.css" assert_file "config/application.rb", /config\.assets\.enabled = true/ - assert_file "public/index.html", /url\("assets\/rails.png"\);/ end def test_invalid_application_name_raises_an_error @@ -251,13 +250,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_if_skip_index_html_is_given - run_generator [destination_root, '--skip-index-html'] - assert_no_file 'public/index.html' - assert_no_file 'app/assets/images/rails.png' - assert_file 'app/assets/images/.keep' - end - def test_creation_of_a_test_directory run_generator assert_file 'test' diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index 6ab1cd58c7..c48bc20899 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -117,13 +117,13 @@ class GeneratedAttributeTest < Rails::Generators::TestCase assert create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic? end end - + def test_polymorphic_reference_is_false %w(foo bar baz).each do |attribute_type| assert !create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic? end end - + def test_blank_type_defaults_to_string_raises_exception assert_equal :string, create_generated_attribute(nil, 'title').type assert_equal :string, create_generated_attribute("", 'title').type @@ -132,6 +132,13 @@ class GeneratedAttributeTest < Rails::Generators::TestCase def test_handles_index_names_for_references assert_equal "post", create_generated_attribute('string', 'post').index_name assert_equal "post_id", create_generated_attribute('references', 'post').index_name + assert_equal "post_id", create_generated_attribute('belongs_to', 'post').index_name assert_equal ["post_id", "post_type"], create_generated_attribute('references{polymorphic}', 'post').index_name end + + def test_handles_column_names_for_references + assert_equal "post", create_generated_attribute('string', 'post').column_name + assert_equal "post_id", create_generated_attribute('references', 'post').column_name + assert_equal "post_id", create_generated_attribute('belongs_to', 'post').column_name + end end diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 0c7ff0ebe7..70e080a8ab 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -273,6 +273,16 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_file "test/fixtures/accounts.yml", /name: MyString/, /age: 1/ end + def test_fixtures_use_the_references_ids + run_generator ["LineItem", "product:references", "cart:belongs_to"] + assert_file "test/fixtures/line_items.yml", /product_id: \n cart_id: / + end + + def test_fixtures_use_the_references_ids_and_type + run_generator ["LineItem", "product:references{polymorphic}", "cart:belongs_to"] + assert_file "test/fixtures/line_items.yml", /product_id: \n product_type: Product\n cart_id: / + end + def test_fixture_is_skipped run_generator ["account", "--skip-fixture"] assert_no_file "test/fixtures/accounts.yml" diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 8cacca668f..57a12d0457 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -20,17 +20,13 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_match(/@users = User\.all/, m) end - assert_instance_method :show, content do |m| - assert_match(/@user = User\.find\(params\[:id\]\)/, m) - end + assert_instance_method :show, content assert_instance_method :new, content do |m| assert_match(/@user = User\.new/, m) end - assert_instance_method :edit, content do |m| - assert_match(/@user = User\.find\(params\[:id\]\)/, m) - end + assert_instance_method :edit, content assert_instance_method :create, content do |m| assert_match(/@user = User\.new\(user_params\)/, m) @@ -39,21 +35,50 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase end assert_instance_method :update, content do |m| - assert_match(/@user = User\.find\(params\[:id\]\)/, m) assert_match(/@user\.update_attributes\(user_params\)/, m) assert_match(/@user\.errors/, m) end assert_instance_method :destroy, content do |m| - assert_match(/@user = User\.find\(params\[:id\]\)/, m) assert_match(/@user\.destroy/, m) end + assert_instance_method :set_user, content do |m| + assert_match(/@user = User\.find\(params\[:id\]\)/, m) + end + assert_match(/def user_params/, content) assert_match(/params\.require\(:user\)\.permit\(:name, :age\)/, content) end end + def test_dont_use_require_or_permit_if_there_are_no_attributes + run_generator ["User"] + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match(/def user_params/, content) + assert_match(/params\[:user\]/, content) + end + end + + def test_controller_permit_references_attributes + run_generator ["LineItem", "product:references", "cart:belongs_to"] + + assert_file "app/controllers/line_items_controller.rb" do |content| + assert_match(/def line_item_params/, content) + assert_match(/params\.require\(:line_item\)\.permit\(:product_id, :cart_id\)/, content) + end + end + + def test_controller_permit_polymorphic_references_attributes + run_generator ["LineItem", "product:references{polymorphic}"] + + assert_file "app/controllers/line_items_controller.rb" do |content| + assert_match(/def line_item_params/, content) + assert_match(/params\.require\(:line_item\)\.permit\(:product_id, :product_type\)/, content) + end + end + def test_helper_are_invoked_with_a_pluralized_name run_generator assert_file "app/helpers/users_helper.rb", /module UsersHelper/ @@ -70,13 +95,13 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase end def test_functional_tests - run_generator + run_generator ["User", "name:string", "age:integer", "organization:references{polymorphic}"] assert_file "test/controllers/users_controller_test.rb" do |content| assert_match(/class UsersControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) - assert_match(/post :create, user: \{ age: @user.age, name: @user.name \}/, content) - assert_match(/put :update, id: @user, user: \{ age: @user.age, name: @user.name \}/, content) + assert_match(/post :create, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) + assert_match(/put :update, id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content) end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 54d5a9db6f..de62fdb1ea 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -30,17 +30,13 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_match(/@product_lines = ProductLine\.all/, m) end - assert_instance_method :show, content do |m| - assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, m) - end + assert_instance_method :show, content assert_instance_method :new, content do |m| assert_match(/@product_line = ProductLine\.new/, m) end - assert_instance_method :edit, content do |m| - assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, m) - end + assert_instance_method :edit, content assert_instance_method :create, content do |m| assert_match(/@product_line = ProductLine\.new\(product_line_params\)/, m) @@ -49,21 +45,23 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_instance_method :update, content do |m| - assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, m) assert_match(/@product_line\.update_attributes\(product_line_params\)/, m) assert_match(/@product_line\.errors/, m) end assert_instance_method :destroy, content do |m| - assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, m) assert_match(/@product_line\.destroy/, m) end + + assert_instance_method :set_product_line, content do |m| + assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, m) + end end assert_file "test/controllers/product_lines_controller_test.rb" do |test| assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test) - assert_match(/post :create, product_line: \{ title: @product_line.title \}/, test) - assert_match(/put :update, id: @product_line, product_line: \{ title: @product_line.title \}/, test) + assert_match(/post :create, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) + assert_match(/put :update, id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test) end # Views @@ -149,17 +147,13 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_match(/@admin_roles = Admin::Role\.all/, m) end - assert_instance_method :show, content do |m| - assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m) - end + assert_instance_method :show, content assert_instance_method :new, content do |m| assert_match(/@admin_role = Admin::Role\.new/, m) end - assert_instance_method :edit, content do |m| - assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m) - end + assert_instance_method :edit, content assert_instance_method :create, content do |m| assert_match(/@admin_role = Admin::Role\.new\(admin_role_params\)/, m) @@ -168,15 +162,17 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_instance_method :update, content do |m| - assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m) assert_match(/@admin_role\.update_attributes\(admin_role_params\)/, m) assert_match(/@admin_role\.errors/, m) end assert_instance_method :destroy, content do |m| - assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m) assert_match(/@admin_role\.destroy/, m) end + + assert_instance_method :set_admin_role, content do |m| + assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m) + end end assert_file "test/controllers/admin/roles_controller_test.rb", @@ -203,7 +199,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase run_generator [ "admin/role" ], :behavior => :revoke # Model - assert_file "app/models/admin.rb" # ( should not be remove ) + assert_file "app/models/admin.rb" # ( should not be remove ) assert_no_file "app/models/admin/role.rb" assert_no_file "test/models/admin/role_test.rb" assert_no_file "test/fixtures/admin/roles.yml" |