diff options
240 files changed, 3143 insertions, 1788 deletions
diff --git a/.travis.yml b/.travis.yml index c6f868498d..7e3d728872 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ before_install: - gem install bundler rvm: - 1.9.3 + - 2.0.0 env: - "GEM=railties" - "GEM=ap,am,amo,as" @@ -23,3 +24,6 @@ notifications: rooms: - secure: "CGWvthGkBKNnTnk9YSmf9AXKoiRI33fCl5D3jU4nx3cOPu6kv2R9nMjt9EAo\nOuS4Q85qNSf4VNQ2cUPNiNYSWQ+XiTfivKvDUw/QW9r1FejYyeWarMsSBWA+\n0fADjF1M2dkDIVLgYPfwoXEv7l+j654F1KLKB69F0F/netwP9CQ=" bundler_args: --path vendor/bundle +matrix: + allow_failures: + - rvm: 2.0.0 @@ -4,7 +4,7 @@ gemspec gem 'arel', github: 'rails/arel', branch: 'master' -gem 'mocha', '>= 0.11.2', require: false +gem 'mocha', '~> 0.13.0', require: false gem 'rack-test', github: 'brynary/rack-test' gem 'rack-cache', '~> 1.2' gem 'bcrypt-ruby', '~> 3.0.0' @@ -23,11 +23,7 @@ gem 'uglifier', require: false gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: 'master' group :doc do - # The current sdoc cannot generate GitHub links due - # to a bug, but the PR that fixes it has been there - # for some weeks unapplied. As a temporary solution - # this is our own fork with the fix. - gem 'sdoc', github: 'fxn/sdoc' + gem 'sdoc', github: 'voloko/sdoc' gem 'redcarpet', '~> 2.2.2', platforms: :ruby gem 'w3c_validators' end @@ -55,7 +51,7 @@ platforms :ruby do group :db do gem 'pg', '>= 0.11.0' - gem 'mysql', '>= 2.8.1' if RUBY_VERSION < '2.0.0' + gem 'mysql', '>= 2.9.0' gem 'mysql2', '>= 0.3.10' end end diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 28a5c0ab71..af91907f46 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,5 +1,8 @@ ## Rails 4.0.0 (unreleased) ## +* 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 diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index 0c669e2e91..e9f979f34b 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -1,4 +1,4 @@ -version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip +version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -6,17 +6,20 @@ Gem::Specification.new do |s| s.version = version s.summary = 'Email composition, delivery, and receiving framework (part of Rails).' s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.' + s.required_ruby_version = '>= 1.9.3' - s.license = 'MIT' - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.license = 'MIT' + + s.author = 'David Heinemeier Hansson' + s.email = 'david@loudthinking.com' + s.homepage = 'http://www.rubyonrails.org' s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*'] s.require_path = 'lib' s.requirements << 'none' - s.add_dependency('actionpack', version) - s.add_dependency('mail', '~> 2.4.4') + s.add_dependency 'actionpack', version + + s.add_dependency 'mail', '~> 2.5.2' end diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index cfbe2f1cbd..a9642dc695 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -44,4 +44,5 @@ module ActionMailer autoload :MailHelper autoload :TestCase autoload :TestHelper + autoload :QueuedMessage end diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index a66e81f1c3..2b533ad054 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -70,7 +70,7 @@ module ActionMailer # The block syntax is also useful in providing information specific to a part: # # mail(to: user.email) do |format| - # format.text(:content_transfer_encoding => "base64") + # format.text(content_transfer_encoding: "base64") # format.html # end # @@ -129,12 +129,12 @@ module ActionMailer # It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt> # option as a configuration option in <tt>config/application.rb</tt>: # - # config.action_mailer.default_url_options = { :host => "example.com" } + # config.action_mailer.default_url_options = { host: "example.com" } # # When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the - # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper + # <tt>only_path: false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper # will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing - # <tt>:only_path => false</tt> will ensure that absolute URLs are generated. + # <tt>only_path: false</tt> will ensure that absolute URLs are generated. # # = Sending mail # @@ -246,10 +246,10 @@ module ActionMailer # You can pass in any header value that a <tt>Mail::Message</tt> accepts. Out of the box, # <tt>ActionMailer::Base</tt> sets the following: # - # * <tt>:mime_version => "1.0"</tt> - # * <tt>:charset => "UTF-8",</tt> - # * <tt>:content_type => "text/plain",</tt> - # * <tt>:parts_order => [ "text/plain", "text/enriched", "text/html" ]</tt> + # * <tt>mime_version: "1.0"</tt> + # * <tt>charset: "UTF-8",</tt> + # * <tt>content_type: "text/plain",</tt> + # * <tt>parts_order: [ "text/plain", "text/enriched", "text/html" ]</tt> # # <tt>parts_order</tt> and <tt>charset</tt> are not actually valid <tt>Mail::Message</tt> header fields, # but Action Mailer translates them appropriately and sets the correct values. @@ -665,7 +665,7 @@ module ActionMailer # # The block syntax also allows you to customize the part headers if desired: # - # mail(:to => 'mikel@test.lindsaar.net') do |format| + # mail(to: 'mikel@test.lindsaar.net') do |format| # format.text(content_transfer_encoding: "base64") # format.html # end @@ -698,7 +698,7 @@ module ActionMailer assignable.each { |k, v| m[k] = v } # Render the templates and blocks - responses, explicit_order = collect_responses_and_parts_order(headers, &block) + responses = collect_responses(headers, &block) create_parts_from_responses(m, responses) # Setup content type, reapply charset and handle parts order @@ -706,8 +706,7 @@ module ActionMailer m.charset = charset if m.multipart? - parts_order ||= explicit_order || headers[:parts_order] - m.body.set_sort_order(parts_order) + m.body.set_sort_order(headers[:parts_order]) m.body.sort_parts! end @@ -742,14 +741,13 @@ module ActionMailer I18n.t(:subject, scope: [mailer_scope, action_name], default: action_name.humanize) end - def collect_responses_and_parts_order(headers) #:nodoc: - responses, parts_order = [], nil + def collect_responses(headers) #:nodoc: + responses = [] if block_given? collector = ActionMailer::Collector.new(lookup_context) { render(action_name) } yield(collector) - parts_order = collector.responses.map { |r| r[:content_type] } - responses = collector.responses + responses = collector.responses elsif headers[:body] responses << { body: headers.delete(:body), @@ -769,7 +767,7 @@ module ActionMailer end end - [responses, parts_order] + responses end def each_template(paths, name, &block) #:nodoc: diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index b07b352082..3c21886502 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -322,19 +322,6 @@ class BaseTest < ActiveSupport::TestCase assert_not_nil(mail.content_type_parameters[:boundary]) end - test "explicit multipart does not sort order" do - order = ["text/html", "text/plain"] - with_default BaseMailer, parts_order: order do - email = BaseMailer.explicit_multipart - assert_equal("text/plain", email.parts[0].mime_type) - assert_equal("text/html", email.parts[1].mime_type) - - email = BaseMailer.explicit_multipart(parts_order: order.reverse) - assert_equal("text/plain", email.parts[0].mime_type) - assert_equal("text/html", email.parts[1].mime_type) - end - end - test "explicit multipart with attachments creates nested parts" do email = BaseMailer.explicit_multipart(attachments: true) assert_equal("application/pdf", email.parts[0].mime_type) @@ -349,10 +336,10 @@ class BaseTest < ActiveSupport::TestCase email = BaseMailer.explicit_multipart_templates assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) - assert_equal("text/html", email.parts[0].mime_type) - assert_equal("HTML Explicit Multipart Templates", email.parts[0].body.encoded) - assert_equal("text/plain", email.parts[1].mime_type) - assert_equal("TEXT Explicit Multipart Templates", email.parts[1].body.encoded) + assert_equal("text/plain", email.parts[0].mime_type) + assert_equal("TEXT Explicit Multipart Templates", email.parts[0].body.encoded) + assert_equal("text/html", email.parts[1].mime_type) + assert_equal("HTML Explicit Multipart Templates", email.parts[1].body.encoded) end test "explicit multipart with format.any" do @@ -387,10 +374,23 @@ class BaseTest < ActiveSupport::TestCase email = BaseMailer.explicit_multipart_with_one_template assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) - assert_equal("text/html", email.parts[0].mime_type) - assert_equal("[:html]", email.parts[0].body.encoded) - assert_equal("text/plain", email.parts[1].mime_type) - assert_equal("[:text]", email.parts[1].body.encoded) + assert_equal("text/plain", email.parts[0].mime_type) + assert_equal("[:text]", email.parts[0].body.encoded) + assert_equal("text/html", email.parts[1].mime_type) + assert_equal("[:html]", email.parts[1].body.encoded) + end + + test "explicit multipart with sort order" do + order = ["text/html", "text/plain"] + with_default BaseMailer, parts_order: order do + email = BaseMailer.explicit_multipart + assert_equal("text/html", email.parts[0].mime_type) + assert_equal("text/plain", email.parts[1].mime_type) + + email = BaseMailer.explicit_multipart(parts_order: order.reverse) + assert_equal("text/plain", email.parts[0].mime_type) + assert_equal("text/html", email.parts[1].mime_type) + end end # Class level API with method missing diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 72121668ac..04a3bf8697 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,46 @@ ## Rails 4.0.0 (unreleased) ## +* Fix error when using a non-hash query argument named "params" in `url_for`. + + Before: + + url_for(params: "") # => undefined method `reject!' for "":String + + After: + + url_for(params: "") # => http://www.example.com?params= + + *tumayun + Carlos Antonio da Silva* + +* Render every partial with a new `ActionView::PartialRenderer`. This resolves + issues when rendering nested partials. + Fix #8197 + + *Yves Senn* + +* Introduce `ActionView::Template::Handlers::ERB.escape_whitelist`. This is a list + of mime types where template text is not html escaped by default. It prevents `Jack & Joe` + from rendering as `Jack & Joe` for the whitelisted mime types. The default whitelist + contains text/plain. Fix #7976 + + *Joost Baaij* + +* Fix input name when `:multiple => true` and `:index` are set. + + Before: + + check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1) + #=> <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" /> + + After: + + check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1) + #=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" /> + + Fix #8108 + + *Daniel Fox, Grant Hutchins & Trace Wax* + * Clear url helpers when reloading routes. *Santiago Pastorino* diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 7d292ac17c..89fdd528c2 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -1,4 +1,4 @@ -version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip +version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -6,24 +6,26 @@ Gem::Specification.new do |s| s.version = version s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).' s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.' + s.required_ruby_version = '>= 1.9.3' - s.license = 'MIT' - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.license = 'MIT' + + s.author = 'David Heinemeier Hansson' + s.email = 'david@loudthinking.com' + s.homepage = 'http://www.rubyonrails.org' s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*'] s.require_path = 'lib' s.requirements << 'none' - s.add_dependency('activesupport', version) - s.add_dependency('builder', '~> 3.1.0') - s.add_dependency('rack', '~> 1.4.1') - s.add_dependency('rack-test', '~> 0.6.1') - s.add_dependency('journey', '~> 2.0.0') - s.add_dependency('erubis', '~> 2.7.0') + s.add_dependency 'activesupport', version + s.add_dependency 'builder', '~> 3.1.0' + s.add_dependency 'rack', '~> 1.4.1' + s.add_dependency 'rack-test', '~> 0.6.1' + s.add_dependency 'journey', '~> 2.0.0' + s.add_dependency 'erubis', '~> 2.7.0' - s.add_development_dependency('activemodel', version) - s.add_development_dependency('tzinfo', '~> 0.3.33') + s.add_development_dependency 'activemodel', version + s.add_development_dependency 'tzinfo', '~> 0.3.33' end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 5705ab590c..02ac111392 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -29,13 +29,14 @@ module AbstractController # * <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 def _normalize_callback_options(options) - if only = options[:only] - only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ") - options[:if] = Array(options[:if]) << only - end - if except = options[:except] - except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ") - options[:unless] = Array(options[:unless]) << except + _normalize_callback_option(options, :only, :if) + _normalize_callback_option(options, :except, :unless) + end + + def _normalize_callback_option(options, from, to) # :nodoc: + if from = options[from] + from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ") + options[to] = Array(options[to]) << from end end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index c1b3994035..12da273af9 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -170,7 +170,7 @@ module AbstractController # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example: # # class WeblogController < ActionController::Base - # layout "weblog_standard", :except => :rss + # layout "weblog_standard", except: :rss # # # ... # @@ -180,7 +180,7 @@ module AbstractController # be rendered directly, without wrapping a layout around the rendered view. # # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so - # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>. + # #<tt>except: [ :rss, :text_only ]</tt> is valid, as is <tt>except: :rss</tt>. # # == Using a different layout in the action render call # @@ -192,7 +192,7 @@ module AbstractController # layout "weblog_standard" # # def help - # render :action => "help", :layout => "help" + # render action: "help", layout: "help" # end # end # diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 9b3bf99fc3..971c4189c8 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,4 +1,5 @@ require "action_controller/log_subscriber" +require "action_controller/metal/params_wrapper" module ActionController # Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 6d46586367..d3b5bafee1 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -249,9 +249,9 @@ module ActionController end def secret_token(request) - secret = request.env["action_dispatch.secret_token"] - raise "You must set config.secret_token in your app's config" if secret.blank? - secret + key_generator = request.env["action_dispatch.key_generator"] + http_auth_salt = request.env["action_dispatch.http_auth_salt"] + key_generator.generate_key(http_auth_salt) end # Uses an MD5 digest based on time to generate a value to be used only once. diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 09abc999c1..a475d4bdff 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/module/anonymous' +require 'active_support/core_ext/struct' require 'action_dispatch/http/mime_types' module ActionController @@ -72,12 +73,99 @@ module ActionController EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8) + require 'mutex_m' + + class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc: + include Mutex_m + + def self.from_hash(hash) + name = hash[:name] + format = Array(hash[:format]) + include = hash[:include] && Array(hash[:include]).collect(&:to_s) + exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s) + new name, format, include, exclude, nil, nil + end + + def initialize(name, format, include, exclude, klass, model) # nodoc + super + @include_set = include + @name_set = name + end + + def model + super || synchronize { super || self.model = _default_wrap_model } + end + + def include + return super if @include_set + + m = model + synchronize do + return super if @include_set + + @include_set = true + + unless super || exclude + if m.respond_to?(:attribute_names) && m.attribute_names.any? + self.include = m.attribute_names + end + end + end + end + + def name + return super if @name_set + + m = model + synchronize do + return super if @name_set + + @name_set = true + + unless super || klass.anonymous? + self.name = m ? m.to_s.demodulize.underscore : + klass.controller_name.singularize + end + end + end + + private + # Determine the wrapper model from the controller's name. By convention, + # this could be done by trying to find the defined model that has the + # same singularize name as the controller. For example, +UsersController+ + # will try to find if the +User+ model exists. + # + # This method also does namespace lookup. Foo::Bar::UsersController will + # try to find Foo::Bar::User, Foo::User and finally User. + def _default_wrap_model #:nodoc: + return nil if klass.anonymous? + model_name = klass.name.sub(/Controller$/, '').classify + + begin + if model_klass = model_name.safe_constantize + model_klass + else + namespaces = model_name.split("::") + namespaces.delete_at(-2) + break if namespaces.last == model_name + model_name = namespaces.join("::") + end + end until model_klass + + model_klass + end + end + included do class_attribute :_wrapper_options - self._wrapper_options = { :format => [] } + self._wrapper_options = Options.from_hash(format: []) end module ClassMethods + def _set_wrapper_options(options) + self._wrapper_options = Options.from_hash(options) + end + # Sets the name of the wrapper key, or the model which +ParamsWrapper+ # would use to determine the attribute names from. # @@ -119,68 +207,24 @@ module ActionController model = name_or_model_or_options end - _set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model) + opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options) + opts.model = model + opts.klass = self + + self._wrapper_options = opts end # Sets the default wrapper key or model which will be used to determine # wrapper key and attribute names. Will be called automatically when the # module is inherited. def inherited(klass) - if klass._wrapper_options[:format].present? - klass._set_wrapper_defaults(klass._wrapper_options.slice(:format)) + if klass._wrapper_options.format.any? + params = klass._wrapper_options.dup + params.klass = klass + klass._wrapper_options = params end super end - - protected - - # Determine the wrapper model from the controller's name. By convention, - # this could be done by trying to find the defined model that has the - # same singularize name as the controller. For example, +UsersController+ - # will try to find if the +User+ model exists. - # - # This method also does namespace lookup. Foo::Bar::UsersController will - # try to find Foo::Bar::User, Foo::User and finally User. - def _default_wrap_model #:nodoc: - return nil if self.anonymous? - model_name = self.name.sub(/Controller$/, '').classify - - begin - if model_klass = model_name.safe_constantize - model_klass - else - namespaces = model_name.split("::") - namespaces.delete_at(-2) - break if namespaces.last == model_name - model_name = namespaces.join("::") - end - end until model_klass - - model_klass - end - - def _set_wrapper_defaults(options, model=nil) - options = options.dup - - unless options[:include] || options[:exclude] - model ||= _default_wrap_model - if model.respond_to?(:attribute_names) && model.attribute_names.present? - options[:include] = model.attribute_names - end - end - - unless options[:name] || self.anonymous? - model ||= _default_wrap_model - options[:name] = model ? model.to_s.demodulize.underscore : - controller_name.singularize - end - - options[:include] = Array(options[:include]).collect(&:to_s) if options[:include] - options[:exclude] = Array(options[:exclude]).collect(&:to_s) if options[:exclude] - options[:format] = Array(options[:format]) - - self._wrapper_options = options - end end # Performs parameters wrapping upon the request. Will be called automatically @@ -205,20 +249,20 @@ module ActionController # Returns the wrapper key which will use to stored wrapped parameters. def _wrapper_key - _wrapper_options[:name] + _wrapper_options.name end # Returns the list of enabled formats. def _wrapper_formats - _wrapper_options[:format] + _wrapper_options.format end # Returns the list of parameters which will be selected for wrapped. def _wrap_parameters(parameters) - value = if include_only = _wrapper_options[:include] + value = if include_only = _wrapper_options.include parameters.slice(*include_only) else - exclude = _wrapper_options[:exclude] || [] + exclude = _wrapper_options.exclude || [] parameters.except(*(exclude + EXCLUDE_PARAMETERS)) end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index a50f0ca8c1..265ce5d6f3 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -121,11 +121,11 @@ module ActionController #:nodoc: class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc: def self.build(request) - secret = request.env[ActionDispatch::Cookies::TOKEN_KEY] - host = request.host - secure = request.ssl? + key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY] + host = request.host + secure = request.ssl? - new(secret, host, secure) + new(key_generator, host, secure) end def write(*) diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 04dc1d37f7..da640502a2 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -65,7 +65,6 @@ module ActionController # params["key"] # => "value" class Parameters < ActiveSupport::HashWithIndifferentAccess cattr_accessor :permit_all_parameters, instance_accessor: false - attr_accessor :permitted # :nodoc: # Returns a new instance of <tt>ActionController::Parameters</tt>. # Also, sets the +permitted+ attribute to the default value of @@ -260,7 +259,9 @@ module ActionController # params.slice(:a, :b) # => {"a"=>1, "b"=>2} # params.slice(:d) # => {} def slice(*keys) - self.class.new(super) + self.class.new(super).tap do |new_instance| + new_instance.instance_variable_set :@permitted, @permitted + end end # Returns an exact copy of the <tt>ActionController::Parameters</tt> diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 0ec355246e..1d716a3248 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -81,10 +81,12 @@ module ActionDispatch end module Session - autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' - autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' - autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' - autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' + autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store' + autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store' + autoload :EncryptedCookieStore, 'action_dispatch/middleware/session/cookie_store' + autoload :UpgradeSignatureToEncryptionCookieStore, 'action_dispatch/middleware/session/cookie_store' + autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store' + autoload :CacheStore, 'action_dispatch/middleware/session/cache_store' end mattr_accessor :test_app diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 2b5d3d85bf..f56f09c5b3 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -288,18 +288,23 @@ module Mime @@html_types.include?(to_sym) || @string =~ /html/ end + private - def method_missing(method, *args) - if method.to_s.ends_with? '?' - method[0..-2].downcase.to_sym == to_sym - else - super - end - end - def respond_to_missing?(method, include_private = false) #:nodoc: - method.to_s.ends_with? '?' + def to_ary; end + def to_a; end + + def method_missing(method, *args) + if method.to_s.ends_with? '?' + method[0..-2].downcase.to_sym == to_sym + else + super end + end + + def respond_to_missing?(method, include_private = false) #:nodoc: + method.to_s.ends_with? '?' + end end end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 8aa02ec482..bced7d84c0 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -8,14 +8,16 @@ module ActionDispatch class << self def extract_domain(host, tld_length = @@tld_length) - return nil unless named_host?(host) - host.split('.').last(1 + tld_length).join('.') + host.split('.').last(1 + tld_length).join('.') if named_host?(host) end def extract_subdomains(host, tld_length = @@tld_length) - return [] unless named_host?(host) - parts = host.split('.') - parts[0..-(tld_length+2)] + if named_host?(host) + parts = host.split('.') + parts[0..-(tld_length + 2)] + else + [] + end end def extract_subdomain(host, tld_length = @@tld_length) @@ -23,15 +25,13 @@ module ActionDispatch end def url_for(options = {}) - path = "" - path << options.delete(:script_name).to_s.chomp("/") + path = options.delete(:script_name).to_s.chomp("/") path << options.delete(:path).to_s - params = options[:params] || {} - params.reject! {|k,v| v.to_param.nil? } + params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params) + params.reject! { |_,v| v.to_param.nil? } result = build_host_url(options) - result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) result << "?#{params.to_query}" unless params.empty? result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor] diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index eaf922595a..2f148752cb 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/module/attribute_accessors' +require 'active_support/message_verifier' module ActionDispatch class Request < Rack::Request @@ -27,7 +28,7 @@ module ActionDispatch # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now } # # # Sets a signed cookie, which prevents users from tampering with its value. - # # The cookie is signed by your app's <tt>config.secret_token</tt> value. + # # The cookie is signed by your app's <tt>config.secret_key_base</tt> value. # # It can be read using the signed method <tt>cookies.signed[:key]</tt> # cookies.signed[:user_id] = current_user.id # @@ -79,7 +80,11 @@ module ActionDispatch # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or # only HTTP. Defaults to +false+. class Cookies - HTTP_HEADER = "Set-Cookie".freeze + HTTP_HEADER = "Set-Cookie".freeze + GENERATOR_KEY = "action_dispatch.key_generator".freeze + SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze + ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze + ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze TOKEN_KEY = "action_dispatch.secret_token".freeze # Raised when storing more than 4K of session data. @@ -103,21 +108,28 @@ module ActionDispatch DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/ def self.build(request) - secret = request.env[TOKEN_KEY] + env = request.env + key_generator = env[GENERATOR_KEY] + options = { signed_cookie_salt: env[SIGNED_COOKIE_SALT], + encrypted_cookie_salt: env[ENCRYPTED_COOKIE_SALT], + encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT], + token_key: env[TOKEN_KEY] } + host = request.host secure = request.ssl? - new(secret, host, secure).tap do |hash| + new(key_generator, host, secure, options).tap do |hash| hash.update(request.cookies) end end - def initialize(secret = nil, host = nil, secure = false) - @secret = secret + def initialize(key_generator, host = nil, secure = false, options = {}) + @key_generator = key_generator @set_cookies = {} @delete_cookies = {} @host = host @secure = secure + @options = options @cookies = {} end @@ -220,7 +232,7 @@ module ActionDispatch # cookies.permanent.signed[:remember_me] = current_user.id # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT def permanent - @permanent ||= PermanentCookieJar.new(self, @secret) + @permanent ||= PermanentCookieJar.new(self, @key_generator, @options) end # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from @@ -228,7 +240,7 @@ module ActionDispatch # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will # be raised. # - # This jar requires that you set a suitable secret for the verification on your app's +config.secret_token+. + # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+. # # Example: # @@ -237,7 +249,28 @@ module ActionDispatch # # cookies.signed[:discount] # => 45 def signed - @signed ||= SignedCookieJar.new(self, @secret) + @signed ||= SignedCookieJar.new(self, @key_generator, @options) + end + + # Only needed for supporting the +UpgradeSignatureToEncryptionCookieStore+, users and plugin authors should not use this + def signed_using_old_secret #:nodoc: + @signed_using_old_secret ||= SignedCookieJar.new(self, ActiveSupport::DummyKeyGenerator.new(@options[:token_key]), @options) + end + + # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read. + # If the cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception + # will be raised. + # + # This jar requires that you set a suitable secret for the verification on your app's +config.secret_key_base+. + # + # Example: + # + # cookies.encrypted[:discount] = 45 + # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/ + # + # cookies.encrypted[:discount] # => 45 + def encrypted + @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options) end def write(headers) @@ -261,8 +294,10 @@ module ActionDispatch end class PermanentCookieJar < CookieJar #:nodoc: - def initialize(parent_jar, secret) - @parent_jar, @secret = parent_jar, secret + def initialize(parent_jar, key_generator, options = {}) + @parent_jar = parent_jar + @key_generator = key_generator + @options = options end def []=(key, options) @@ -283,11 +318,11 @@ module ActionDispatch class SignedCookieJar < CookieJar #:nodoc: MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes. - SECRET_MIN_LENGTH = 30 # Characters - def initialize(parent_jar, secret) - ensure_secret_secure(secret) + def initialize(parent_jar, key_generator, options = {}) @parent_jar = parent_jar + @options = options + secret = key_generator.generate_key(@options[:signed_cookie_salt]) @verifier = ActiveSupport::MessageVerifier.new(secret) end @@ -314,26 +349,41 @@ module ActionDispatch def method_missing(method, *arguments, &block) @parent_jar.send(method, *arguments, &block) end + end + + class EncryptedCookieJar < SignedCookieJar #:nodoc: + def initialize(parent_jar, key_generator, options = {}) + if ActiveSupport::DummyKeyGenerator === key_generator + raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." + + "Set config.secret_key_base in config/initializers/secret_token.rb" + end + + @parent_jar = parent_jar + @options = options + secret = key_generator.generate_key(@options[:encrypted_cookie_salt]) + sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt]) + @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + end - protected - - # To prevent users from using something insecure like "Password" we make sure that the - # secret they've provided is at least 30 characters in length. - def ensure_secret_secure(secret) - if secret.blank? - raise ArgumentError, "A secret is required to generate an " + - "integrity hash for cookie session data. Use " + - "config.secret_token = \"some secret phrase of at " + - "least #{SECRET_MIN_LENGTH} characters\"" + - "in config/initializers/secret_token.rb" + def [](name) + if encrypted_message = @parent_jar[name] + @encryptor.decrypt_and_verify(encrypted_message) end + rescue ActiveSupport::MessageVerifier::InvalidSignature, + ActiveSupport::MessageVerifier::InvalidMessage + nil + end - if secret.length < SECRET_MIN_LENGTH - raise ArgumentError, "Secret should be something secure, " + - "like \"#{SecureRandom.hex(16)}\". The value you " + - "provided, \"#{secret}\", is shorter than the minimum length " + - "of #{SECRET_MIN_LENGTH} characters" + def []=(key, options) + if options.is_a?(Hash) + options.symbolize_keys! + else + options = { :value => options } end + options[:value] = @encryptor.encrypt_and_sign(options[:value]) + + raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE + @parent_jar[key] = options end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 3f28ea75ef..ce5f89ee5b 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -57,8 +57,7 @@ module ActionDispatch def unpacked_cookie_data(env) env["action_dispatch.request.unsigned_session_cookie"] ||= begin stale_session_check! do - request = ActionDispatch::Request.new(env) - if data = request.cookie_jar.signed[@key] + if data = get_cookie(env) data.stringify_keys! end data || {} @@ -72,8 +71,51 @@ module ActionDispatch end def set_cookie(env, session_id, cookie) + cookie_jar(env)[@key] = cookie + end + + def get_cookie(env) + cookie_jar(env)[@key] + end + + def cookie_jar(env) + request = ActionDispatch::Request.new(env) + request.cookie_jar.signed + end + end + + class EncryptedCookieStore < CookieStore + + private + + def cookie_jar(env) + request = ActionDispatch::Request.new(env) + request.cookie_jar.encrypted + end + end + + # This cookie store 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 your config/initializers/session_store.rb + # + # You will also need to add + # + # Myapp::Application.config.secret_key_base = 'some secret' + # + # in your config/initializers/secret_token.rb, but do not remove +Myapp::Application.config.secret_token = 'some secret'+ + class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore + private + + def get_cookie(env) + signed_using_old_secret_cookie_jar(env)[@key] || cookie_jar(env)[@key] + end + + def signed_using_old_secret_cookie_jar(env) request = ActionDispatch::Request.new(env) - request.cookie_jar.signed[@key] = cookie + request.cookie_jar.signed_using_old_secret end end end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 0de10695e0..2b37a8d026 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -28,7 +28,7 @@ module ActionDispatch def call(env) begin - response = @app.call(env) + response = @app.call(env) rescue Exception => exception raise exception if env['action_dispatch.show_exceptions'] == false end diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 284dd180db..98c87d9b2d 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -13,6 +13,10 @@ module ActionDispatch config.action_dispatch.rescue_responses = { } config.action_dispatch.default_charset = nil config.action_dispatch.rack_cache = false + config.action_dispatch.http_auth_salt = 'http authentication' + config.action_dispatch.signed_cookie_salt = 'signed cookie' + config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie' + config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie' config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 045299281c..d6fe436b68 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -299,7 +299,7 @@ module ActionDispatch # and +:action+ to the controller's action. A pattern can also map # wildcard segments (globs) to params: # - # match 'songs/*category/:title' => 'songs#show' + # match 'songs/*category/:title', to: 'songs#show' # # # 'songs/rock/classic/stairway-to-heaven' sets # # params[:category] = 'rock/classic' @@ -315,10 +315,14 @@ module ActionDispatch # A pattern can also point to a +Rack+ endpoint i.e. anything that # responds to +call+: # - # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon"] } - # match 'photos/:id' => PhotoRackApp + # match 'photos/:id', to: lambda {|hash| [200, {}, "Coming soon"] } + # match 'photos/:id', to: PhotoRackApp # # Yes, controller actions are just rack endpoints - # match 'photos/:id' => PhotosController.action(:show) + # match 'photos/:id', to: PhotosController.action(:show) + # + # Because request various HTTP verbs with a single action has security + # implications, is recommendable use HttpHelpers[rdoc-ref:HttpHelpers] + # instead +match+ # # === Options # @@ -336,7 +340,7 @@ module ActionDispatch # [:module] # The namespace for :controller. # - # match 'path' => 'c#a', module: 'sekret', controller: 'posts' + # match 'path', to: 'c#a', module: 'sekret', controller: 'posts' # #=> Sekret::PostsController # # See <tt>Scoping#namespace</tt> for its scope equivalent. @@ -347,8 +351,9 @@ module ActionDispatch # [:via] # Allowed HTTP verb(s) for route. # - # match 'path' => 'c#a', via: :get - # match 'path' => 'c#a', via: [:get, :post] + # match 'path', to: 'c#a', via: :get + # match 'path', to: 'c#a', via: [:get, :post] + # match 'path', to: 'c#a', via: :all # # [:to] # Points to a +Rack+ endpoint. Can be an object that responds to @@ -364,14 +369,14 @@ module ActionDispatch # <tt>resource(s)</tt> block. For example: # # resource :bar do - # match 'foo' => 'c#a', on: :member, via: [:get, :post] + # match 'foo', to: 'c#a', on: :member, via: [:get, :post] # end # # Is equivalent to: # # resource :bar do # member do - # match 'foo' => 'c#a', via: [:get, :post] + # match 'foo', to: 'c#a', via: [:get, :post] # end # end # @@ -384,7 +389,7 @@ module ActionDispatch # class Blacklist # def matches?(request) request.remote_ip == '1.2.3.4' end # end - # match 'path' => 'c#a', constraints: Blacklist.new + # match 'path', to: 'c#a', constraints: Blacklist.new # # See <tt>Scoping#constraints</tt> for more examples with its scope # equivalent. @@ -393,7 +398,7 @@ module ActionDispatch # Sets defaults for parameters # # # Sets params[:format] to 'jpg' by default - # match 'path' => 'c#a', defaults: { format: 'jpg' } + # match 'path', to: 'c#a', defaults: { format: 'jpg' } # # See <tt>Scoping#defaults</tt> for its scope equivalent. # @@ -402,7 +407,7 @@ module ActionDispatch # false, the pattern matches any request prefixed with the given path. # # # Matches any request starting with 'path' - # match 'path' => 'c#a', anchor: false + # match 'path', to: 'c#a', anchor: false # # [:format] # Allows you to specify the default value for optional +format+ @@ -491,9 +496,7 @@ module ActionDispatch prefix_options = options.slice(*_route.segment_keys) # we must actually delete prefix segment keys to avoid passing them to next url_for _route.segment_keys.each { |k| options.delete(k) } - prefix = _routes.url_helpers.send("#{name}_path", prefix_options) - prefix = '' if prefix == '/' - prefix + _routes.url_helpers.send("#{name}_path", prefix_options) end end end @@ -501,7 +504,7 @@ module ActionDispatch module HttpHelpers # Define a route that only recognizes HTTP GET. - # For supported arguments, see <tt>Base#match</tt>. + # For supported arguments, see match[rdoc-ref:Base#match] # # get 'bacon', to: 'food#bacon' def get(*args, &block) @@ -509,7 +512,7 @@ module ActionDispatch end # Define a route that only recognizes HTTP POST. - # For supported arguments, see <tt>Base#match</tt>. + # For supported arguments, see match[rdoc-ref:Base#match] # # post 'bacon', to: 'food#bacon' def post(*args, &block) @@ -517,7 +520,7 @@ module ActionDispatch end # Define a route that only recognizes HTTP PATCH. - # For supported arguments, see <tt>Base#match</tt>. + # For supported arguments, see match[rdoc-ref:Base#match] # # patch 'bacon', to: 'food#bacon' def patch(*args, &block) @@ -525,7 +528,7 @@ module ActionDispatch end # Define a route that only recognizes HTTP PUT. - # For supported arguments, see <tt>Base#match</tt>. + # For supported arguments, see match[rdoc-ref:Base#match] # # put 'bacon', to: 'food#bacon' def put(*args, &block) @@ -533,7 +536,7 @@ module ActionDispatch end # Define a route that only recognizes HTTP DELETE. - # For supported arguments, see <tt>Base#match</tt>. + # For supported arguments, see match[rdoc-ref:Base#match] # # delete 'broccoli', to: 'food#broccoli' def delete(*args, &block) diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index 1ed5eb1dff..d70063d0e9 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -98,11 +98,11 @@ module ActionDispatch # Redirect any path to another path: # - # match "/stories" => redirect("/posts") + # get "/stories" => redirect("/posts") # # You can also use interpolation in the supplied redirect argument: # - # match 'docs/:article', to: redirect('/wiki/%{article}') + # get 'docs/:article', to: redirect('/wiki/%{article}') # # Alternatively you can use one of the other syntaxes: # @@ -111,25 +111,25 @@ module ActionDispatch # params, depending of how many arguments your block accepts. A string is required as a # return value. # - # match 'jokes/:number', to: redirect { |params, request| + # get 'jokes/:number', to: redirect { |params, request| # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp") # "http://#{request.host_with_port}/#{path}" # } # # Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass - # the block to +match+ instead of +redirect+. Use <tt>{ ... }</tt> instead. + # the block to +get+ instead of +redirect+. Use <tt>{ ... }</tt> instead. # # The options version of redirect allows you to supply only the parts of the url which need # to change, it also supports interpolation of the path similar to the first example. # - # match 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}') - # match 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}') + # get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}') + # get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}') # # Finally, an object which responds to call can be supplied to redirect, allowing you to reuse # common redirect routes. The call method must accept two arguments, params and request, and return # a string. # - # match 'accounts/:name' => redirect(SubdomainRedirector.new('api')) + # get 'accounts/:name' => redirect(SubdomainRedirector.new('api')) # def redirect(*args, &block) options = args.extract_options! diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 305bafc0c5..79dff7d121 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -1,5 +1,4 @@ require 'uri' -require 'active_support/core_ext/hash/diff' require 'active_support/core_ext/hash/indifferent_access' require 'action_controller/metal/exceptions' @@ -37,17 +36,19 @@ module ActionDispatch # # # Test a custom route # assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1') - def assert_recognizes(expected_options, path, extras={}, message=nil) + def assert_recognizes(expected_options, path, extras={}, msg=nil) request = recognized_request_for(path, extras) expected_options = expected_options.clone expected_options.stringify_keys! - # FIXME: minitest does object diffs, do we need to have our own? - message ||= sprintf("The recognized options <%s> did not match <%s>, difference: <%s>", - request.path_parameters, expected_options, expected_options.diff(request.path_parameters)) - assert_equal(expected_options, request.path_parameters, message) + msg = message(msg, "") { + sprintf("The recognized options <%s> did not match <%s>, difference:", + request.path_parameters, expected_options) + } + + assert_equal(expected_options, request.path_parameters, msg) end # Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+. diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 459f95bb73..6e51ba66a5 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -129,7 +129,7 @@ module ActionView minutes_with_offset = distance_in_minutes end remainder = (minutes_with_offset % 525600) - distance_in_years = (minutes_with_offset / 525600) + distance_in_years = (minutes_with_offset.div 525600) if remainder < 131400 locale.t(:about_x_years, :count => distance_in_years) elsif remainder < 394200 diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index b7b3db959e..46ebe60ec2 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -152,7 +152,7 @@ module ActionView # form, and parameters extraction gets the last occurrence of any repeated # key in the query string, that works for ordinary forms. # - # In case if you don't want the helper to generate this hidden field you can specify <tt>include_blank: false</tt> option. + # In case if you don't want the helper to generate this hidden field you can specify <tt>include_hidden: false</tt> option. # def select(object, method, choices, options = {}, html_options = {}) Tags::Select.new(object, method, self, choices, options, html_options).render diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 7f42d6e9c3..e298751062 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -52,7 +52,7 @@ module ActionView # <%= form_tag('/posts') do -%> # <div><%= submit_tag 'Save' %></div> # <% end -%> - # # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form> + # # => <form action="/posts" method="post"><div><input type="submit" name="commit" value="Save" /></div></form> # # <%= form_tag('/posts', remote: true) %> # # => <form action="/posts" method="post" data-remote="true"> diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb index bbb1af8154..d9c76366f8 100644 --- a/actionpack/lib/action_view/path_set.rb +++ b/actionpack/lib/action_view/path_set.rb @@ -5,6 +5,8 @@ module ActionView #:nodoc: attr_reader :paths + delegate :[], :include?, :pop, :size, :each, to: :paths + def initialize(paths = []) @paths = typecast paths end @@ -14,30 +16,10 @@ module ActionView #:nodoc: self end - def [](i) - paths[i] - end - def to_ary paths.dup end - def include?(item) - paths.include? item - end - - def pop - paths.pop - end - - def size - paths.size - end - - def each(&block) - paths.each(&block) - end - def compact PathSet.new paths.compact end diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb index bf1b5a7d22..dfef43bc9d 100644 --- a/actionpack/lib/action_view/renderer/renderer.rb +++ b/actionpack/lib/action_view/renderer/renderer.rb @@ -44,11 +44,11 @@ module ActionView private def _template_renderer #:nodoc: - @_template_renderer ||= TemplateRenderer.new(@lookup_context) + TemplateRenderer.new(@lookup_context) end def _partial_renderer #:nodoc: - @_partial_renderer ||= PartialRenderer.new(@lookup_context) + PartialRenderer.new(@lookup_context) end end end diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index aa8eac7846..731d8f9dab 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -47,6 +47,10 @@ module ActionView class_attribute :erb_implementation self.erb_implementation = Erubis + # Do not escape templates of these mime types. + class_attribute :escape_whitelist + self.escape_whitelist = ["text/plain"] + ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*") def self.call(template) @@ -78,6 +82,7 @@ module ActionView self.class.erb_implementation.new( erb, + :escape => (self.class.escape_whitelist.include? template.type), :trim => (self.class.erb_trim_mode == "-") ).src end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 8340aab4d2..6414ba3994 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -1,4 +1,6 @@ require 'abstract_unit' +# FIXME remove DummyKeyGenerator and this require in 4.1 +require 'active_support/key_generator' class FlashTest < ActionController::TestCase class TestController < ActionController::Base @@ -217,7 +219,7 @@ end class FlashIntegrationTest < ActionDispatch::IntegrationTest SessionKey = '_myapp_session' - SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33' + Generator = ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33') class TestController < ActionController::Base add_flash_types :bar @@ -291,7 +293,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest # Overwrite get to send SessionSecret in env hash def get(path, parameters = nil, env = {}) - env["action_dispatch.secret_token"] ||= SessionSecret + env["action_dispatch.key_generator"] ||= Generator super end diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index b11ad633bd..c4a94264c3 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -1,4 +1,6 @@ require 'abstract_unit' +# FIXME remove DummyKeyGenerator and this require in 4.1 +require 'active_support/key_generator' class HttpDigestAuthenticationTest < ActionController::TestCase class DummyDigestController < ActionController::Base @@ -40,8 +42,8 @@ class HttpDigestAuthenticationTest < ActionController::TestCase setup do # Used as secret in generating nonce to prevent tampering of timestamp - @secret = "session_options_secret" - @request.env["action_dispatch.secret_token"] = @secret + @secret = "4fb45da9e4ab4ddeb7580d6a35503d99" + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(@secret) end teardown do diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index fc63470174..7cc71fe6dc 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -20,26 +20,51 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal "monkey", @params.fetch(:foo) { "monkey" } end - test "permitted is sticky on accessors" do + test "not permitted is sticky on accessors" do assert !@params.slice(:person).permitted? assert !@params[:person][:name].permitted? + assert !@params[:person].except(:name).permitted? - @params.each { |key, value| assert(value.permitted?) if key == :person } + @params.each { |key, value| assert(!value.permitted?) if key == "person" } assert !@params.fetch(:person).permitted? assert !@params.values_at(:person).first.permitted? end + test "permitted is sticky on accessors" do + @params.permit! + assert @params.slice(:person).permitted? + assert @params[:person][:name].permitted? + assert @params[:person].except(:name).permitted? + + @params.each { |key, value| assert(value.permitted?) if key == "person" } + + assert @params.fetch(:person).permitted? + + assert @params.values_at(:person).first.permitted? + end + + test "not permitted is sticky on mutators" do + assert !@params.delete_if { |k| k == "person" }.permitted? + assert !@params.keep_if { |k,v| k == "person" }.permitted? + end + test "permitted is sticky on mutators" do - assert !@params.delete_if { |k| k == :person }.permitted? - assert !@params.keep_if { |k,v| k == :person }.permitted? + @params.permit! + assert @params.delete_if { |k| k == "person" }.permitted? + assert @params.keep_if { |k,v| k == "person" }.permitted? end - test "permitted is sticky beyond merges" do + test "not permitted is sticky beyond merges" do assert !@params.merge(a: "b").permitted? end + test "permitted is sticky beyond merges" do + @params.permit! + assert @params.merge(a: "b").permitted? + end + test "modifying the parameters" do @params[:person][:hometown] = "Chicago" @params[:person][:family] = { brother: "Jonas" } @@ -77,7 +102,7 @@ class ParametersPermitTest < ActiveSupport::TestCase ActionController::Parameters.permit_all_parameters = false end end - + test "permitting parameters as an array" do assert_equal "32", @params[:person].permit([ :age ])[:age] end diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 209f021cf7..d87e2b85b0 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -4,7 +4,7 @@ module Admin; class User; end; end module ParamsWrapperTestHelp def with_default_wrapper_options(&block) - @controller.class._wrapper_options = {:format => [:json]} + @controller.class._set_wrapper_options({:format => [:json]}) @controller.class.inherited(@controller.class) yield end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index aa33f01d02..859ed1466b 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -646,6 +646,10 @@ class TestController < ActionController::Base render :partial => "customer", :spacer_template => "partial_only", :collection => [ Customer.new("david"), Customer.new("mary") ] end + def partial_collection_with_spacer_which_uses_render + render :partial => "customer", :spacer_template => "partial_with_partial", :collection => [ Customer.new("david"), Customer.new("mary") ] + end + def partial_collection_shorthand_with_locals render :partial => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } end @@ -1445,6 +1449,12 @@ class RenderTest < ActionController::TestCase assert_template :partial => '_customer' end + def test_partial_collection_with_spacer_which_uses_render + get :partial_collection_with_spacer_which_uses_render + assert_equal "Hello: davidpartial html\npartial with partial\nHello: mary", @response.body + assert_template :partial => '_customer' + end + def test_partial_collection_shorthand_with_locals get :partial_collection_shorthand_with_locals assert_equal "Bonjour: davidBonjour: mary", @response.body diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index ab1bd0e3b6..718d06ef38 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -104,7 +104,7 @@ module ShowExceptions get '/', {}, 'HTTP_ACCEPT' => 'text/json' assert_response :internal_server_error assert_equal 'text/plain', response.content_type.to_s - + ensure @app.instance_variable_set(:@exceptions_app, @exceptions_app) $stderr = STDERR end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 347b3b3b5a..ffa91d63c4 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -1,4 +1,6 @@ require 'abstract_unit' +# FIXME remove DummyKeyGenerator and this require in 4.1 +require 'active_support/key_generator' class CookiesTest < ActionController::TestCase class TestController < ActionController::Base @@ -65,6 +67,11 @@ class CookiesTest < ActionController::TestCase head :ok end + def set_encrypted_cookie + cookies.encrypted[:foo] = 'bar' + head :ok + end + def raise_data_overflow cookies.signed[:foo] = 'bye!' * 1024 head :ok @@ -146,7 +153,10 @@ class CookiesTest < ActionController::TestCase def setup super - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" + @request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") + @request.env["action_dispatch.signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33" + @request.env["action_dispatch.encrypted_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33" + @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33" @request.host = "www.nextangle.com" end @@ -296,6 +306,16 @@ class CookiesTest < ActionController::TestCase assert_equal 45, @controller.send(:cookies).signed[:user_id] end + def test_encrypted_cookie + get :set_encrypted_cookie + cookies = @controller.send :cookies + assert_not_equal 'bar', cookies[:foo] + assert_raises TypeError do + cookies.signed[:foo] + end + assert_equal 'bar', cookies.encrypted[:foo] + end + def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature get :set_signed_cookie assert_nil @controller.send(:cookies).signed[:non_existant_attribute] @@ -329,29 +349,29 @@ class CookiesTest < ActionController::TestCase def test_raises_argument_error_if_missing_secret assert_raise(ArgumentError, nil.inspect) { - @request.env["action_dispatch.secret_token"] = nil + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new(nil) get :set_signed_cookie } assert_raise(ArgumentError, ''.inspect) { - @request.env["action_dispatch.secret_token"] = "" + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("") get :set_signed_cookie } end def test_raises_argument_error_if_secret_is_probably_insecure assert_raise(ArgumentError, "password".inspect) { - @request.env["action_dispatch.secret_token"] = "password" + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("password") get :set_signed_cookie } assert_raise(ArgumentError, "secret".inspect) { - @request.env["action_dispatch.secret_token"] = "secret" + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("secret") get :set_signed_cookie } assert_raise(ArgumentError, "12345678901234567890123456789".inspect) { - @request.env["action_dispatch.secret_token"] = "12345678901234567890123456789" + @request.env["action_dispatch.key_generator"] = ActiveSupport::DummyKeyGenerator.new("12345678901234567890123456789") get :set_signed_cookie } end diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index cfbf970a37..113608ecf4 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -241,6 +241,11 @@ module TestGenerationPrefix assert_equal "/something/", app_object.root_path end + test "[OBJECT] generating application's route includes default_url_options[:trailing_slash]" do + RailsApplication.routes.default_url_options[:trailing_slash] = true + assert_equal "/awesome/blog/posts", engine_object.posts_path + end + test "[OBJECT] generating engine's route with url_for" do path = engine_object.url_for(:controller => "inside_engine_generating", :action => "show", diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index e2964f9071..f2bacf3e20 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' class RequestTest < ActiveSupport::TestCase def url_for(options = {}) - options.reverse_merge!(:host => 'www.example.com') + options = { host: 'www.example.com' }.merge!(options) ActionDispatch::Http::URL.url_for(options) end @@ -25,6 +25,8 @@ class RequestTest < ActiveSupport::TestCase assert_equal 'http://www.example.com/', url_for(:trailing_slash => true) assert_equal 'http://dhh:supersecret@www.example.com', url_for(:user => 'dhh', :password => 'supersecret') assert_equal 'http://www.example.com?search=books', url_for(:params => { :search => 'books' }) + assert_equal 'http://www.example.com?params=', url_for(:params => '') + assert_equal 'http://www.example.com?params=1', url_for(:params => 1) end test "remote ip" do @@ -355,7 +357,6 @@ class RequestTest < ActiveSupport::TestCase assert_equal "/of/some/uri", request.path_info end - test "host with default port" do request = stub_request 'HTTP_HOST' => 'rubyonrails.org:80' assert_equal "rubyonrails.org", request.host_with_port @@ -577,16 +578,16 @@ class RequestTest < ActiveSupport::TestCase test "formats with accept header" do request = stub_request 'HTTP_ACCEPT' => 'text/html' request.expects(:parameters).at_least_once.returns({}) - assert_equal [ Mime::HTML ], request.formats + assert_equal [Mime::HTML], request.formats request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8', 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" request.expects(:parameters).at_least_once.returns({}) - assert_equal with_set(Mime::XML), request.formats + assert_equal [Mime::XML], request.formats request = stub_request request.expects(:parameters).at_least_once.returns({ :format => :txt }) - assert_equal with_set(Mime::TEXT), request.formats + assert_equal [Mime::TEXT], request.formats request = stub_request request.expects(:parameters).at_least_once.returns({ :format => :unknown }) @@ -811,8 +812,4 @@ protected ActionDispatch::Http::URL.tld_length = tld_length ActionDispatch::Request.new(env) end - - def with_set(*args) - args - end end diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index 41fa036a92..1677dee524 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -1,9 +1,12 @@ require 'abstract_unit' require 'stringio' +# FIXME remove DummyKeyGenerator and this require in 4.1 +require 'active_support/key_generator' class CookieStoreTest < ActionDispatch::IntegrationTest SessionKey = '_myapp_session' SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33' + Generator = ActiveSupport::DummyKeyGenerator.new(SessionSecret) Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, :digest => 'SHA1') SignedBar = Verifier.generate(:foo => "bar", :session_id => SecureRandom.hex(16)) @@ -330,7 +333,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest # Overwrite get to send SessionSecret in env hash def get(path, parameters = nil, env = {}) - env["action_dispatch.secret_token"] ||= SessionSecret + env["action_dispatch.key_generator"] ||= Generator super end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 8bd8eff3c0..f9ce63fcb0 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -19,6 +19,8 @@ class DateHelperTest < ActionView::TestCase end def assert_distance_of_time_in_words(from, to=nil) + Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn) + to ||= from # 0..1 minute with :include_seconds => true @@ -121,6 +123,9 @@ class DateHelperTest < ActionView::TestCase assert_equal "about 4 hours", distance_of_time_in_words(from + 4.hours, to) assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => true) assert_equal "less than a minute", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => false) + + ensure + Fixnum.send :public, :/ end def test_distance_in_words diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 86ba5f3b4d..ed9d303158 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -1,3 +1,4 @@ +# encoding: US-ASCII require "abstract_unit" require "logger" @@ -25,6 +26,10 @@ class TestERBTemplate < ActiveSupport::TestCase "Hello" end + def apostrophe + "l'apostrophe" + end + def partial ActionView::Template.new( "<%= @virtual_path %>", @@ -47,7 +52,7 @@ class TestERBTemplate < ActiveSupport::TestCase end end - def new_template(body = "<%= hello %>", details = {}) + def new_template(body = "<%= hello %>", details = { format: :html }) ActionView::Template.new(body, "hello template", details.fetch(:handler) { ERBHandler }, {:virtual_path => "hello"}.merge!(details)) end @@ -71,6 +76,16 @@ class TestERBTemplate < ActiveSupport::TestCase assert_equal "Hello", render end + def test_basic_template_does_html_escape + @template = new_template("<%= apostrophe %>") + assert_equal "l'apostrophe", render + end + + def test_text_template_does_not_html_escape + @template = new_template("<%= apostrophe %>", format: :text) + assert_equal "l'apostrophe", render + end + def test_raw_template @template = new_template("<%= hello %>", :handler => ActionView::Template::Handlers::Raw.new) assert_equal "<%= hello %>", render diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index aa42bf762f..133bb558a9 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,5 +1,9 @@ ## Rails 4.0.0 (unreleased) ## +* Use BCrypt's MIN_COST in the test environment for speedier tests when using `has_secure_pasword`. + + *Brian Cardarella + Jeremy Kemper + Trevor Turk* + * Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to protect attributes from mass assignment when non-permitted attributes are passed. diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index be5d5d3ca8..51655fe3da 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -8,15 +8,17 @@ Gem::Specification.new do |s| s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.' s.required_ruby_version = '>= 1.9.3' - s.license = 'MIT' - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.license = 'MIT' + + s.author = 'David Heinemeier Hansson' + s.email = 'david@loudthinking.com' + s.homepage = 'http://www.rubyonrails.org' s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*'] s.require_path = 'lib' - s.add_dependency('activesupport', version) - s.add_dependency('builder', '~> 3.1.0') + s.add_dependency 'activesupport', version + + s.add_dependency 'builder', '~> 3.1.0' end diff --git a/activemodel/examples/validations.rb b/activemodel/examples/validations.rb index 0b2706076f..a56ec4db39 100644 --- a/activemodel/examples/validations.rb +++ b/activemodel/examples/validations.rb @@ -22,8 +22,8 @@ class Person end person1 = Person.new -p person1.valid? -person1.errors +p person1.valid? # => false +p person1.errors.messages # => {:name=>["can't be blank"]} person2 = Person.new(:name => "matz") -p person2.valid? +p person2.valid? # => true diff --git a/activemodel/lib/active_model/deprecated_mass_assignment_security.rb b/activemodel/lib/active_model/deprecated_mass_assignment_security.rb index 2ea69991fc..1f409c87b9 100644 --- a/activemodel/lib/active_model/deprecated_mass_assignment_security.rb +++ b/activemodel/lib/active_model/deprecated_mass_assignment_security.rb @@ -5,14 +5,16 @@ module ActiveModel module ClassMethods # :nodoc: def attr_protected(*args) raise "`attr_protected` is extracted out of Rails into a gem. " \ - "Please use new recommended protection model for params " \ - "or add `protected_attributes` to your Gemfile to use old one." + "Please use new recommended protection model for params" \ + "(strong_parameters) or add `protected_attributes` to your " \ + "Gemfile to use old one." end def attr_accessible(*args) raise "`attr_accessible` is extracted out of Rails into a gem. " \ - "Please use new recommended protection model for params " \ - "or add `protected_attributes` to your Gemfile to use old one." + "Please use new recommended protection model for params" \ + "(strong_parameters) or add `protected_attributes` to your " \ + "Gemfile to use old one." end end end diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 6882b59e26..c82d4f012c 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -308,7 +308,7 @@ module ActiveModel # person.errors.messages # # => {:name=>["can't be empty"]} def add_on_empty(attributes, options = {}) - [attributes].flatten.each do |attribute| + Array(attributes).each do |attribute| value = @base.send(:read_attribute_for_validation, attribute) is_empty = value.respond_to?(:empty?) ? value.empty? : false add(attribute, :empty, options) if value.nil? || is_empty @@ -322,7 +322,7 @@ module ActiveModel # person.errors.messages # # => {:name=>["can't be blank"]} def add_on_blank(attributes, options = {}) - [attributes].flatten.each do |attribute| + Array(attributes).each do |attribute| value = @base.send(:read_attribute_for_validation, attribute) add(attribute, :blank, options) if value.blank? end diff --git a/activemodel/lib/active_model/forbidden_attributes_protection.rb b/activemodel/lib/active_model/forbidden_attributes_protection.rb index 4c05b19cba..7468f95548 100644 --- a/activemodel/lib/active_model/forbidden_attributes_protection.rb +++ b/activemodel/lib/active_model/forbidden_attributes_protection.rb @@ -16,7 +16,7 @@ module ActiveModel module ForbiddenAttributesProtection # :nodoc: protected - def sanitize_for_mass_assignment(attributes, options = {}) + def sanitize_for_mass_assignment(attributes) if attributes.respond_to?(:permitted?) && !attributes.permitted? raise ActiveModel::ForbiddenAttributesError else diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index eb8265d8c6..264880eecd 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -263,10 +263,10 @@ module ActiveModel # namespaced models regarding whether it's inside isolated engine. # # # For isolated engine: - # ActiveModel::Naming.route_key(Blog::Post) #=> post + # ActiveModel::Naming.singular_route_key(Blog::Post) #=> post # # # For shared engine: - # ActiveModel::Naming.route_key(Blog::Post) #=> blog_post + # ActiveModel::Naming.singular_route_key(Blog::Post) #=> blog_post def self.singular_route_key(record_or_class) model_name_from_record_or_class(record_or_class).singular_route_key end diff --git a/activemodel/lib/active_model/railtie.rb b/activemodel/lib/active_model/railtie.rb index 75cde900e3..1671eb7bd4 100644 --- a/activemodel/lib/active_model/railtie.rb +++ b/activemodel/lib/active_model/railtie.rb @@ -4,5 +4,9 @@ require "rails" module ActiveModel class Railtie < Rails::Railtie # :nodoc: config.eager_load_namespaces << ActiveModel + + initializer "active_model.secure_password" do + ActiveModel::SecurePassword.min_cost = Rails.env.test? + end end end diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index 4b328b399a..6644b60609 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -2,6 +2,9 @@ module ActiveModel module SecurePassword extend ActiveSupport::Concern + class << self; attr_accessor :min_cost; end + self.min_cost = false + module ClassMethods # Adds methods to set and authenticate against a BCrypt password. # This mechanism requires you to have a password_digest attribute. @@ -11,6 +14,10 @@ module ActiveModel # you wish to turn off validations, pass <tt>validations: false</tt> as an # argument. You can add more validations by hand if need be. # + # If you don't need the confirmation validation, just don't set any + # value to the password_confirmation attribute and the the validation + # will not be triggered. + # # You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use #has_secure_password: # # gem 'bcrypt-ruby', '~> 3.0.0' @@ -88,7 +95,8 @@ module ActiveModel def password=(unencrypted_password) unless unencrypted_password.blank? @password = unencrypted_password - self.password_digest = BCrypt::Password.create(unencrypted_password) + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine::DEFAULT_COST + self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost) end end end diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index 4651154934..1eb0716891 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -104,7 +104,7 @@ module ActiveModel raise ArgumentError, "You need to supply at least one attribute" if attributes.empty? raise ArgumentError, "You need to supply at least one validation" if validations.empty? - defaults.merge!(:attributes => attributes) + defaults[:attributes] = attributes validations.each do |key, options| next unless options diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index c795dc9dcd..629b157fed 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -135,7 +135,7 @@ module ActiveModel # and instead be made available through the +attributes+ reader. def initialize(options) @attributes = Array(options.delete(:attributes)) - raise ":attributes cannot be blank" if @attributes.empty? + raise ArgumentError, ":attributes cannot be blank" if @attributes.empty? super check_validity! end diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index 3bc0d58351..293ce07f4e 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -7,7 +7,7 @@ class ErrorsTest < ActiveModel::TestCase @errors = ActiveModel::Errors.new(self) end - attr_accessor :name + attr_accessor :name, :age attr_reader :errors def validate! @@ -201,5 +201,43 @@ class ErrorsTest < ActiveModel::TestCase person.errors.generate_message(:name, :blank) } end + + test "add_on_empty generates message" do + person = Person.new + person.errors.expects(:generate_message).with(:name, :empty, {}) + person.errors.add_on_empty :name + end + + test "add_on_empty generates message for multiple attributes" do + person = Person.new + person.errors.expects(:generate_message).with(:name, :empty, {}) + person.errors.expects(:generate_message).with(:age, :empty, {}) + person.errors.add_on_empty [:name, :age] + end + + test "add_on_empty generates message with custom default message" do + person = Person.new + person.errors.expects(:generate_message).with(:name, :empty, {:message => 'custom'}) + person.errors.add_on_empty :name, :message => 'custom' + end + + test "add_on_blank generates message" do + person = Person.new + person.errors.expects(:generate_message).with(:name, :blank, {}) + person.errors.add_on_blank :name + end + + test "add_on_blank generates message for multiple attributes" do + person = Person.new + person.errors.expects(:generate_message).with(:name, :blank, {}) + person.errors.expects(:generate_message).with(:age, :blank, {}) + person.errors.add_on_blank [:name, :age] + end + + test "add_on_blank generates message with custom default message" do + person = Person.new + person.errors.expects(:generate_message).with(:name, :blank, {:message => 'custom'}) + person.errors.add_on_blank :name, :message => 'custom' + end end diff --git a/activemodel/test/cases/railtie_test.rb b/activemodel/test/cases/railtie_test.rb new file mode 100644 index 0000000000..f89a288f8f --- /dev/null +++ b/activemodel/test/cases/railtie_test.rb @@ -0,0 +1,28 @@ +require 'cases/helper' +require 'active_support/testing/isolation' + +class RailtieTest < ActiveModel::TestCase + include ActiveSupport::Testing::Isolation + + def setup + require 'rails/all' + + @app ||= Class.new(::Rails::Application).tap do |app| + app.config.eager_load = false + end + end + + test 'secure password min_cost is false in the development environment' do + Rails.env = 'development' + @app.initialize! + + assert_equal false, ActiveModel::SecurePassword.min_cost + end + + test 'secure password min_cost is true in the test environment' do + Rails.env = 'test' + @app.initialize! + + assert_equal true, ActiveModel::SecurePassword.min_cost + end +end diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 509e2fdbb5..c7e93370ec 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -63,10 +63,21 @@ class SecurePasswordTest < ActiveModel::TestCase @user.run_callbacks :create end end - + test "Oauthed user can be created with blank digest" do assert_nothing_raised do @oauthed_user.run_callbacks :create end end + + test "Password digest cost defaults to bcrypt default cost" do + @user.password = "secret" + assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost + end + + test "Password digest cost can be set to bcrypt min cost to speed up tests" do + ActiveModel::SecurePassword.min_cost = true + @user.password = "secret" + assert_equal BCrypt::Engine::MIN_COST, @user.password_digest.cost + end end diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index 4f8b7327c0..4c01b47608 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -21,26 +21,6 @@ class I18nValidationTest < ActiveModel::TestCase I18n.backend = @old_backend end - def test_errors_add_on_empty_generates_message - @person.errors.expects(:generate_message).with(:title, :empty, {}) - @person.errors.add_on_empty :title - end - - def test_errors_add_on_empty_generates_message_with_custom_default_message - @person.errors.expects(:generate_message).with(:title, :empty, {:message => 'custom'}) - @person.errors.add_on_empty :title, :message => 'custom' - end - - def test_errors_add_on_blank_generates_message - @person.errors.expects(:generate_message).with(:title, :blank, {}) - @person.errors.add_on_blank :title - end - - def test_errors_add_on_blank_generates_message_with_custom_default_message - @person.errors.expects(:generate_message).with(:title, :blank, {:message => 'custom'}) - @person.errors.add_on_blank :title, :message => 'custom' - end - def test_full_message_encoding I18n.backend.store_translations('en', :errors => { :messages => { :too_short => '猫舌' }}) diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index 07c1bd0533..457f553661 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -151,7 +151,7 @@ class ValidatesWithTest < ActiveModel::TestCase end test "each validator expects attributes to be given" do - assert_raise RuntimeError do + assert_raise ArgumentError do Topic.validates_with(ValidatorPerEachAttribute) end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 28d8b066e0..f166d73801 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,53 @@ ## Rails 4.0.0 (unreleased) ## +* Fix postgresql adapter to handle BC timestamps correctly + + HistoryEvent.create!(:name => "something", :occured_at => Date.new(0) - 5.years) + + *Bogdan Gusiev* + +* When running migrations on Postgresql, the `:limit` option for `binary` and `text` columns is silently dropped. + Previously, these migrations caused sql exceptions, because Postgresql doesn't support limits on these types. + + *Victor Costan* + +* Don't change STI type when calling `ActiveRecord::Base#becomes`. + Add `ActiveRecord::Base#becomes!` with the previous behavior. + + See #3023 for more information. + + *Thomas Hollstegge* + +* `rename_index` can be used inside a `change_table` block. + + change_table :accounts do |t| + t.rename_index :user_id, :account_id + end + + *Jarek Radosz* + +* `#pluck` can be used on a relation with `select` clause. Fix #7551 + + Example: + + Topic.select([:approved, :id]).order(:id).pluck(:id) + + *Yves Senn* + +* Do not create useless database transaction when building `has_one` association. + + Example: + + User.has_one :profile + User.new.build_profile + + *Bogdan Gusiev* + +* `:counter_cache` option for `has_many` associations to support custom named counter caches. + Fix #7993 + + *Yves Senn* + * Deprecate the possibility to pass a string as third argument of `add_index`. Pass `unique: true` instead. @@ -19,7 +67,7 @@ *Nikita Afanasenko* -* Use query cache/uncache when using DATABASE_URL. +* Use query cache/uncache when using `DATABASE_URL`. Fix #6951. *kennyj* diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 53791d96ef..31ddb01123 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -1,4 +1,4 @@ -version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip +version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -8,21 +8,22 @@ Gem::Specification.new do |s| s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.' s.required_ruby_version = '>= 1.9.3' - s.license = 'MIT' - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.license = 'MIT' + + s.author = 'David Heinemeier Hansson' + s.email = 'david@loudthinking.com' + s.homepage = 'http://www.rubyonrails.org' s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*'] s.require_path = 'lib' - s.extra_rdoc_files = %w( README.rdoc ) + s.extra_rdoc_files = %w(README.rdoc) s.rdoc_options.concat ['--main', 'README.rdoc'] - s.add_dependency('activesupport', version) - s.add_dependency('activemodel', version) - s.add_dependency('arel', '~> 3.0.2') + s.add_dependency 'activesupport', version + s.add_dependency 'activemodel', version - s.add_dependency('activerecord-deprecated_finders', '0.0.1') + s.add_dependency 'arel', '~> 3.0.2' + s.add_dependency 'activerecord-deprecated_finders', '0.0.1' end diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 3db8e0716b..8101f7a45e 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -16,8 +16,8 @@ module ActiveRecord # the database). # # class Customer < ActiveRecord::Base - # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount) - # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ] + # composed_of :balance, class_name: "Money", mapping: %w(balance amount) + # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] # end # # The customer class now has the following methods to manipulate the value objects: @@ -138,15 +138,15 @@ module ActiveRecord # # class NetworkResource < ActiveRecord::Base # composed_of :cidr, - # :class_name => 'NetAddr::CIDR', - # :mapping => [ %w(network_address network), %w(cidr_range bits) ], - # :allow_nil => true, - # :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") }, - # :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) } + # class_name: 'NetAddr::CIDR', + # mapping: [ %w(network_address network), %w(cidr_range bits) ], + # allow_nil: true, + # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") }, + # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) } # end # # # This calls the :constructor - # network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24) + # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24) # # # These assignments will both use the :converter # network_resource.cidr = [ '192.168.2.1', 8 ] @@ -165,7 +165,7 @@ module ActiveRecord # by specifying an instance of the value object in the conditions hash. The following example # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD": # - # Customer.where(:balance => Money.new(20, "USD")).all + # Customer.where(balance: Money.new(20, "USD")).all # module ClassMethods # Adds reader and writer methods for manipulating a value object: @@ -197,17 +197,17 @@ module ActiveRecord # can return nil to skip the assignment. # # Option examples: - # composed_of :temperature, :mapping => %w(reading celsius) - # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), - # :converter => Proc.new { |balance| balance.to_money } - # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ] + # composed_of :temperature, mapping: %w(reading celsius) + # composed_of :balance, class_name: "Money", mapping: %w(balance amount), + # converter: Proc.new { |balance| balance.to_money } + # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] # composed_of :gps_location - # composed_of :gps_location, :allow_nil => true + # composed_of :gps_location, allow_nil: true # composed_of :ip_address, - # :class_name => 'IPAddr', - # :mapping => %w(ip to_i), - # :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) }, - # :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) } + # class_name: 'IPAddr', + # mapping: %w(ip to_i), + # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) }, + # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) } # def composed_of(part_id, options = {}) options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 69b95f814c..651b17920c 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1,6 +1,9 @@ require 'active_support/core_ext/enumerable' require 'active_support/core_ext/string/conversions' require 'active_support/core_ext/module/remove_method' +require 'active_support/dependencies/autoload' +require 'active_support/concern' +require 'active_record/errors' module ActiveRecord class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc: @@ -305,11 +308,11 @@ module ActiveRecord # end # class Programmer < ActiveRecord::Base # has_many :assignments - # has_many :projects, :through => :assignments + # has_many :projects, through: :assignments # end # class Project < ActiveRecord::Base # has_many :assignments - # has_many :programmers, :through => :assignments + # has_many :programmers, through: :assignments # end # # For the second way, use +has_and_belongs_to_many+ in both models. This requires a join table @@ -426,7 +429,7 @@ module ActiveRecord # object from an association collection. # # class Project - # has_and_belongs_to_many :developers, :after_add => :evaluate_velocity + # has_and_belongs_to_many :developers, after_add: :evaluate_velocity # # def evaluate_velocity(developer) # ... @@ -437,7 +440,7 @@ module ActiveRecord # # class Project # has_and_belongs_to_many :developers, - # :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] + # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] # end # # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+. @@ -507,7 +510,7 @@ module ActiveRecord # # class Author < ActiveRecord::Base # has_many :authorships - # has_many :books, :through => :authorships + # has_many :books, through: :authorships # end # # class Authorship < ActiveRecord::Base @@ -523,7 +526,7 @@ module ActiveRecord # # class Firm < ActiveRecord::Base # has_many :clients - # has_many :invoices, :through => :clients + # has_many :invoices, through: :clients # end # # class Client < ActiveRecord::Base @@ -543,7 +546,7 @@ module ActiveRecord # # class Group < ActiveRecord::Base # has_many :users - # has_many :avatars, :through => :users + # has_many :avatars, through: :users # end # # class User < ActiveRecord::Base @@ -571,7 +574,7 @@ module ActiveRecord # works correctly (where <tt>tags</tt> is a +has_many+ <tt>:through</tt> association): # # @post = Post.first - # @tag = @post.tags.build :name => "ruby" + # @tag = @post.tags.build name: "ruby" # @tag.save # # The last line ought to save the through record (a <tt>Taggable</tt>). This will only work if the @@ -579,7 +582,7 @@ module ActiveRecord # # class Taggable < ActiveRecord::Base # belongs_to :post - # belongs_to :tag, :inverse_of => :taggings + # belongs_to :tag, inverse_of: :taggings # end # # == Nested Associations @@ -589,8 +592,8 @@ module ActiveRecord # # class Author < ActiveRecord::Base # has_many :posts - # has_many :comments, :through => :posts - # has_many :commenters, :through => :comments + # has_many :comments, through: :posts + # has_many :commenters, through: :comments # end # # class Post < ActiveRecord::Base @@ -608,12 +611,12 @@ module ActiveRecord # # class Author < ActiveRecord::Base # has_many :posts - # has_many :commenters, :through => :posts + # has_many :commenters, through: :posts # end # # class Post < ActiveRecord::Base # has_many :comments - # has_many :commenters, :through => :comments + # has_many :commenters, through: :comments # end # # class Comment < ActiveRecord::Base @@ -632,11 +635,11 @@ module ActiveRecord # must adhere to. # # class Asset < ActiveRecord::Base - # belongs_to :attachable, :polymorphic => true + # belongs_to :attachable, polymorphic: true # end # # class Post < ActiveRecord::Base - # has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use. + # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use. # end # # @asset.attachable = @post @@ -653,7 +656,7 @@ module ActiveRecord # column in the posts table. # # class Asset < ActiveRecord::Base - # belongs_to :attachable, :polymorphic => true + # belongs_to :attachable, polymorphic: true # # def attachable_type=(sType) # super(sType.to_s.classify.constantize.base_class.to_s) @@ -661,8 +664,8 @@ module ActiveRecord # end # # class Post < ActiveRecord::Base - # # because we store "Post" in attachable_type now :dependent => :destroy will work - # has_many :assets, :as => :attachable, :dependent => :destroy + # # because we store "Post" in attachable_type now dependent: :destroy will work + # has_many :assets, as: :attachable, dependent: :destroy # end # # class GuestPost < Post @@ -724,7 +727,7 @@ module ActiveRecord # # To include a deep hierarchy of associations, use a hash: # - # Post.includes(:author, {:comments => {:author => :gravatar}}).each do |post| + # Post.includes(:author, {comments: {author: :gravatar}}).each do |post| # # That'll grab not only all the comments but all their authors and gravatar pictures. # You can mix and match symbols, arrays and hashes in any combination to describe the @@ -749,13 +752,13 @@ module ActiveRecord # In the above example posts with no approved comments are not returned at all, because # the conditions apply to the SQL statement as a whole and not just to the association. # You must disambiguate column references for this fallback to happen, for example - # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not. + # <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not. # # If you do want eager load only some members of an association it is usually more natural # to include an association which has conditions defined on it: # # class Post < ActiveRecord::Base - # has_many :approved_comments, -> { where approved: true }, :class_name => 'Comment' + # has_many :approved_comments, -> { where approved: true }, class_name: 'Comment' # end # # Post.includes(:approved_comments) @@ -767,7 +770,7 @@ module ActiveRecord # returning all the associated objects: # # class Picture < ActiveRecord::Base - # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, :class_name => 'Comment' + # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment' # end # # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments. @@ -775,7 +778,7 @@ module ActiveRecord # Eager loading is supported with polymorphic associations. # # class Address < ActiveRecord::Base - # belongs_to :addressable, :polymorphic => true + # belongs_to :addressable, polymorphic: true # end # # A call that tries to eager load the addressable model @@ -809,10 +812,10 @@ module ActiveRecord # # TreeMixin.joins(:children) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... - # TreeMixin.joins(:children => :parent) + # TreeMixin.joins(children: :parent) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... # INNER JOIN parents_mixins ... - # TreeMixin.joins(:children => {:parent => :children}) + # TreeMixin.joins(children: {parent: :children}) # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... # INNER JOIN parents_mixins ... # INNER JOIN mixins childrens_mixins_2 @@ -821,10 +824,10 @@ module ActiveRecord # # Post.joins(:categories) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... - # Post.joins(:categories => :posts) + # Post.joins(categories: :posts) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories - # Post.joins(:categories => {:posts => :categories}) + # Post.joins(categories: {posts: :categories}) # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2 @@ -868,7 +871,7 @@ module ActiveRecord # # module Billing # class Account < ActiveRecord::Base - # belongs_to :firm, :class_name => "MyApplication::Business::Firm" + # belongs_to :firm, class_name: "MyApplication::Business::Firm" # end # end # end @@ -910,16 +913,16 @@ module ActiveRecord # example, if we changed our model definitions to: # # class Dungeon < ActiveRecord::Base - # has_many :traps, :inverse_of => :dungeon - # has_one :evil_wizard, :inverse_of => :dungeon + # has_many :traps, inverse_of: :dungeon + # has_one :evil_wizard, inverse_of: :dungeon # end # # class Trap < ActiveRecord::Base - # belongs_to :dungeon, :inverse_of => :traps + # belongs_to :dungeon, inverse_of: :traps # end # # class EvilWizard < ActiveRecord::Base - # belongs_to :dungeon, :inverse_of => :evil_wizard + # belongs_to :dungeon, inverse_of: :evil_wizard # end # # Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same @@ -942,7 +945,7 @@ module ActiveRecord # For example: # # class Author - # has_many :posts, :dependent => :destroy + # has_many :posts, dependent: :destroy # end # Author.find(1).destroy # => Will destroy all of the author's posts, too # @@ -1026,12 +1029,12 @@ module ActiveRecord # parent object. # [collection.delete(object, ...)] # Removes one or more objects from the collection by setting their foreign keys to +NULL+. - # Objects will be in addition destroyed if they're associated with <tt>:dependent => :destroy</tt>, - # and deleted if they're associated with <tt>:dependent => :delete_all</tt>. + # Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>, + # and deleted if they're associated with <tt>dependent: :delete_all</tt>. # # If the <tt>:through</tt> option is used, then the join records are deleted (rather than - # nullified) by default, but you can specify <tt>:dependent => :destroy</tt> or - # <tt>:dependent => :nullify</tt> to override this. + # nullified) by default, but you can specify <tt>dependent: :destroy</tt> or + # <tt>dependent: :nullify</tt> to override this. # [collection.destroy(object, ...)] # Removes one or more objects from the collection by running <tt>destroy</tt> on # each record, regardless of any dependent option, ensuring callbacks are run. @@ -1049,8 +1052,8 @@ module ActiveRecord # method loads the models and calls <tt>collection=</tt>. See above. # [collection.clear] # Removes every object from the collection. This destroys the associated objects if they - # are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the - # database if <tt>:dependent => :delete_all</tt>, otherwise sets their foreign keys to +NULL+. + # are associated with <tt>dependent: :destroy</tt>, deletes them directly from the + # database if <tt>dependent: :delete_all</tt>, otherwise sets their foreign keys to +NULL+. # If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models. # Join models are directly deleted. # [collection.empty?] @@ -1078,7 +1081,7 @@ module ActiveRecord # === Example # # Example: A Firm class declares <tt>has_many :clients</tt>, which will add: - # * <tt>Firm#clients</tt> (similar to <tt>Clients.all :conditions => ["firm_id = ?", id]</tt>) + # * <tt>Firm#clients</tt> (similar to <tt>Clients.all conditions: ["firm_id = ?", id]</tt>) # * <tt>Firm#clients<<</tt> # * <tt>Firm#clients.delete</tt> # * <tt>Firm#clients.destroy</tt> @@ -1088,8 +1091,8 @@ module ActiveRecord # * <tt>Firm#clients.clear</tt> # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>) # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>) - # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>) - # * <tt>Firm#clients.exists?(:name => 'ACME')</tt> (similar to <tt>Client.exists?(:name => 'ACME', :firm_id => firm.id)</tt>) + # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, conditions: "firm_id = #{id}")</tt>) + # * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>) # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>) # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>) # The declaration can also include an options hash to specialize the behavior of the association. @@ -1122,6 +1125,9 @@ module ActiveRecord # If using with the <tt>:through</tt> option, the association on the join model must be # a +belongs_to+, and the records which get deleted are the join records, rather than # the associated records. + # [:counter_cache] + # This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option, + # when you customized the name of your <tt>:counter_cache</tt> on the <tt>belongs_to</tt> association. # [:as] # Specifies a polymorphic interface (See <tt>belongs_to</tt>). # [:through] @@ -1143,7 +1149,7 @@ module ActiveRecord # [:source] # Specifies the source association name used by <tt>has_many :through</tt> queries. # Only use it if the name cannot be inferred from the association. - # <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or + # <tt>has_many :subscribers, through: :subscriptions</tt> will look for either <tt>:subscribers</tt> or # <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given. # [:source_type] # Specifies type of the source association used by <tt>has_many :through</tt> queries where the source @@ -1207,7 +1213,7 @@ module ActiveRecord # === Example # # An Account class declares <tt>has_one :beneficiary</tt>, which will add: - # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.first(:conditions => "account_id = #{id}")</tt>) + # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.first(conditions: "account_id = #{id}")</tt>) # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>) # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) @@ -1247,7 +1253,7 @@ module ActiveRecord # [:source] # Specifies the source association name used by <tt>has_one :through</tt> queries. # Only use it if the name cannot be inferred from the association. - # <tt>has_one :favorite, :through => :favorites</tt> will look for a + # <tt>has_one :favorite, through: :favorites</tt> will look for a # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given. # [:source_type] # Specifies type of the source association used by <tt>has_one :through</tt> queries where the source @@ -1267,11 +1273,11 @@ module ActiveRecord # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. # # Option examples: - # has_one :credit_card, :dependent => :destroy # destroys the associated credit card - # has_one :credit_card, :dependent => :nullify # updates the associated records foreign + # has_one :credit_card, dependent: :destroy # destroys the associated credit card + # has_one :credit_card, dependent: :nullify # updates the associated records foreign # # key value to NULL rather than destroying it - # has_one :last_comment, -> { order 'posted_on' }, :class_name => "Comment" - # has_one :project_manager, -> { where role: 'project_manager' }, :class_name => "Person" + # has_one :last_comment, -> { order 'posted_on' }, class_name: "Comment" + # has_one :project_manager, -> { where role: 'project_manager' }, class_name: "Person" # has_one :attachment, as: :attachable # has_one :boss, readonly: :true # has_one :club, through: :membership @@ -1326,12 +1332,12 @@ module ActiveRecord # Specify the foreign key used for the association. By default this is guessed to be the name # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly, - # <tt>belongs_to :favorite_person, :class_name => "Person"</tt> will use a foreign key + # <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key # of "favorite_person_id". # [:foreign_type] # Specify the column used to store the associated object's type, if this is a polymorphic # association. By default this is guessed to be the name of the association with a "_type" - # suffix. So a class that defines a <tt>belongs_to :taggable, :polymorphic => true</tt> + # suffix. So a class that defines a <tt>belongs_to :taggable, polymorphic: true</tt> # association will use "taggable_type" as the default <tt>:foreign_type</tt>. # [:primary_key] # Specify the method that returns the primary key of associated object used for the association. @@ -1351,7 +1357,7 @@ module ActiveRecord # <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will # return the count cached, see note below). You can also specify a custom counter # cache column by providing a column name instead of a +true+/+false+ value to this - # option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.) + # option (e.g., <tt>counter_cache: :my_custom_counter</tt>.) # Note: Specifying a counter cache will add it to that model's list of readonly attributes # using +attr_readonly+. # [:polymorphic] @@ -1409,7 +1415,7 @@ module ActiveRecord # # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration # def change - # create_table :developers_projects, :id => false do |t| + # create_table :developers_projects, id: false do |t| # t.integer :developer_id # t.integer :project_id # end diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index 1b382f7285..fcdfc1e150 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -1,5 +1,8 @@ +require 'active_record/associations' + module ActiveRecord::Associations::Builder class CollectionAssociation < Association #:nodoc: + CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove] def valid_options diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb index ab8225460a..0d1bdd21ee 100644 --- a/activerecord/lib/active_record/associations/builder/has_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_many.rb @@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder end def valid_options - super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of] + super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache] end def valid_dependent_options diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 54215cf88d..862ff201de 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -174,8 +174,6 @@ module ActiveRecord # association, it will be used for the query. Otherwise, construct options and pass them with # scope to the target class's +count+. def count(column_name = nil, count_options = {}) - return 0 if owner.new_record? - column_name, count_options = nil, column_name if column_name.is_a?(Hash) if options[:counter_sql] || options[:finder_sql] @@ -366,6 +364,16 @@ module ActiveRecord record end + def scope(opts = {}) + scope = super() + scope.none! if opts.fetch(:nullify, true) && null_scope? + scope + end + + def null_scope? + owner.new_record? && !foreign_key_present? + end + private def custom_counter_sql diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index e73f940334..e444b0ed83 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -28,10 +28,12 @@ module ActiveRecord # is computed directly through SQL and does not trigger by itself the # instantiation of the actual post records. class CollectionProxy < Relation + delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope) + def initialize(association) #:nodoc: @association = association super association.klass, association.klass.arel_table - merge! association.scope + merge! association.scope(nullify: false) end def target diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 74864d271f..f59565ae77 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -76,7 +76,7 @@ module ActiveRecord end def cached_counter_attribute_name(reflection = reflection) - "#{reflection.name}_count" + options[:counter_cache] || "#{reflection.name}_count" end def update_counter(difference, reflection = reflection) diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 06bead41de..ee816d2392 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -28,7 +28,7 @@ module ActiveRecord # If target and record are nil, or target is equal to record, # we don't need to have transaction. if (target || record) && target != record - reflection.klass.transaction do + transaction_if(save) do remove_target!(options[:dependent]) if target && !target.destroyed? if record @@ -90,6 +90,14 @@ module ActiveRecord def nullify_owner_attributes(record) record[reflection.foreign_key] = nil end + + def transaction_if(value) + if value + reflection.klass.transaction { yield } + else + yield + end + end end end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 437fd00948..e0bfdb8f3e 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -369,14 +369,10 @@ module ActiveRecord end def typecasted_attribute_value(name) - if self.class.serialized_attributes.include?(name) - @attributes[name].serialized_value - else - # FIXME: we need @attributes to be used consistently. - # If the values stored in @attributes were already typecasted, this code - # could be simplified - read_attribute(name) - end + # FIXME: we need @attributes to be used consistently. + # If the values stored in @attributes were already typecasted, this code + # could be simplified + read_attribute(name) end end end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 5b9ed81424..47d4a938af 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -5,7 +5,7 @@ module ActiveRecord included do # Returns a hash of all the attributes that have been specified for - # serialization as keys and their class restriction as values. + # serialization as keys and their class restriction as values. class_attribute :serialized_attributes, instance_accessor: false self.serialized_attributes = {} end @@ -129,6 +129,14 @@ module ActiveRecord end end end + + def typecasted_attribute_value(name) + if self.class.serialized_attributes.include?(name) + @attributes[name].serialized_value + else + super + end + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 806dc5b1d2..427c61079a 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -64,8 +64,7 @@ module ActiveRecord private def round_usec(value) - return unless value - value.change(:usec => 0) + value.change(usec: 0) if value end end end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index a30f888a7a..907fe70522 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -16,7 +16,7 @@ module ActiveRecord # Note that it also means that associations marked for destruction won't # be destroyed directly. They will however still be marked for destruction. # - # Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>. + # Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>. # When the <tt>:autosave</tt> option is not present new associations are saved. # # == Validation @@ -37,7 +37,7 @@ module ActiveRecord # === One-to-one Example # # class Post - # has_one :author, :autosave => true + # has_one :author, autosave: true # end # # Saving changes to the parent and its associated model can now be performed @@ -81,27 +81,27 @@ module ActiveRecord # has_many :comments # :autosave option is not declared # end # - # post = Post.new(:title => 'ruby rocks') - # post.comments.build(:body => 'hello world') + # post = Post.new(title: 'ruby rocks') + # post.comments.build(body: 'hello world') # post.save # => saves both post and comment # - # post = Post.create(:title => 'ruby rocks') - # post.comments.build(:body => 'hello world') + # post = Post.create(title: 'ruby rocks') + # post.comments.build(body: 'hello world') # post.save # => saves both post and comment # - # post = Post.create(:title => 'ruby rocks') - # post.comments.create(:body => 'hello world') + # post = Post.create(title: 'ruby rocks') + # post.comments.create(body: 'hello world') # post.save # => saves both post and comment # # When <tt>:autosave</tt> is true all children are saved, no matter whether they # are new records or not: # # class Post - # has_many :comments, :autosave => true + # has_many :comments, autosave: true # end # - # post = Post.create(:title => 'ruby rocks') - # post.comments.create(:body => 'hello world') + # post = Post.create(title: 'ruby rocks') + # post.comments.create(body: 'hello world') # post.comments[0].body = 'hi everyone' # post.save # => saves both post and comment, with 'hi everyone' as body # diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index eabbd80f66..5eacb8f143 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -8,7 +8,6 @@ require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/class/delegating_attributes' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/hash/deep_merge' -require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/behavior' require 'active_support/core_ext/kernel/singleton_class' @@ -36,7 +35,7 @@ module ActiveRecord #:nodoc: # method is especially useful when you're receiving the data from somewhere else, like an # HTTP request. It works like this: # - # user = User.new(:name => "David", :occupation => "Code Artist") + # user = User.new(name: "David", occupation: "Code Artist") # user.name # => "David" # # You can also use block initialization: @@ -69,7 +68,7 @@ module ActiveRecord #:nodoc: # end # # def self.authenticate_safely_simply(user_name, password) - # where(:user_name => user_name, :password => password).first + # where(user_name: user_name, password: password).first # end # end # @@ -87,27 +86,27 @@ module ActiveRecord #:nodoc: # # Company.where( # "id = :id AND name = :name AND division = :division AND created_at > :accounting_date", - # { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' } + # { id: 3, name: "37signals", division: "First", accounting_date: '2005-01-01' } # ).first # # Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND # operator. For instance: # - # Student.where(:first_name => "Harvey", :status => 1) + # Student.where(first_name: "Harvey", status: 1) # Student.where(params[:student]) # # A range may be used in the hash to use the SQL BETWEEN operator: # - # Student.where(:grade => 9..12) + # Student.where(grade: 9..12) # # An array may be used in the hash to use the SQL IN operator: # - # Student.where(:grade => [9,11,12]) + # Student.where(grade: [9,11,12]) # # When joining tables, nested hashes or keys written in the form 'table_name.column_name' # can be used to qualify the table name of a particular condition. For instance: # - # Student.joins(:schools).where(:schools => { :category => 'public' }) + # Student.joins(:schools).where(schools: { category: 'public' }) # Student.joins(:schools).where('schools.category' => 'public' ) # # == Overwriting default accessors @@ -141,10 +140,10 @@ module ActiveRecord #:nodoc: # For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call # to determine whether the user has a name: # - # user = User.new(:name => "David") + # user = User.new(name: "David") # user.name? # => true # - # anonymous = User.new(:name => "") + # anonymous = User.new(name: "") # anonymous.name? # => false # # == Accessing attributes before they have been typecasted @@ -165,8 +164,8 @@ module ActiveRecord #:nodoc: # to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt> and thus produces finders # like <tt>Person.find_by_user_name</tt>, <tt>Person.find_all_by_last_name</tt>, and # <tt>Payment.find_by_transaction_id</tt>. Instead of writing - # <tt>Person.where(:user_name => user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>. - # And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do + # <tt>Person.where(user_name: user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>. + # And instead of writing <tt>Person.where(last_name: last_name).all</tt>, you just do # <tt>Person.find_all_by_last_name(last_name)</tt>. # # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an @@ -175,7 +174,7 @@ module ActiveRecord #:nodoc: # # It's also possible to use multiple attributes in the same find by separating them with "_and_". # - # Person.where(:user_name => user_name, :password => password).first + # Person.where(user_name: user_name, password: password).first # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder # # It's even possible to call these dynamic finder methods on relations and named scopes. @@ -189,13 +188,13 @@ module ActiveRecord #:nodoc: # unless they are given in a block. # # # No 'Summer' tag exists - # Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer") + # Tag.find_or_create_by_name("Summer") # equal to Tag.create(name: "Summer") # # # Now the 'Summer' tag does exist # Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer") # # # Now 'Bob' exist and is an 'admin' - # User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true } + # User.find_or_create_by_name('Bob', age: 40) { |u| u.admin = true } # # Adding an exclamation point (!) on to the end of <tt>find_or_create_by_</tt> will # raise an <tt>ActiveRecord::RecordInvalid</tt> error if the new record is invalid. @@ -210,7 +209,7 @@ module ActiveRecord #:nodoc: # To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of # a list of parameters. # - # Tag.find_or_create_by_name(:name => "rails", :creator => current_user) + # Tag.find_or_create_by_name(name: "rails", creator: current_user) # # That will either find an existing tag named "rails", or create a new one while setting the # user that created it. @@ -232,7 +231,7 @@ module ActiveRecord #:nodoc: # serialize :preferences # end # - # user = User.create(:preferences => { "background" => "black", "display" => large }) + # user = User.create(preferences: { "background" => "black", "display" => large }) # User.find(user.id).preferences # => { "background" => "black", "display" => large } # # You can also specify a class option as the second parameter that'll raise an exception @@ -242,7 +241,7 @@ module ActiveRecord #:nodoc: # serialize :preferences, Hash # end # - # user = User.create(:preferences => %w( one two three )) + # user = User.create(preferences: %w( one two three )) # User.find(user.id).preferences # raises SerializationTypeMismatch # # When you specify a class option, the default value for that attribute will be a new @@ -267,9 +266,9 @@ module ActiveRecord #:nodoc: # class Client < Company; end # class PriorityClient < Client; end # - # When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in + # When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in # the companies table with type = "Firm". You can then fetch this row again using - # <tt>Company.where(:name => '37signals').first</tt> and it will return a Firm object. + # <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object. # # If you don't have a type column defined in your table, single-table inheritance won't # be triggered. In that case, it'll work just like normal subclasses with no special magic @@ -333,7 +332,6 @@ module ActiveRecord #:nodoc: extend Translation extend DynamicMatchers extend Explain - extend ConnectionHandling include Persistence include ReadonlyAttributes diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 725bfffef2..1c9c627090 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -35,7 +35,7 @@ module ActiveRecord # class CreditCard < ActiveRecord::Base # # Strip everything but digits, so the user can specify "555 234 34" or # # "5552-3434" and both will mean "55523434" - # before_validation(:on => :create) do + # before_validation(on: :create) do # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number") # 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 1da95f451f..db0db272a6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -1,7 +1,7 @@ require 'thread' require 'monitor' require 'set' -require 'active_support/core_ext/module/deprecation' +require 'active_support/deprecation' module ActiveRecord # Raised when a connection could not be obtained within the connection @@ -494,10 +494,18 @@ module ActiveRecord @class_to_pool = Hash.new { |h,k| h[k] = {} } end - def connection_pools + def connection_pool_list owner_to_pool.values.compact end + def connection_pools + ActiveSupport::Deprecation.warn( + "In the next release, this will return the same as #connection_pool_list. " \ + "(An array of pools, rather than a hash mapping specs to pools.)" + ) + Hash[connection_pool_list.map { |pool| [pool.spec, pool] }] + end + def establish_connection(owner, spec) @class_to_pool.clear owner_to_pool[owner] = ConnectionAdapters::ConnectionPool.new(spec) @@ -506,23 +514,23 @@ module ActiveRecord # Returns true if there are any active connections among the connection # pools that the ConnectionHandler is managing. def active_connections? - connection_pools.any?(&:active_connection?) + connection_pool_list.any?(&:active_connection?) end # Returns any connections in use by the current thread back to the pool, # and also returns connections to the pool cached by threads that are no # longer alive. def clear_active_connections! - connection_pools.each(&:release_connection) + connection_pool_list.each(&:release_connection) end # Clears the cache which maps classes. def clear_reloadable_connections! - connection_pools.each(&:clear_reloadable_connections!) + connection_pool_list.each(&:clear_reloadable_connections!) end def clear_all_connections! - connection_pools.each(&:disconnect!) + connection_pool_list.each(&:disconnect!) end # Locate the connection of the nearest super class. This can be an 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 38960ab873..7ec6abbc45 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -324,6 +324,7 @@ module ActiveRecord # change_table :table do |t| # t.column # t.index + # t.rename_index # t.timestamps # t.change # t.change_default @@ -386,6 +387,13 @@ module ActiveRecord @base.index_exists?(@table_name, column_name, options) end + # Renames the given index on the table. + # + # t.rename_index(:user_id, :account_id) + def rename_index(index_name, new_index_name) + @base.rename_index(@table_name, index_name, new_index_name) + end + # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps # # t.timestamps 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 73aaffc146..f1e42dfbbe 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -540,7 +540,7 @@ module ActiveRecord column_type_sql << "(#{precision})" end elsif scale - raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified" + raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified" end elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit]) @@ -629,11 +629,13 @@ module ActiveRecord index_options = options[:where] ? " WHERE #{options[:where]}" : "" end else - message = "Passing a string as third argument of `add_index` is deprecated and will" + - " be removed in Rails 4.1." + - " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead" + if options + message = "Passing a string as third argument of `add_index` is deprecated and will" + + " be removed in Rails 4.1." + + " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead" - ActiveSupport::Deprecation.warn message + ActiveSupport::Deprecation.warn message + end index_type = options end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 76667616a1..e9677415cc 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -2,7 +2,7 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' require 'active_record/connection_adapters/statement_pool' require 'active_support/core_ext/hash/keys' -gem 'mysql', '~> 2.8.1' +gem 'mysql', '~> 2.9' require 'mysql' class Mysql diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index 62d091357d..c04a799b8d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -8,6 +8,8 @@ module ActiveRecord case string when 'infinity'; 1.0 / 0.0 when '-infinity'; -1.0 / 0.0 + when / BC$/ + super("-" + string.sub(/ BC$/, "")) else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 9d3fa18e3a..62a4d76928 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -129,11 +129,15 @@ module ActiveRecord # Quote date/time values for use in SQL input. Includes microseconds # if the value is a Time responding to usec. def quoted_date(value) #:nodoc: + result = super if value.acts_like?(:time) && value.respond_to?(:usec) - "#{super}.#{sprintf("%06d", value.usec)}" - else - super + result = "#{result}.#{sprintf("%06d", value.usec)}" + end + + if value.year < 0 + result = result.sub(/^-/, "") + " BC" end + result end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 82a0b662f4..7c561b6f82 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -396,6 +396,13 @@ module ActiveRecord when nil, 0..0x3fffffff; super(type) else raise(ActiveRecordError, "No binary type has byte size #{limit}.") end + when 'text' + # PostgreSQL doesn't support limits on text columns. + # The hard limit is 1Gb, according to section 8.3 in the manual. + case limit + when nil, 0..0x3fffffff; super(type) + else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.") + end when 'integer' return 'integer' unless limit @@ -422,20 +429,17 @@ module ActiveRecord # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and # requires that the ORDER BY include the distinct column. # - # distinct("posts.id", "posts.created_at desc") + # distinct("posts.id", ["posts.created_at desc"]) + # # => "DISTINCT posts.id, posts.created_at AS alias_0" def distinct(columns, orders) #:nodoc: - return "DISTINCT #{columns}" if orders.empty? - - # Construct a clean list of column names from the ORDER BY clause, removing - # any ASC/DESC modifiers - order_columns = orders.collect do |s| - s = s.to_sql unless s.is_a?(String) - s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') - end - order_columns.delete_if { |c| c.blank? } - order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" } - - "DISTINCT #{columns}, #{order_columns * ', '}" + order_columns = orders.map{ |s| + # Convert Arel node to string + s = s.to_sql unless s.is_a?(String) + # Remove any ASC/DESC modifiers + s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') + }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } + + [super].concat(order_columns).join(', ') end end end diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 8ce7a7a463..d6d998c7be 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -5,18 +5,18 @@ module ActiveRecord # example for regular databases (MySQL, Postgresql, etc): # # ActiveRecord::Base.establish_connection( - # :adapter => "mysql", - # :host => "localhost", - # :username => "myuser", - # :password => "mypass", - # :database => "somedatabase" + # adapter: "mysql", + # host: "localhost", + # username: "myuser", + # password: "mypass", + # database: "somedatabase" # ) # # Example for SQLite database: # # ActiveRecord::Base.establish_connection( - # :adapter => "sqlite", - # :database => "path/to/dbfile" + # adapter: "sqlite", + # database: "path/to/dbfile" # ) # # Also accepts keys as strings (for parsing from YAML for example): @@ -64,7 +64,7 @@ module ActiveRecord # Returns the configuration of the associated connection as a hash: # # ActiveRecord::Base.connection_config - # # => {:pool=>5, :timeout=>5000, :database=>"db/development.sqlite3", :adapter=>"sqlite3"} + # # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"} # # Please use only for reading. def connection_config diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 98032db2ef..957027c1ee 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -3,9 +3,6 @@ require 'active_support/core_ext/object/duplicable' require 'thread' module ActiveRecord - ActiveSupport.on_load(:active_record_config) do - end - module Core extend ActiveSupport::Concern @@ -157,7 +154,7 @@ module ActiveRecord # # ==== Example: # # Instantiates a single new object - # User.new(:first_name => 'Jamie') + # User.new(first_name: 'Jamie') def initialize(attributes = nil) defaults = self.class.column_defaults.dup defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? } diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 57838ff984..c53b7b3e78 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -56,7 +56,7 @@ module ActiveRecord # # # For the Post with id of 5, decrement the comment_count by 1, and # # increment the action_count by 1 - # Post.update_counters 5, :comment_count => -1, :action_count => 1 + # Post.update_counters 5, comment_count: -1, action_count: 1 # # Executes the following SQL: # # UPDATE posts # # SET comment_count = COALESCE(comment_count, 0) - 1, @@ -64,7 +64,7 @@ module ActiveRecord # # WHERE id = 5 # # # For the Posts with id of 10 and 15, increment the comment_count by 1 - # Post.update_counters [10, 15], :comment_count => 1 + # Post.update_counters [10, 15], comment_count: 1 # # Executes the following SQL: # # UPDATE posts # # SET comment_count = COALESCE(comment_count, 0) + 1 diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 04c0fbfe70..c615d59725 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -22,7 +22,7 @@ module ActiveRecord # end # # # Comments are not patches, this assignment raises AssociationTypeMismatch. - # @ticket.patches << Comment.new(:content => "Please attach tests to your patch.") + # @ticket.patches << Comment.new(content: "Please attach tests to your patch.") class AssociationTypeMismatch < ActiveRecordError end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 29a99a5336..7922bbcfa0 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -250,7 +250,7 @@ module ActiveRecord # # ### in fruit.rb # - # belongs_to :eater, :polymorphic => true + # belongs_to :eater, polymorphic: true # # ### in fruits.yml # @@ -381,6 +381,12 @@ module ActiveRecord @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} } + def self.find_table_name(fixture_set_name) # :nodoc: + ActiveSupport::Deprecation.warn( + "ActiveRecord::Fixtures.find_table_name is deprecated and shall be removed from future releases. Use ActiveRecord::Fixtures.default_fixture_model_name instead.") + default_fixture_model_name(fixture_set_name) + end + def self.default_fixture_model_name(fixture_set_name) # :nodoc: ActiveRecord::Base.pluralize_table_names ? fixture_set_name.singularize.camelize : @@ -730,7 +736,7 @@ module ActiveRecord # # Examples: # - # set_fixture_class :some_fixture => SomeModel, + # set_fixture_class some_fixture: SomeModel, # 'namespaced/fixture' => Another::Model # # The keys must be the fixture names, that coincide with the short paths to the fixture files. @@ -883,7 +889,7 @@ module ActiveRecord end def enlist_fixture_connections - ActiveRecord::Base.connection_handler.connection_pools.map(&:connection) + ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection) end private diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index 58af92f0b1..b4bb95a392 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -3,12 +3,12 @@ module ActiveRecord # Locking::Pessimistic provides support for row-level locking using # SELECT ... FOR UPDATE and other lock types. # - # Pass <tt>:lock => true</tt> to <tt>ActiveRecord::Base.find</tt> to obtain an exclusive + # Pass <tt>lock: true</tt> to <tt>ActiveRecord::Base.find</tt> to obtain an exclusive # lock on the selected rows: # # select * from accounts where id=1 for update - # Account.find(1, :lock => true) + # Account.find(1, lock: true) # - # Pass <tt>:lock => 'some locking clause'</tt> to give a database-specific locking clause + # Pass <tt>lock: 'some locking clause'</tt> to give a database-specific locking clause # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example: # # Account.transaction do diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index c3b9a0f9b7..22347fcaef 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -642,7 +642,11 @@ module ActiveRecord def proper_table_name(name) # Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string - name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}" + if name.respond_to? :table_name + name.table_name + else + "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}" + end end def migrations_paths diff --git a/activerecord/lib/active_record/migration/join_table.rb b/activerecord/lib/active_record/migration/join_table.rb index e880ae97bb..ebf64cbcdc 100644 --- a/activerecord/lib/active_record/migration/join_table.rb +++ b/activerecord/lib/active_record/migration/join_table.rb @@ -8,7 +8,7 @@ module ActiveRecord end def join_table_name(table_1, table_2) - [table_1, table_2].sort.join("_").to_sym + [table_1.to_s, table_2.to_s].sort.join("_").to_sym end end end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 1b95b72c8a..628ab0f566 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -285,7 +285,7 @@ module ActiveRecord # # JobLevel.reset_column_information # %w{assistant executive manager director}.each do |type| - # JobLevel.create(:name => type) + # JobLevel.create(name: type) # end # end # diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index aba56c2861..4c9bd76d7c 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -50,14 +50,14 @@ module ActiveRecord # Enabling nested attributes on a one-to-one association allows you to # create the member and avatar in one go: # - # params = { :member => { :name => 'Jack', :avatar_attributes => { :icon => 'smiling' } } } + # params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } } # member = Member.create(params[:member]) # member.avatar.id # => 2 # member.avatar.icon # => 'smiling' # # It also allows you to update the avatar through the member: # - # params = { :member => { :avatar_attributes => { :id => '2', :icon => 'sad' } } } + # params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } } # member.update_attributes params[:member] # member.avatar.icon # => 'sad' # @@ -68,13 +68,13 @@ module ActiveRecord # # class Member < ActiveRecord::Base # has_one :avatar - # accepts_nested_attributes_for :avatar, :allow_destroy => true + # accepts_nested_attributes_for :avatar, allow_destroy: true # end # # Now, when you add the <tt>_destroy</tt> key to the attributes hash, with a # value that evaluates to +true+, you will destroy the associated model: # - # member.avatar_attributes = { :id => '2', :_destroy => '1' } + # member.avatar_attributes = { id: '2', _destroy: '1' } # member.avatar.marked_for_destruction? # => true # member.save # member.reload.avatar # => nil @@ -97,11 +97,11 @@ module ActiveRecord # be instantiated, unless the hash also contains a <tt>_destroy</tt> key # that evaluates to +true+. # - # params = { :member => { - # :name => 'joe', :posts_attributes => [ - # { :title => 'Kari, the awesome Ruby documentation browser!' }, - # { :title => 'The egalitarian assumption of the modern citizen' }, - # { :title => '', :_destroy => '1' } # this will be ignored + # params = { member: { + # name: 'joe', posts_attributes: [ + # { title: 'Kari, the awesome Ruby documentation browser!' }, + # { title: 'The egalitarian assumption of the modern citizen' }, + # { title: '', _destroy: '1' } # this will be ignored # ] # }} # @@ -116,14 +116,14 @@ module ActiveRecord # # class Member < ActiveRecord::Base # has_many :posts - # accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? } + # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? } # end # - # params = { :member => { - # :name => 'joe', :posts_attributes => [ - # { :title => 'Kari, the awesome Ruby documentation browser!' }, - # { :title => 'The egalitarian assumption of the modern citizen' }, - # { :title => '' } # this will be ignored because of the :reject_if proc + # params = { member: { + # name: 'joe', posts_attributes: [ + # { title: 'Kari, the awesome Ruby documentation browser!' }, + # { title: 'The egalitarian assumption of the modern citizen' }, + # { title: '' } # this will be ignored because of the :reject_if proc # ] # }} # @@ -136,12 +136,12 @@ module ActiveRecord # # class Member < ActiveRecord::Base # has_many :posts - # accepts_nested_attributes_for :posts, :reject_if => :new_record? + # accepts_nested_attributes_for :posts, reject_if: :new_record? # end # # class Member < ActiveRecord::Base # has_many :posts - # accepts_nested_attributes_for :posts, :reject_if => :reject_posts + # accepts_nested_attributes_for :posts, reject_if: :reject_posts # # def reject_posts(attributed) # attributed['title'].blank? @@ -152,10 +152,10 @@ module ActiveRecord # associated record, the matching record will be modified: # # member.attributes = { - # :name => 'Joe', - # :posts_attributes => [ - # { :id => 1, :title => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' }, - # { :id => 2, :title => '[UPDATED] other post' } + # name: 'Joe', + # posts_attributes: [ + # { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' }, + # { id: 2, title: '[UPDATED] other post' } # ] # } # @@ -170,11 +170,11 @@ module ActiveRecord # # class Member < ActiveRecord::Base # has_many :posts - # accepts_nested_attributes_for :posts, :allow_destroy => true + # accepts_nested_attributes_for :posts, allow_destroy: true # end # - # params = { :member => { - # :posts_attributes => [{ :id => '2', :_destroy => '1' }] + # params = { member: { + # posts_attributes: [{ id: '2', _destroy: '1' }] # }} # # member.attributes = params[:member] @@ -197,12 +197,12 @@ module ActiveRecord # <tt>inverse_of</tt> as this example illustrates: # # class Member < ActiveRecord::Base - # has_many :posts, :inverse_of => :member + # has_many :posts, inverse_of: :member # accepts_nested_attributes_for :posts # end # # class Post < ActiveRecord::Base - # belongs_to :member, :inverse_of => :posts + # belongs_to :member, inverse_of: :posts # validates_presence_of :member # end module ClassMethods @@ -248,11 +248,11 @@ module ActiveRecord # # Examples: # # creates avatar_attributes= - # accepts_nested_attributes_for :avatar, :reject_if => proc { |attributes| attributes['name'].blank? } + # accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? } # # creates avatar_attributes= - # accepts_nested_attributes_for :avatar, :reject_if => :all_blank + # accepts_nested_attributes_for :avatar, reject_if: :all_blank # # creates avatar_attributes= and posts_attributes= - # accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true + # accepts_nested_attributes_for :avatar, :posts, allow_destroy: true def accepts_nested_attributes_for(*attr_names) options = { :allow_destroy => false, :update_only => false } options.update(attr_names.extract_options!) @@ -348,9 +348,9 @@ module ActiveRecord # For example: # # assign_nested_attributes_for_collection_association(:people, { - # '1' => { :id => '1', :name => 'Peter' }, - # '2' => { :name => 'John' }, - # '3' => { :id => '2', :_destroy => true } + # '1' => { id: '1', name: 'Peter' }, + # '2' => { name: 'John' }, + # '3' => { id: '2', _destroy: true } # }) # # Will update the name of the Person with ID 1, build a new associated @@ -360,9 +360,9 @@ module ActiveRecord # Also accepts an Array of attribute hashes: # # assign_nested_attributes_for_collection_association(:people, [ - # { :id => '1', :name => 'Peter' }, - # { :name => 'John' }, - # { :id => '2', :_destroy => true } + # { id: '1', name: 'Peter' }, + # { name: 'John' }, + # { id: '2', _destroy: true } # ]) def assign_nested_attributes_for_collection_association(association_name, attributes_collection) options = self.nested_attributes_options[association_name] diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index 4c1c91e3df..711fc8b883 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -46,7 +46,11 @@ module ActiveRecord {} end - def count + def count(*) + 0 + end + + def sum(*) 0 end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 8e749772a1..eed49e17b1 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -72,11 +72,9 @@ module ActiveRecord # +save+ returns +false+. See ActiveRecord::Callbacks for further # details. def save(*) - begin - create_or_update - rescue ActiveRecord::RecordInvalid - false - end + create_or_update + rescue ActiveRecord::RecordInvalid + false end # Saves the model. @@ -155,7 +153,18 @@ module ActiveRecord became.instance_variable_set("@new_record", new_record?) became.instance_variable_set("@destroyed", destroyed?) became.instance_variable_set("@errors", errors) - became.public_send("#{klass.inheritance_column}=", klass.name) unless self.class.descends_from_active_record? + became + end + + # Wrapper around +becomes+ that also changes the instance's sti column value. + # This is especially useful if you want to persist the changed class in your + # database. + # + # Note: The old instance's sti column value will be changed too, as both objects + # share the same set of attributes. + def becomes!(klass) + became = becomes(klass) + became.public_send("#{klass.inheritance_column}=", klass.sti_name) unless self.class.descends_from_active_record? became end @@ -171,7 +180,7 @@ module ActiveRecord name = name.to_s verify_readonly_attribute(name) send("#{name}=", value) - save(:validate => false) + save(validate: false) end # Updates the attributes of the model from the passed-in hash and saves the @@ -226,8 +235,8 @@ module ActiveRecord updated_count = self.class.where(self.class.primary_key => id).update_all(attributes) - attributes.each do |k,v| - raw_write_attribute(k,v) + attributes.each do |k, v| + raw_write_attribute(k, v) end updated_count == 1 @@ -379,10 +388,14 @@ module ActiveRecord # Returns the number of affected rows. def update(attribute_names = @attributes.keys) attributes_with_values = arel_attributes_with_values_for_update(attribute_names) - return 0 if attributes_with_values.empty? - klass = self.class - stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values) - klass.connection.update stmt + + if attributes_with_values.empty? + 0 + else + klass = self.class + stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values) + klass.connection.update stmt + end end # Creates a record with values matching those of the instance attributes diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 77e41ea927..5464ca6066 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -92,6 +92,33 @@ module ActiveRecord initializer "active_record.set_configs" do |app| ActiveSupport.on_load(:active_record) do + begin + old_behavior, ActiveSupport::Deprecation.behavior = ActiveSupport::Deprecation.behavior, :stderr + whitelist_attributes = app.config.active_record.delete(:whitelist_attributes) + + if respond_to?(:mass_assignment_sanitizer=) + mass_assignment_sanitizer = nil + else + mass_assignment_sanitizer = app.config.active_record.delete(:mass_assignment_sanitizer) + end + + unless whitelist_attributes.nil? && mass_assignment_sanitizer.nil? + ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, [] + Model based mass assignment security has been extracted + out of Rails into a gem. Please use the new recommended protection model for + params or add `protected_attributes` to your Gemfile to use the old one. + + To disable this message remove the `whitelist_attributes` option from your + `config/application.rb` file and any `mass_assignment_sanitizer` options + from your `config/environments/*.rb` files. + + See http://edgeguides.rubyonrails.org/security.html#mass-assignment for more information + EOF + end + ensure + ActiveSupport::Deprecation.behavior = old_behavior + end + app.config.active_record.each do |k,v| send "#{k}=", v end @@ -122,8 +149,10 @@ module ActiveRecord ActiveSupport.on_load(:active_record) do ActionDispatch::Reloader.send(hook) do - ActiveRecord::Base.clear_reloadable_connections! - ActiveRecord::Base.clear_cache! + if ActiveRecord::Base.connected? + ActiveRecord::Base.clear_reloadable_connections! + ActiveRecord::Base.clear_cache! + end end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index cb9cc5a9df..3ee55c580e 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -486,7 +486,7 @@ module ActiveRecord # Returns a hash of where conditions # # Users.where(name: 'Oscar').where_values_hash - # # => {:name=>"oscar"} + # # => {name: "oscar"} def where_values_hash equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node| node.left.relation.name == table_name diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index a7d2f4bd24..741f94f777 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/object/try' - module ActiveRecord module Calculations # Count the records. @@ -145,7 +143,7 @@ module ActiveRecord # # SELECT DISTINCT role FROM people # # => ['admin', 'member', 'guest'] # - # Person.where(:age => 21).limit(5).pluck(:id) + # Person.where(age: 21).limit(5).pluck(:id) # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5 # # => [2, 3] # @@ -165,7 +163,9 @@ module ActiveRecord if has_include?(column_names.first) construct_relation_for_association_calculations.pluck(*column_names) else - result = klass.connection.select_all(select(column_names).arel, nil, bind_values) + relation = spawn + relation.select_values = column_names + result = klass.connection.select_all(relation.arel, nil, bind_values) columns = result.columns.map do |key| klass.column_types.fetch(key) { result.column_types.fetch(key) { @@ -271,17 +271,19 @@ module ActiveRecord group_fields = group_attrs end - group_aliases = group_fields.map { |field| column_alias_for(field) } + group_aliases = group_fields.map { |field| + column_alias_for(field) + } group_columns = group_aliases.zip(group_fields).map { |aliaz,field| [aliaz, column_for(field)] } - group = @klass.connection.adapter_name == 'FrontBase' ? group_aliases : group_fields + group = group_fields if operation == 'count' && column_name == :all aggregate_alias = 'count_all' else - aggregate_alias = column_alias_for(operation, column_name) + aggregate_alias = column_alias_for([operation, column_name].join(' ')) end select_values = [ @@ -300,7 +302,8 @@ module ActiveRecord end } - relation = except(:group).group(group) + relation = except(:group) + relation.group_values = group relation.select_values = select_values calculated_data = @klass.connection.select_all(relation, nil, bind_values) @@ -329,10 +332,12 @@ module ActiveRecord # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" # column_alias_for("count(*)") # => "count_all" # column_alias_for("count", "id") # => "count_id" - def column_alias_for(*keys) - keys.map! {|k| k.respond_to?(:to_sql) ? k.to_sql : k} - table_name = keys.join(' ') - table_name.downcase! + def column_alias_for(keys) + if keys.respond_to? :name + keys = "#{keys.relation.name}.#{keys.name}" + end + + table_name = keys.to_s.downcase table_name.gsub!(/\*/, 'all') table_name.gsub!(/\W+/, ' ') table_name.strip! diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index ab8b36c8ab..dbfa92bbbd 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -1,3 +1,4 @@ +require 'thread' module ActiveRecord module Delegation # :nodoc: @@ -6,6 +7,8 @@ module ActiveRecord delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass + @@delegation_mutex = Mutex.new + def self.delegate_to_scoped_klass(method) if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/ module_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -32,13 +35,28 @@ module ActiveRecord def method_missing(method, *args, &block) if @klass.respond_to?(method) - ::ActiveRecord::Delegation.delegate_to_scoped_klass(method) + @@delegation_mutex.synchronize do + unless ::ActiveRecord::Delegation.method_defined?(method) + ::ActiveRecord::Delegation.delegate_to_scoped_klass(method) + end + end + scoping { @klass.send(method, *args, &block) } elsif Array.method_defined?(method) - ::ActiveRecord::Delegation.delegate method, :to => :to_a + @@delegation_mutex.synchronize do + unless ::ActiveRecord::Delegation.method_defined?(method) + ::ActiveRecord::Delegation.delegate method, :to => :to_a + end + end + to_a.send(method, *args, &block) elsif arel.respond_to?(method) - ::ActiveRecord::Delegation.delegate method, :to => :arel + @@delegation_mutex.synchronize do + unless ::ActiveRecord::Delegation.method_defined?(method) + ::ActiveRecord::Delegation.delegate method, :to => :arel + end + end + arel.send(method, *args, &block) else super diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 99c2f45bc8..eafe4a54c4 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/hash/indifferent_access' - module ActiveRecord module FinderMethods # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). @@ -78,7 +76,7 @@ module ActiveRecord # # Person.first # returns the first object fetched by SELECT * FROM people # Person.where(["user_name = ?", user_name]).first - # Person.where(["user_name = :u", { :u => user_name }]).first + # Person.where(["user_name = :u", { u: user_name }]).first # Person.order("created_on DESC").offset(5).first # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3 def first(limit = nil) @@ -225,7 +223,7 @@ module ActiveRecord def construct_limited_ids_condition(relation) orders = relation.order_values.map { |val| val.presence }.compact - values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders) + values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders) relation = relation.dup @@ -234,8 +232,6 @@ module ActiveRecord end def find_with_ids(*ids) - return to_a.find { |*block_args| yield(*block_args) } if block_given? - expects_array = ids.first.kind_of?(Array) return ids.first if expects_array && ids.first.empty? diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index bd7aeb0c4e..83074e72c1 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -36,10 +36,10 @@ module ActiveRecord queries = [] # Find the foreign key when using queries such as: - # Post.where(:author => author) + # Post.where(author: author) # # For polymorphic relationships, find the foreign key and type: - # PriceEstimate.where(:estimate_of => treasure) + # PriceEstimate.where(estimate_of: treasure) if klass && value.class < Base && reflection = klass.reflect_on_association(column.to_sym) if reflection.polymorphic? queries << build(table[reflection.foreign_type], value.class.base_class) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 4fdc296c7e..b3712b4ad6 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -218,7 +218,6 @@ module ActiveRecord # Like #order, but modifies relation in place. def order!(*args) args.flatten! - validate_order_args args references = args.reject { |arg| Arel::Node === arg } @@ -245,7 +244,6 @@ module ActiveRecord # Like #reorder, but modifies relation in place. def reorder!(*args) args.flatten! - validate_order_args args self.reordering_value = true @@ -357,17 +355,17 @@ module ActiveRecord # author = Author.find(1) # # # The following queries will be equivalent: - # Post.where(:author => author) - # Post.where(:author_id => author) + # Post.where(author: author) + # Post.where(author_id: author) # # This also works with polymorphic belongs_to relationships: # - # treasure = Treasure.create(:name => 'gold coins') - # treasure.price_estimates << PriceEstimate.create(:price => 125) + # treasure = Treasure.create(name: 'gold coins') + # treasure.price_estimates << PriceEstimate.create(price: 125) # # # The following queries will be equivalent: - # PriceEstimate.where(:estimate_of => treasure) - # PriceEstimate.where(:estimate_of_type => 'Treasure', :estimate_of_id => treasure) + # PriceEstimate.where(estimate_of: treasure) + # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure) # # === Joins # @@ -379,7 +377,7 @@ module ActiveRecord # For hash conditions, you can either use the table name in the key, or use a sub-hash. # # User.joins(:posts).where({ "posts.published" => true }) - # User.joins(:posts).where({ :posts => { :published => true } }) + # User.joins(:posts).where({ posts: { published: true } }) # # === empty condition # @@ -478,13 +476,13 @@ module ActiveRecord # # For example: # - # @posts = current_user.visible_posts.where(:name => params[:name]) + # @posts = current_user.visible_posts.where(name: params[:name]) # # => the visible_posts method is expected to return a chainable Relation # # def visible_posts # case role # when 'Country Manager' - # Post.where(:country => country) + # Post.where(country: country) # when 'Reviewer' # Post.published # when 'Bad User' @@ -796,7 +794,7 @@ module ActiveRecord def reverse_sql_order(order_query) order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty? - order_query.map do |o| + order_query.flat_map do |o| case o when Arel::Nodes::Ordering o.reverse @@ -814,7 +812,7 @@ module ActiveRecord else o end - end.flatten + end end def array_of_strings?(o) @@ -825,7 +823,7 @@ module ActiveRecord orders = order_values orders = reverse_sql_order(orders) if reverse_order_value - orders = orders.uniq.reject(&:blank?).map do |order| + orders = orders.uniq.reject(&:blank?).flat_map do |order| case order when Symbol table[order].asc @@ -834,7 +832,7 @@ module ActiveRecord else order end - end.flatten + end arel.order(*orders) unless orders.empty? end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 5394c1b28b..62dda542ab 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -15,11 +15,11 @@ module ActiveRecord # # ==== Examples # - # Post.where(:published => true).joins(:comments).merge( Comment.where(:spam => false) ) + # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) ) # # Performs a single join query with both where conditions. # # recent_posts = Post.order('created_at DESC').first(5) - # Post.where(:published => true).merge(recent_posts) + # Post.where(published: true).merge(recent_posts) # # Returns the intersection of all published posts with the 5 most recently created posts. # # (This is just an example. You'd probably want to do this with a single query!) # diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index f3e47a958e..2dad1dc177 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -17,7 +17,7 @@ module ActiveRecord # Accepts an array, hash, or string of SQL conditions and sanitizes # them into a valid SQL fragment for a WHERE clause. # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" - # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'" + # { name: "foo'bar", group_id: 4 } returns "name='foo''bar' and group_id='4'" # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'" def sanitize_sql_for_conditions(condition, table_name = self.table_name) return nil if condition.blank? @@ -32,7 +32,7 @@ module ActiveRecord # Accepts an array, hash, or string of SQL conditions and sanitizes # them into a valid SQL fragment for a SET clause. - # { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'" + # { name: nil, group_id: 4 } returns "name = NULL , group_id='4'" def sanitize_sql_for_assignment(assignments) case assignments when Array; sanitize_sql_array(assignments) @@ -46,12 +46,12 @@ module ActiveRecord # aggregate attribute values. # Given: # class Person < ActiveRecord::Base - # composed_of :address, :class_name => "Address", - # :mapping => [%w(address_street street), %w(address_city city)] + # composed_of :address, class_name: "Address", + # mapping: [%w(address_street street), %w(address_city city)] # end # Then: - # { :address => Address.new("813 abc st.", "chicago") } - # # => { :address_street => "813 abc st.", :address_city => "chicago" } + # { address: Address.new("813 abc st.", "chicago") } + # # => { address_street: "813 abc st.", address_city: "chicago" } def expand_hash_conditions_for_aggregates(attrs) expanded_attrs = {} attrs.each do |attr, value| @@ -72,18 +72,18 @@ module ActiveRecord end # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause. - # { :name => "foo'bar", :group_id => 4 } + # { name: "foo'bar", group_id: 4 } # # => "name='foo''bar' and group_id= 4" - # { :status => nil, :group_id => [1,2,3] } + # { status: nil, group_id: [1,2,3] } # # => "status IS NULL and group_id IN (1,2,3)" - # { :age => 13..18 } + # { age: 13..18 } # # => "age BETWEEN 13 AND 18" # { 'other_records.id' => 7 } # # => "`other_records`.`id` = 7" - # { :other_records => { :id => 7 } } + # { other_records: { id: 7 } } # # => "`other_records`.`id` = 7" # And for value objects on a composed_of relationship: - # { :address => Address.new("123 abc st.", "chicago") } + # { address: Address.new("123 abc st.", "chicago") } # # => "address_street='123 abc st.' and address_city='chicago'" def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name) attrs = expand_hash_conditions_for_aggregates(attrs) @@ -96,7 +96,7 @@ module ActiveRecord alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. - # { :status => nil, :group_id => 1 } + # { status: nil, group_id: 1 } # # => "status = NULL , group_id = 1" def sanitize_sql_hash_for_assignment(attrs) attrs.map do |attr, value| diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index 834d01a1e8..1a766093d0 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -36,7 +36,7 @@ module ActiveRecord #:nodoc: # # For instance: # - # topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ]) + # topic.to_xml(skip_instruct: true, except: [ :id, :bonus_time, :written_on, :replies_count ]) # # <topic> # <title>The First Topic</title> @@ -50,7 +50,7 @@ module ActiveRecord #:nodoc: # # To include first level associations use <tt>:include</tt>: # - # firm.to_xml :include => [ :account, :clients ] + # firm.to_xml include: [ :account, :clients ] # # <?xml version="1.0" encoding="UTF-8"?> # <firm> @@ -81,7 +81,7 @@ module ActiveRecord #:nodoc: # associated with models. # # proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) } - # firm.to_xml :procs => [ proc ] + # firm.to_xml procs: [ proc ] # # <firm> # # ... normal attributes as shown above ... @@ -90,7 +90,7 @@ module ActiveRecord #:nodoc: # # To include deeper levels of associations pass a hash like this: # - # firm.to_xml :include => {:account => {}, :clients => {:include => :address}} + # firm.to_xml include: {account: {}, clients: {include: :address}} # <?xml version="1.0" encoding="UTF-8"?> # <firm> # <id type="integer">1</id> @@ -120,7 +120,7 @@ module ActiveRecord #:nodoc: # # To include any methods on the model being called use <tt>:methods</tt>: # - # firm.to_xml :methods => [ :calculated_earnings, :real_earnings ] + # firm.to_xml methods: [ :calculated_earnings, :real_earnings ] # # <firm> # # ... normal attributes as shown above ... @@ -132,7 +132,7 @@ module ActiveRecord #:nodoc: # modified version of the options hash that was given to +to_xml+: # # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') } - # firm.to_xml :procs => [ proc ] + # firm.to_xml procs: [ proc ] # # <firm> # # ... normal attributes as shown above ... @@ -164,7 +164,7 @@ module ActiveRecord #:nodoc: # def to_xml(options = {}) # require 'builder' # options[:indent] ||= 2 - # xml = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent]) + # xml = options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent]) # xml.instruct! unless options[:skip_instruct] # xml.level_one do # xml.tag!(:second_level, 'content') diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 920d6848c1..cf17b1d8a4 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -1,8 +1,5 @@ module ActiveRecord - ActiveSupport.on_load(:active_record_config) do - end - # = Active Record Timestamp # # Active Record automatically timestamps create and update operations if the diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index f91abfbd19..ce6998530f 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -108,10 +108,10 @@ module ActiveRecord # # # Suppose that we have a Number model with a unique column called 'i'. # Number.transaction do - # Number.create(:i => 0) + # Number.create(i: 0) # begin # # This will raise a unique constraint error... - # Number.create(:i => 0) + # Number.create(i: 0) # rescue ActiveRecord::StatementInvalid # # ...which we ignore. # end @@ -119,7 +119,7 @@ module ActiveRecord # # On PostgreSQL, the transaction is now unusable. The following # # statement will cause a PostgreSQL error, even though the unique # # constraint is no longer violated: - # Number.create(:i => 1) + # Number.create(i: 1) # # => "PGError: ERROR: current transaction is aborted, commands # # ignored until end of transaction block" # end @@ -134,9 +134,9 @@ module ActiveRecord # transaction. For example, the following behavior may be surprising: # # User.transaction do - # User.create(:username => 'Kotori') + # User.create(username: 'Kotori') # User.transaction do - # User.create(:username => 'Nemu') + # User.create(username: 'Nemu') # raise ActiveRecord::Rollback # end # end @@ -147,14 +147,14 @@ module ActiveRecord # real transaction is committed. # # In order to get a ROLLBACK for the nested transaction you may ask for a real - # sub-transaction by passing <tt>:requires_new => true</tt>. If anything goes wrong, + # sub-transaction by passing <tt>requires_new: true</tt>. If anything goes wrong, # the database rolls back to the beginning of the sub-transaction without rolling # back the parent transaction. If we add it to the previous example: # # User.transaction do - # User.create(:username => 'Kotori') - # User.transaction(:requires_new => true) do - # User.create(:username => 'Nemu') + # User.create(username: 'Kotori') + # User.transaction(requires_new: true) do + # User.create(username: 'Nemu') # raise ActiveRecord::Rollback # end # end @@ -194,7 +194,7 @@ module ActiveRecord # automatically released. The following example demonstrates the problem: # # Model.connection.transaction do # BEGIN - # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1 + # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 # Model.connection.create_table(...) # active_record_1 now automatically released # end # RELEASE savepoint active_record_1 # # ^^^^ BOOM! database error! @@ -213,13 +213,13 @@ module ActiveRecord # You can specify that the callback should only be fired by a certain action with # the +:on+ option: # - # after_commit :do_foo, :on => :create - # after_commit :do_bar, :on => :update - # after_commit :do_baz, :on => :destroy + # after_commit :do_foo, on: :create + # after_commit :do_bar, on: :update + # after_commit :do_baz, on: :destroy # # Also, to have the callback fired on create and update, but not on destroy: # - # after_commit :do_zoo, :if => :persisted? + # after_commit :do_zoo, if: :persisted? # # Note that transactional fixtures do not play well with this feature. Please # use the +test_after_commit+ gem to have these hooks fired in tests. diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index f1362dd15f..872204c644 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -216,6 +216,35 @@ module ActiveRecord assert_equal "(number > 100)", index.where end + def test_distinct_zero_orders + assert_equal "DISTINCT posts.id", + @connection.distinct("posts.id", []) + end + + def test_distinct_one_order + assert_equal "DISTINCT posts.id, posts.created_at AS alias_0", + @connection.distinct("posts.id", ["posts.created_at desc"]) + end + + def test_distinct_few_orders + assert_equal "DISTINCT posts.id, posts.created_at AS alias_0, posts.position AS alias_1", + @connection.distinct("posts.id", ["posts.created_at desc", "posts.position asc"]) + end + + def test_distinct_blank_not_nil_orders + assert_equal "DISTINCT posts.id, posts.created_at AS alias_0", + @connection.distinct("posts.id", ["posts.created_at desc", "", " "]) + end + + def test_distinct_with_arel_order + order = Object.new + def order.to_sql + "posts.created_at desc" + end + assert_equal "DISTINCT posts.id, posts.created_at AS alias_0", + @connection.distinct("posts.id", [order]) + end + def test_distinct_with_nulls assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls first"]) assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"]) diff --git a/activerecord/test/cases/adapters/postgresql/sql_types_test.rb b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb new file mode 100644 index 0000000000..d7d40f6385 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb @@ -0,0 +1,18 @@ +require "cases/helper" + +class SqlTypesTest < ActiveRecord::TestCase + def test_binary_types + assert_equal 'bytea', type_to_sql(:binary, 100_000) + assert_raise ActiveRecord::ActiveRecordError do + type_to_sql :binary, 4294967295 + end + assert_equal 'text', type_to_sql(:text, 100_000) + assert_raise ActiveRecord::ActiveRecordError do + type_to_sql :text, 4294967295 + end + end + + def type_to_sql(*args) + ActiveRecord::Base.connection.type_to_sql(*args) + end +end diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index 26507ad654..630bdeec67 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -75,6 +75,15 @@ class TimestampTest < ActiveRecord::TestCase assert_equal '4', pg_datetime_precision('foos', 'updated_at') end + def test_bc_timestamp + unless current_adapter?(:PostgreSQLAdapter) + return skip("only tested on postgresql") + end + date = Date.new(0) - 1.second + Developer.create!(:name => "aaron", :updated_at => date) + assert_equal date, Developer.find_by_name("aaron").updated_at + end + private def pg_datetime_precision(table_name, column_name) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 42f5b69d4e..1b1b479f1a 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -799,12 +799,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, developer.projects.count end - def test_counting_should_not_fire_sql_if_parent_is_unsaved - assert_no_queries do - assert_equal 0, Developer.new.projects.count - end - end - unless current_adapter?(:PostgreSQLAdapter) def test_count_with_finder_sql assert_equal 3, projects(:active_record).developers_with_finder_sql.count @@ -862,4 +856,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def klass.name; 'Foo'; end assert_deprecated { klass.has_and_belongs_to_many :posts, :delete_sql => 'lol' } end + + test "has and belongs to many associations on new records use null relations" do + projects = Developer.new.projects + assert_no_queries do + assert_equal [], projects + assert_equal [], projects.where(title: 'omg') + assert_equal [], projects.pluck(:title) + assert_equal 0, projects.count + 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 50c23c863f..01afa087be 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -262,12 +262,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal firm.limited_clients.length, firm.limited_clients.count end - def test_counting_should_not_fire_sql_if_parent_is_unsaved - assert_no_queries do - assert_equal 0, Person.new.readers.count - end - end - def test_finding assert_equal 2, Firm.all.merge!(:order => "id").first.clients.length end @@ -754,6 +748,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end + def test_custom_named_counter_cache + topic = topics(:first) + + assert_difference "topic.reload.replies_count", -1 do + topic.approved_replies.clear + end + end + def test_deleting_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") @@ -1148,6 +1150,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert companies(:first_firm).clients.include?(Client.find(2)) end + def test_included_in_collection_for_new_records + client = Client.create(:name => 'Persisted') + assert_nil client.client_of + assert !Firm.new.clients_of_firm.include?(client), + 'includes a client that does not belong to any firm' + end + def test_adding_array_and_collection assert_nothing_raised { Firm.first.clients + Firm.all.last.clients } end @@ -1648,4 +1657,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase klass = Class.new(ActiveRecord::Base) assert_deprecated { klass.has_many :foo, :counter_sql => 'lol' } end + + test "has many associations on new records use null relations" do + post = Post.new + + assert_no_queries do + assert_equal [], post.comments + assert_equal [], post.comments.where(body: 'omg') + assert_equal [], post.comments.pluck(:body) + assert_equal 0, post.comments.sum(:id) + assert_equal 0, post.comments.count + end + end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index b2a5d9d6f7..8e52ce1d91 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -766,12 +766,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal 1, authors(:mary).categories.general.count end - def test_counting_should_not_fire_sql_if_parent_is_unsaved - assert_no_queries do - assert_equal 0, Person.new.posts.count - end - end - def test_has_many_through_belongs_to_should_update_when_the_through_foreign_key_changes post = posts(:eager_other) @@ -876,4 +870,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase post = tags(:general).tagged_posts.create! :title => "foo", :body => "bar" assert_equal [tags(:general)], post.reload.tags end + + test "has many through associations on new records use null relations" do + person = Person.new + + assert_no_queries do + assert_equal [], person.posts + assert_equal [], person.posts.where(body: 'omg') + assert_equal [], person.posts.pluck(:body) + assert_equal 0, person.posts.sum(:tags_count) + assert_equal 0, person.posts.count + end + end + end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 2d3cb654df..ea1cfa0805 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -206,6 +206,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal account, firm.account end + def test_build_association_dont_create_transaction + assert_no_queries { + Firm.new.build_account + } + end + def test_build_and_create_should_not_happen_within_scope pirate = pirates(:blackbeard) scoped_count = pirate.association(:foo_bulb).scope.where_values.count diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 8b82b79219..c503c21e27 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -12,6 +12,8 @@ require 'models/contact' require 'models/keyboard' class AttributeMethodsTest < ActiveRecord::TestCase + include InTimeZone + fixtures :topics, :developers, :companies, :computers def setup @@ -311,26 +313,17 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_read_write_boolean_attribute topic = Topic.new - # puts "" - # puts "New Topic" - # puts topic.inspect topic.approved = "false" - # puts "Expecting false" - # puts topic.inspect assert !topic.approved?, "approved should be false" + topic.approved = "false" - # puts "Expecting false" - # puts topic.inspect assert !topic.approved?, "approved should be false" + topic.approved = "true" - # puts "Expecting true" - # puts topic.inspect assert topic.approved?, "approved should be true" + topic.approved = "true" - # puts "Expecting true" - # puts topic.inspect assert topic.approved?, "approved should be true" - # puts "" end def test_overridden_write_attribute @@ -793,27 +786,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase private def cached_columns - Topic.columns.find_all { |column| - !Topic.serialized_attributes.include? column.name - }.map(&:name) + Topic.columns.map(&:name) - Topic.serialized_attributes.keys end def time_related_columns_on_topic Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) } end - def in_time_zone(zone) - old_zone = Time.zone - old_tz = ActiveRecord::Base.time_zone_aware_attributes - - Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil - ActiveRecord::Base.time_zone_aware_attributes = !zone.nil? - yield - ensure - Time.zone = old_zone - ActiveRecord::Base.time_zone_aware_attributes = old_tz - end - def privatize(method_signature) @target.class_eval(<<-private_method, __FILE__, __LINE__ + 1) private diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 203e44857a..8644f2f496 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -616,6 +616,12 @@ class BasicsTest < ActiveRecord::TestCase assert_equal 'value2', weird.read_attribute('a$b') end + def test_group_weirds_by_from + Weird.create('a$b' => 'value', :from => 'aaron') + count = Weird.group(Weird.arel_table[:from]).count + assert_equal 1, count['aaron'] + end + def test_attributes_on_dummy_time # Oracle, and Sybase do not have a TIME datatype. return true if current_adapter?(:OracleAdapter, :SybaseAdapter) @@ -1442,6 +1448,13 @@ class BasicsTest < ActiveRecord::TestCase assert_match(/\/#{dev.id}$/, dev.cache_key) end + def test_cache_key_format_is_precise_enough + dev = Developer.first + key = dev.cache_key + dev.touch + assert_not_equal key, dev.cache_key + end + def test_uniq_delegates_to_scoped scope = stub Bird.stubs(:all).returns(mock(:uniq => scope)) diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index abbf2a765e..65d28ea028 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -580,4 +580,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal ["Over There"], Possession.pluck(:where) end + + def test_pluck_replaces_select_clause + taks_relation = Topic.select(:approved, :id).order(:id) + assert_equal [1,2,3,4], taks_relation.pluck(:id) + assert_equal [false, true, true, true], taks_relation.pluck(:approved) + end end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 631bf1aaac..2ddabe058f 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -8,7 +8,7 @@ module ActiveRecord @subklass = Class.new(@klass) @handler = ConnectionHandler.new - @handler.establish_connection @klass, Base.connection_pool.spec + @pool = @handler.establish_connection(@klass, Base.connection_pool.spec) end def test_retrieve_connection @@ -44,6 +44,12 @@ module ActiveRecord assert_same @handler.retrieve_connection_pool(@klass), @handler.retrieve_connection_pool(@subklass) end + + def test_connection_pools + assert_deprecated do + assert_equal({ Base.connection_pool.spec => @pool }, @handler.connection_pools) + end + end end end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 40f1dbccde..d4fc5f204b 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -27,6 +27,8 @@ class NumericData < ActiveRecord::Base end class DirtyTest < ActiveRecord::TestCase + include InTimeZone + # Dummy to force column loads so query counts are clean. def setup Person.create :first_name => 'foo' @@ -121,7 +123,6 @@ class DirtyTest < ActiveRecord::TestCase end def test_time_attributes_changes_without_time_zone - target = Class.new(ActiveRecord::Base) target.table_name = 'pirates' @@ -604,16 +605,4 @@ class DirtyTest < ActiveRecord::TestCase assert_equal %w(parrot_id), pirate.changed assert_nil pirate.parrot_id_was end - - def in_time_zone(zone) - old_zone = Time.zone - old_tz = ActiveRecord::Base.time_zone_aware_attributes - - Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil - ActiveRecord::Base.time_zone_aware_attributes = !zone.nil? - yield - ensure - Time.zone = old_zone - ActiveRecord::Base.time_zone_aware_attributes = old_tz - end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index d44ac21b05..7db7953313 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -610,6 +610,11 @@ class FinderTest < ActiveRecord::TestCase assert_nil Topic.find_by_heading("The First Topic!") end + def test_find_by_one_attribute_bang_with_blank_defined + blank_topic = BlankTopic.create(title: "The Blank One") + assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One") + end + def test_find_by_one_attribute_with_conditions assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50) end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index cff6689c15..1bff005510 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -135,3 +135,18 @@ module LogIntercepter end end +module InTimeZone + private + + def in_time_zone(zone) + old_zone = Time.zone + old_tz = ActiveRecord::Base.time_zone_aware_attributes + + Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil + ActiveRecord::Base.time_zone_aware_attributes = !zone.nil? + yield + ensure + Time.zone = old_zone + ActiveRecord::Base.time_zone_aware_attributes = old_tz + end +end diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index 4614be9650..8fb03cdee0 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -164,6 +164,13 @@ module ActiveRecord end end + def test_rename_index_renames_index + with_change_table do |t| + @connection.expect :rename_index, nil, [:delete_me, :bar, :baz] + t.rename_index :bar, :baz + end + end + def test_change_changes_column with_change_table do |t| @connection.expect :change_column, nil, [:delete_me, :bar, :string, {}] diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb index 768ebc5861..e28feedcf9 100644 --- a/activerecord/test/cases/migration/helper.rb +++ b/activerecord/test/cases/migration/helper.rb @@ -2,12 +2,10 @@ require "cases/helper" module ActiveRecord class Migration - class << self - attr_accessor :message_count - end + class << self; attr_accessor :message_count; end + self.message_count = 0 def puts(text="") - ActiveRecord::Migration.message_count ||= 0 ActiveRecord::Migration.message_count += 1 end diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb index 144302bd4a..d8a6565d54 100644 --- a/activerecord/test/cases/migration/references_statements_test.rb +++ b/activerecord/test/cases/migration/references_statements_test.rb @@ -64,7 +64,7 @@ module ActiveRecord remove_reference table_name, :supplier refute index_exists?(table_name, :supplier_id) end - + def test_does_not_delete_reference_type_column with_polymorphic_column do remove_reference table_name, :supplier @@ -73,7 +73,7 @@ module ActiveRecord assert column_exists?(table_name, :supplier_type, :string) end end - + def test_deletes_reference_type_column with_polymorphic_column do remove_reference table_name, :supplier, polymorphic: true diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index c155f29973..b77da39e29 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -755,4 +755,11 @@ class CopyMigrationsTest < ActiveRecord::TestCase ensure clear end + + def test_create_join_table_with_symbol_and_string + connection.create_join_table :artists, 'musics' + + assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort + end + end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index fe9eddbdec..3f08f9ea4d 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -185,6 +185,17 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase assert_equal "James", mean_pirate.parrot.name assert_equal "blue", mean_pirate.parrot.color end + + def test_accepts_nested_attributes_for_can_be_overridden_in_subclasses + Pirate.accepts_nested_attributes_for(:parrot) + + mean_pirate_class = Class.new(Pirate) do + accepts_nested_attributes_for :parrot + end + mean_pirate = mean_pirate_class.new + mean_pirate.parrot_attributes = { :name => "James" } + assert_equal "James", mean_pirate.parrot.name + end end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase @@ -464,17 +475,15 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase end def test_should_unset_association_when_an_existing_record_is_destroyed - @ship.reload original_pirate_id = @ship.pirate.id - @ship.attributes = {:pirate_attributes => {:id => @ship.pirate.id, :_destroy => true}} - @ship.save! + @ship.update_attributes! pirate_attributes: { id: @ship.pirate.id, _destroy: true } - assert_empty Pirate.where(["id = ?", original_pirate_id]) + assert_empty Pirate.where(id: original_pirate_id) assert_nil @ship.pirate_id assert_nil @ship.pirate @ship.reload - assert_empty Pirate.where(["id = ?", original_pirate_id]) + assert_empty Pirate.where(id: original_pirate_id) assert_nil @ship.pirate_id assert_nil @ship.pirate end @@ -491,7 +500,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => '1' }) assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload } - + ensure Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 4b938da5c4..b2609f6395 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -280,12 +280,23 @@ class PersistencesTest < ActiveRecord::TestCase def test_update_sti_type assert_instance_of Reply, topics(:second) - topic = topics(:second).becomes(Topic) + topic = topics(:second).becomes!(Topic) assert_instance_of Topic, topic topic.save! assert_instance_of Topic, Topic.find(topic.id) end + def test_preserve_original_sti_type + reply = topics(:second) + assert_equal "Reply", reply.type + + topic = reply.becomes(Topic) + assert_equal "Reply", reply.type + + assert_instance_of Topic, topic + assert_equal "Reply", topic.type + end + def test_delete topic = Topic.find(1) assert_equal topic, topic.delete, 'topic.delete did not return self' diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index bc6cac0c6c..c34aeaf925 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -157,19 +157,19 @@ class RelationTest < ActiveRecord::TestCase assert_equal 4, topics.to_a.size assert_equal topics(:first).title, topics.first.title end - + def test_finding_with_assoc_order topics = Topic.order(:id => :desc) assert_equal 4, topics.to_a.size assert_equal topics(:fourth).title, topics.first.title end - + def test_finding_with_reverted_assoc_order topics = Topic.order(:id => :asc).reverse_order assert_equal 4, topics.to_a.size assert_equal topics(:fourth).title, topics.first.title end - + def test_raising_exception_on_invalid_hash_params assert_raise(ArgumentError) { Topic.order(:name, "id DESC", :id => :DeSc) } end @@ -1438,4 +1438,18 @@ class RelationTest < ActiveRecord::TestCase end assert_no_queries { relation.to_a } end + + test 'group with select and includes' do + authors_count = Post.select('author_id, COUNT(author_id) AS num_posts'). + group('author_id').order('author_id').includes(:author).to_a + + assert_no_queries do + result = authors_count.map do |post| + [post.num_posts, post.author.try(:name)] + end + + expected = [[1, nil], [5, "David"], [3, "Mary"], [2, "Bob"]] + assert_equal expected, result + end + end end diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb index 561b431766..1636c118a2 100644 --- a/activerecord/test/models/reference.rb +++ b/activerecord/test/models/reference.rb @@ -4,9 +4,8 @@ class Reference < ActiveRecord::Base has_many :agents_posts_authors, :through => :person - class << self - attr_accessor :make_comments - end + class << self; attr_accessor :make_comments; end + self.make_comments = false before_destroy :make_comments diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 4b27c16681..f7f4cebc5a 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -33,6 +33,7 @@ class Topic < ActiveRecord::Base end has_many :replies, :dependent => :destroy, :foreign_key => "parent_id" + has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count' has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title" has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id" @@ -106,6 +107,12 @@ class ImportantTopic < Topic serialize :important, Hash end +class BlankTopic < Topic + def blank? + true + end +end + module Web class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply' diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index d0e7338f15..0cfde83778 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -192,5 +192,10 @@ end _SQL rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table end + + create_table :limitless_fields, force: true do |t| + t.binary :binary, limit: 100_000 + t.text :text, limit: 100_000 + end end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index eec06754a5..35778d008a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -770,6 +770,7 @@ ActiveRecord::Schema.define do end create_table :weirds, :force => true do |t| t.string 'a$b' + t.string 'from' end except 'SQLite' do diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 6534c0af85..504ebcb2fe 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,37 @@ ## Rails 4.0.0 (unreleased) ## +* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. *Carlos Antonio da Silva* + +* `XmlMini.with_backend` now may be safely used with threads: + + Thread.new do + XmlMini.with_backend("REXML") { rexml_power } + end + Thread.new do + XmlMini.with_backend("LibXML") { libxml_power } + end + + Each thread will use it's own backend. + + *Nikita Afanasenko* + +* Dependencies no longer trigger Kernel#autoload in remove_constant [fixes #8213]. *Xavier Noria* + +* Simplify mocha integration and remove monkey-patches, bumping mocha to 0.13.0. *James Mead* + +* `#as_json` isolates options when encoding a hash. + Fix #8182 + + *Yves Senn* + +* Deprecate Hash#diff in favor of MiniTest's #diff. *Steve Klabnik* + +* Kernel#capture can catch output from subprocesses *Dmitry Vorotilin* + +* `to_xml` conversions now use builder's `tag!` method instead of explicit invocation of `method_missing`. + + *Nikita Afanasenko* + * Fixed timezone mapping of the Solomon Islands. *Steve Klabnik* * Make callstack attribute optional in @@ -240,8 +272,6 @@ * Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva* -* Remove ActiveSupport::TestCase#pending method, use `skip` instead. *Carlos Antonio da Silva* - * Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols). *fxn* diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 30f4ded005..a4216d2cb4 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -1,4 +1,4 @@ -version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip +version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -8,19 +8,20 @@ Gem::Specification.new do |s| s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.' s.required_ruby_version = '>= 1.9.3' - s.license = 'MIT' - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.license = 'MIT' + + s.author = 'David Heinemeier Hansson' + s.email = 'david@loudthinking.com' + s.homepage = 'http://www.rubyonrails.org' s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*'] s.require_path = 'lib' s.rdoc_options.concat ['--encoding', 'UTF-8'] - s.add_dependency('i18n', '~> 0.6') - s.add_dependency('multi_json', '~> 1.3') - s.add_dependency('tzinfo', '~> 0.3.33') - s.add_dependency('minitest', '~> 4.1') + s.add_dependency 'i18n', '~> 0.6' + s.add_dependency 'multi_json', '~> 1.3' + s.add_dependency 'tzinfo', '~> 0.3.33' + s.add_dependency 'minitest', '~> 4.1' end diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index fb7065ef3b..eeeba60839 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -4,7 +4,9 @@ module ActiveSupport # module M # def self.included(base) # base.extend ClassMethods - # scope :disabled, -> { where(disabled: true) } + # base.class_eval do + # scope :disabled, -> { where(disabled: true) } + # end # end # # module ClassMethods @@ -77,10 +79,8 @@ module ActiveSupport # module Foo # extend ActiveSupport::Concern # included do - # class_eval do - # def self.method_injected_by_foo - # ... - # end + # def self.method_injected_by_foo + # ... # end # end # end diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 6a0c4a015a..ff06436bd6 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -194,7 +194,7 @@ class Array options = options.dup options[:indent] ||= 2 - options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) + options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent]) options[:root] ||= \ if first.class != Hash && all? { |e| e.is_a?(first.class) } underscored = ActiveSupport::Inflector.underscore(first.class.name) @@ -208,12 +208,12 @@ class Array root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) children = options.delete(:children) || root.singularize - attributes = options[:skip_types] ? {} : {:type => 'array'} + attributes = options[:skip_types] ? {} : { type: 'array' } if empty? builder.tag!(root, attributes) else - builder.__send__(:method_missing, root, attributes) do + builder.tag!(root, attributes) do each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) } yield builder if block_given? end diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index f79b100b3b..640e6e9328 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -58,7 +58,7 @@ class Array # size / number gives minor group size; # size % number gives how many objects need extra accommodation; # each group hold either division or division + 1 items. - division = size / number + division = size.div number modulo = size % number # create a new array avoiding dup @@ -67,9 +67,9 @@ class Array number.times do |index| length = division + (modulo > 0 && modulo > index ? 1 : 0) - padding = fill_with != false && - modulo > 0 && length == division ? 1 : 0 - groups << slice(start, length).concat([fill_with] * padding) + groups << last_group = slice(start, length) + last_group << fill_with if fill_with != false && + modulo > 0 && length == division start += length end diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 02ae57b4a6..439d380af7 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -8,6 +8,8 @@ require 'active_support/core_ext/date_and_time/calculations' class Date include DateAndTime::Calculations + @beginning_of_week_default = nil + class << self attr_accessor :beginning_of_week_default diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index f5e3a9b842..85b0e10be2 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -74,14 +74,14 @@ class Hash options = options.dup options[:indent] ||= 2 options[:root] ||= 'hash' - options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) + options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent]) builder = options[:builder] builder.instruct! unless options.delete(:skip_instruct) root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) - builder.__send__(:method_missing, root) do + builder.tag!(root) do each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) } yield builder if block_given? end @@ -94,17 +94,17 @@ class Hash private def typecast_xml_value(value) - case value.class.to_s - when 'Hash' + case value + when Hash if value['type'] == 'array' _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) }) if entries.nil? || (c = value['__content__'] && c.blank?) [] else - case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a? - when 'Array' + case entries # something weird with classes not matching here. maybe singleton methods breaking is_a? + when Array entries.collect { |v| typecast_xml_value(v) } - when 'Hash' + when Hash [typecast_xml_value(entries)] else raise "can't typecast #{entries.inspect}" @@ -135,10 +135,10 @@ class Hash # how multipart uploaded files from HTML appear xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value end - when 'Array' + when Array value.map! { |i| typecast_xml_value(i) } value.length > 1 ? value : value.first - when 'String' + when String value else raise "can't typecast #{value.class.name} - #{value.inspect}" @@ -146,10 +146,10 @@ class Hash end def unrename_keys(params) - case params.class.to_s - when 'Hash' + case params + when Hash Hash[params.map { |k,v| [k.to_s.tr('-', '_'), unrename_keys(v)] } ] - when 'Array' + when Array params.map { |v| unrename_keys(v) } else params diff --git a/activesupport/lib/active_support/core_ext/hash/diff.rb b/activesupport/lib/active_support/core_ext/hash/diff.rb index 831dee8ecb..5f3868b5b0 100644 --- a/activesupport/lib/active_support/core_ext/hash/diff.rb +++ b/activesupport/lib/active_support/core_ext/hash/diff.rb @@ -6,6 +6,7 @@ class Hash # {}.diff(1 => 2) # => {1 => 2} # {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4} def diff(other) + ActiveSupport::Deprecation.warn "Hash#diff is no longer used inside of Rails, and is being deprecated with no replacement. If you're using it to compare hashes for the purpose of testing, please use MiniTest's assert_equal instead." dup. delete_if { |k, v| other[k] == v }. merge!(other.dup.delete_if { |k, v| has_key?(k) }) diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index bc97da6ef2..7b518821c8 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -1,4 +1,5 @@ require 'rbconfig' +require 'tempfile' module Kernel # Sets $VERBOSE to nil for the duration of the block and back to its original @@ -66,19 +67,33 @@ module Kernel # Captures the given stream and returns it: # - # stream = capture(:stdout) { puts 'Cool' } - # stream # => "Cool\n" + # stream = capture(:stdout) { puts 'notice' } + # stream # => "notice\n" + # + # stream = capture(:stderr) { warn 'error' } + # stream # => "error\n" + # + # even for subprocesses: + # + # stream = capture(:stdout) { system('echo notice') } + # stream # => "notice\n" + # + # stream = capture(:stderr) { system('echo error 1>&2') } + # stream # => "error\n" def capture(stream) - begin - stream = stream.to_s - eval "$#{stream} = StringIO.new" - yield - result = eval("$#{stream}").string - ensure - eval("$#{stream} = #{stream.upcase}") - end + stream = stream.to_s + captured_stream = Tempfile.new(stream) + stream_io = eval("$#{stream}") + origin_stream = stream_io.dup + stream_io.reopen(captured_stream) + + yield - result + stream_io.rewind + return captured_stream.read + ensure + captured_stream.unlink + stream_io.reopen(origin_stream) end alias :silence :capture diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb new file mode 100644 index 0000000000..c2c30044f2 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/struct.rb @@ -0,0 +1,6 @@ +# Backport of Struct#to_h from Ruby 2.0 +class Struct # :nodoc: + def to_h + Hash[members.zip(values)] + end +end unless Struct.instance_methods.include?(:to_h) diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 139d48f59c..796c5f9805 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -1,6 +1,8 @@ require 'active_support/time_with_zone' class Time + @zone_default = nil + class << self attr_accessor :zone_default diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 42746582fa..c75fb46263 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -572,7 +572,6 @@ module ActiveSupport #:nodoc: # Determine if the given constant has been automatically loaded. def autoloaded?(desc) - # No name => anonymous module. return false if desc.is_a?(Module) && desc.anonymous? name = to_constant_name desc return false unless qualified_const_defined? name @@ -641,19 +640,50 @@ module ActiveSupport #:nodoc: end def remove_constant(const) #:nodoc: - return false unless qualified_const_defined? const - - # Normalize ::Foo, Foo, Object::Foo, and ::Object::Foo to Object::Foo - names = const.to_s.sub(/^::(Object)?/, 'Object::').split("::") - to_remove = names.pop - parent = Inflector.constantize(names * '::') - - log "removing constant #{const}" - constantized = constantize(const) - constantized.before_remove_const if constantized.respond_to?(:before_remove_const) - parent.instance_eval { remove_const to_remove } + # Normalize ::Foo, ::Object::Foo, Object::Foo, Object::Object::Foo, etc. as Foo. + normalized = const.to_s.sub(/\A::/, '') + normalized.sub!(/\A(Object::)+/, '') + + constants = normalized.split('::') + to_remove = constants.pop + parent_name = constants.empty? ? 'Object' : constants.join('::') + + if parent = safe_constantize(parent_name) + log "removing constant #{const}" + + # In an autoloaded user.rb like this + # + # autoload :Foo, 'foo' + # + # class User < ActiveRecord::Base + # end + # + # we correctly register "Foo" as being autoloaded. But if the app does + # not use the "Foo" constant we need to be careful not to trigger + # loading "foo.rb" ourselves. While #const_defined? and #const_get? do + # require the file, #autoload? and #remove_const don't. + # + # We are going to remove the constant nonetheless ---which exists as + # far as Ruby is concerned--- because if the user removes the macro + # call from a class or module that were not autoloaded, as in the + # example above with Object, accessing to that constant must err. + unless parent.autoload?(to_remove) + begin + constantized = parent.const_get(to_remove, false) + rescue NameError + log "the constant #{const} is not reachable anymore, skipping" + return + else + constantized.before_remove_const if constantized.respond_to?(:before_remove_const) + end + end - true + begin + parent.instance_eval { remove_const to_remove } + rescue NameError + log "the constant #{const} is not reachable anymore, skipping" + end + end end protected diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 3910a2dc42..1eb2b4212b 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -286,10 +286,12 @@ module ActiveSupport # ordinal(-11) # => "th" # ordinal(-1021) # => "st" def ordinal(number) - if (11..13).include?(number.to_i.abs % 100) + abs_number = number.to_i.abs + + if (11..13).include?(abs_number % 100) "th" else - case number.to_i.abs % 10 + case abs_number % 10 when 1; "st" when 2; "nd" when 3; "rd" diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index f65c831e04..7a5c351ca8 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -65,7 +65,7 @@ module ActiveSupport # they can detect circular references. options.merge(:encoder => self) else - options + options.dup end end diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb index 04d170f801..6beb2b6afa 100644 --- a/activesupport/lib/active_support/key_generator.rb +++ b/activesupport/lib/active_support/key_generator.rb @@ -1,3 +1,4 @@ +require 'mutex_m' require 'openssl' module ActiveSupport @@ -20,4 +21,57 @@ module ActiveSupport OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) end end + + # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid + # re-executing the key generation process when it's called using the same salt and + # key_size + class CachingKeyGenerator + def initialize(key_generator) + @key_generator = key_generator + @cache_keys = {}.extend(Mutex_m) + 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 + end + end + + class DummyKeyGenerator # :nodoc: + SECRET_MIN_LENGTH = 30 # Characters + + def initialize(secret) + ensure_secret_secure(secret) + @secret = secret + end + + def generate_key(salt) + @secret + end + + private + + # To prevent users from using something insecure like "Password" we make sure that the + # secret they've provided is at least 30 characters in length. + def ensure_secret_secure(secret) + if secret.blank? + raise ArgumentError, "A secret is required to generate an " + + "integrity hash for cookie session data. Use " + + "config.secret_key_base = \"some secret phrase of at " + + "least #{SECRET_MIN_LENGTH} characters\"" + + "in config/initializers/secret_token.rb" + end + + if secret.length < SECRET_MIN_LENGTH + raise ArgumentError, "Secret should be something secure, " + + "like \"#{SecureRandom.hex(16)}\". The value you " + + "provided, \"#{secret}\", is shorter than the minimum length " + + "of #{SECRET_MIN_LENGTH} characters" + end + end + end end diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 580267708c..1588674afc 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -39,10 +39,13 @@ module ActiveSupport # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'. # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+. - def initialize(secret, options = {}) + def initialize(secret, *signature_key_or_options) + options = signature_key_or_options.extract_options! + sign_secret = signature_key_or_options.first @secret = secret + @sign_secret = sign_secret @cipher = options[:cipher] || 'aes-256-cbc' - @verifier = MessageVerifier.new(@secret, :serializer => NullSerializer) + @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer) @serializer = options[:serializer] || Marshal end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 099117cebb..705a4693b7 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -84,15 +84,15 @@ module ActiveSupport # resulting in the following output within the logs including a hash with the payload: # # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 { - # :controller=>"Devise::SessionsController", - # :action=>"new", - # :params=>{"action"=>"new", "controller"=>"devise/sessions"}, - # :format=>:html, - # :method=>"GET", - # :path=>"/login/sign_in", - # :status=>200, - # :view_runtime=>279.3080806732178, - # :db_runtime=>40.053 + # controller: "Devise::SessionsController", + # action: "new", + # params: {"action"=>"new", "controller"=>"devise/sessions"}, + # format: :html, + # method: "GET", + # path: "/login/sign_in", + # status: 200, + # view_runtime: 279.3080806732178, + # db_runtime: 40.053 # } # # You can also subscribe to all events whose name matches a certain regexp: diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 8b06739b7f..ee7bda9f93 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -4,17 +4,20 @@ require 'active_support/testing/tagged_logging' require 'active_support/testing/setup_and_teardown' require 'active_support/testing/assertions' require 'active_support/testing/deprecation' +require 'active_support/testing/pending' require 'active_support/testing/isolation' -require 'active_support/testing/mocha_module' require 'active_support/testing/constant_lookup' require 'active_support/core_ext/kernel/reporting' require 'active_support/deprecation' +begin + silence_warnings { require 'mocha/setup' } +rescue LoadError +end + module ActiveSupport class TestCase < ::MiniTest::Spec - include ActiveSupport::Testing::MochaModule - # Use AS::TestCase for the base class when describing a model register_spec_type(self) do |desc| Class === desc && desc < ActiveRecord::Base @@ -38,6 +41,7 @@ module ActiveSupport include ActiveSupport::Testing::SetupAndTeardown include ActiveSupport::Testing::Assertions include ActiveSupport::Testing::Deprecation + include ActiveSupport::Testing::Pending def self.describe(text) if block_given? diff --git a/activesupport/lib/active_support/testing/mocha_module.rb b/activesupport/lib/active_support/testing/mocha_module.rb deleted file mode 100644 index ed2942d23a..0000000000 --- a/activesupport/lib/active_support/testing/mocha_module.rb +++ /dev/null @@ -1,22 +0,0 @@ -module ActiveSupport - module Testing - module MochaModule - begin - require 'mocha_standalone' - include Mocha::API - - def before_setup - mocha_setup - super - end - - def after_teardown - super - mocha_verify - mocha_teardown - end - rescue LoadError - end - end - end -end diff --git a/activesupport/lib/active_support/testing/pending.rb b/activesupport/lib/active_support/testing/pending.rb new file mode 100644 index 0000000000..944806bb64 --- /dev/null +++ b/activesupport/lib/active_support/testing/pending.rb @@ -0,0 +1,14 @@ +require 'active_support/deprecation' + +module ActiveSupport + module Testing + module Pending + unless defined?(Spec) + def pending(description = "", &block) + ActiveSupport::Deprecation.warn("#pending is deprecated and will be removed in Rails 4.1, please use #skip instead.") + skip(description.blank? ? nil : description) + end + end + end + end +end diff --git a/activesupport/lib/active_support/testing/tagged_logging.rb b/activesupport/lib/active_support/testing/tagged_logging.rb index 899467c45f..8ea2605733 100644 --- a/activesupport/lib/active_support/testing/tagged_logging.rb +++ b/activesupport/lib/active_support/testing/tagged_logging.rb @@ -1,10 +1,6 @@ -require 'active_support/concern' - module ActiveSupport module Testing module TaggedLogging - extend ActiveSupport::Concern - attr_writer :tagged_logger def before_setup diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index 88f9acb588..d082a0a499 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -76,23 +76,24 @@ module ActiveSupport ) end - attr_reader :backend delegate :parse, :to => :backend + def backend + current_thread_backend || @backend + end + def backend=(name) - if name.is_a?(Module) - @backend = name - else - require "active_support/xml_mini/#{name.downcase}" - @backend = ActiveSupport.const_get("XmlMini_#{name}") - end + backend = name && cast_backend_name_to_module(name) + self.current_thread_backend = backend if current_thread_backend + @backend = backend end def with_backend(name) - old_backend, self.backend = backend, name + old_backend = current_thread_backend + self.current_thread_backend = name && cast_backend_name_to_module(name) yield ensure - self.backend = old_backend + self.current_thread_backend = old_backend end def to_tag(key, value, options) @@ -163,6 +164,25 @@ module ActiveSupport f.content_type = entity['content_type'] f end + + private + + def current_thread_backend + Thread.current[:xml_mini_backend] + end + + def current_thread_backend=(name) + Thread.current[:xml_mini_backend] = name && cast_backend_name_to_module(name) + end + + def cast_backend_name_to_module(name) + if name.is_a?(Module) + name + else + require "active_support/xml_mini/#{name.downcase}" + ActiveSupport.const_get("XmlMini_#{name}") + end + end end XmlMini.backend = 'REXML' diff --git a/activesupport/test/autoloading_fixtures/should_not_be_required.rb b/activesupport/test/autoloading_fixtures/should_not_be_required.rb new file mode 100644 index 0000000000..1fcf170cc5 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/should_not_be_required.rb @@ -0,0 +1 @@ +ShouldNotBeAutoloaded = 0 diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb index ec05213409..9b62295c96 100644 --- a/activesupport/test/constantize_test_cases.rb +++ b/activesupport/test/constantize_test_cases.rb @@ -24,52 +24,52 @@ end module ConstantizeTestCases def run_constantize_tests_on - assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") } - assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") } - assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") } - assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") } - assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") } - assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") } - assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") } - assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") } - assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") } - assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") } - assert_nothing_raised { assert_equal Object, yield("") } - assert_nothing_raised { assert_equal Object, yield("::") } - assert_raise(NameError) { yield("UnknownClass") } - assert_raise(NameError) { yield("UnknownClass::Ace") } - assert_raise(NameError) { yield("UnknownClass::Ace::Base") } - assert_raise(NameError) { yield("An invalid string") } - assert_raise(NameError) { yield("InvalidClass\n") } - assert_raise(NameError) { yield("Ace::ConstantizeTestCases") } - assert_raise(NameError) { yield("Ace::Base::ConstantizeTestCases") } - assert_raise(NameError) { yield("Ace::Gas::Base") } - assert_raise(NameError) { yield("Ace::Gas::ConstantizeTestCases") } + assert_equal Ace::Base::Case, yield("Ace::Base::Case") + assert_equal Ace::Base::Case, yield("::Ace::Base::Case") + assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") + assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") + assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") + assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") + assert_equal Case::Dice, yield("Case::Dice") + assert_equal Case::Dice, yield("Object::Case::Dice") + assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") + assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") + assert_equal Object, yield("") + assert_equal Object, yield("::") + assert_raises(NameError) { yield("UnknownClass") } + assert_raises(NameError) { yield("UnknownClass::Ace") } + assert_raises(NameError) { yield("UnknownClass::Ace::Base") } + assert_raises(NameError) { yield("An invalid string") } + assert_raises(NameError) { yield("InvalidClass\n") } + assert_raises(NameError) { yield("Ace::ConstantizeTestCases") } + assert_raises(NameError) { yield("Ace::Base::ConstantizeTestCases") } + assert_raises(NameError) { yield("Ace::Gas::Base") } + assert_raises(NameError) { yield("Ace::Gas::ConstantizeTestCases") } end def run_safe_constantize_tests_on - assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") } - assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") } - assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") } - assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") } - assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") } - assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") } - assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") } - assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") } - assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") } - assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") } - assert_nothing_raised { assert_equal Object, yield("") } - assert_nothing_raised { assert_equal Object, yield("::") } - assert_nothing_raised { assert_equal nil, yield("UnknownClass") } - assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace") } - assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace::Base") } - assert_nothing_raised { assert_equal nil, yield("An invalid string") } - assert_nothing_raised { assert_equal nil, yield("InvalidClass\n") } - assert_nothing_raised { assert_equal nil, yield("blargle") } - assert_nothing_raised { assert_equal nil, yield("Ace::ConstantizeTestCases") } - assert_nothing_raised { assert_equal nil, yield("Ace::Base::ConstantizeTestCases") } - assert_nothing_raised { assert_equal nil, yield("Ace::Gas::Base") } - assert_nothing_raised { assert_equal nil, yield("Ace::Gas::ConstantizeTestCases") } - assert_nothing_raised { assert_equal nil, yield("#<Class:0x7b8b718b>::Nested_1") } + assert_equal Ace::Base::Case, yield("Ace::Base::Case") + assert_equal Ace::Base::Case, yield("::Ace::Base::Case") + assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") + assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") + assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") + assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") + assert_equal Case::Dice, yield("Case::Dice") + assert_equal Case::Dice, yield("Object::Case::Dice") + assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") + assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") + assert_equal Object, yield("") + assert_equal Object, yield("::") + assert_nil yield("UnknownClass") + assert_nil yield("UnknownClass::Ace") + assert_nil yield("UnknownClass::Ace::Base") + assert_nil yield("An invalid string") + assert_nil yield("InvalidClass\n") + assert_nil yield("blargle") + assert_nil yield("Ace::ConstantizeTestCases") + assert_nil yield("Ace::Base::ConstantizeTestCases") + assert_nil yield("Ace::Gas::Base") + assert_nil yield("Ace::Gas::ConstantizeTestCases") + assert_nil yield("#<Class:0x7b8b718b>::Nested_1") end end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 9dfa2cbf11..efa7582ab0 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -112,6 +112,14 @@ class ArrayExtToSTests < ActiveSupport::TestCase end class ArrayExtGroupingTests < ActiveSupport::TestCase + def setup + Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn) + end + + def teardown + Fixnum.send :public, :/ + end + def test_in_groups_of_with_perfect_fit groups = [] ('a'..'i').to_a.in_groups_of(3) do |group| diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 6746b58cd3..c378dcd01d 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -656,7 +656,9 @@ class HashExtTest < ActiveSupport::TestCase end def test_diff - assert_equal({ :a => 2 }, { :a => 2, :b => 5 }.diff({ :a => 1, :b => 5 })) + assert_deprecated do + assert_equal({ :a => 2 }, { :a => 2, :b => 5 }.diff({ :a => 1, :b => 5 })) + end end def test_slice diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index 439bc87323..1583c1fa32 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -51,6 +51,8 @@ class KernelTest < ActiveSupport::TestCase def test_capture assert_equal 'STDERR', capture(:stderr) { $stderr.print 'STDERR' } assert_equal 'STDOUT', capture(:stdout) { print 'STDOUT' } + assert_equal "STDERR\n", capture(:stderr) { system('echo STDERR 1>&2') } + assert_equal "STDOUT\n", capture(:stdout) { system('echo STDOUT') } end end diff --git a/activesupport/test/core_ext/struct_test.rb b/activesupport/test/core_ext/struct_test.rb new file mode 100644 index 0000000000..0dff7b32d2 --- /dev/null +++ b/activesupport/test/core_ext/struct_test.rb @@ -0,0 +1,10 @@ +require 'abstract_unit' +require 'active_support/core_ext/struct' + +class StructExt < ActiveSupport::TestCase + def test_to_h + x = Struct.new(:foo, :bar) + z = x.new(1, 2) + assert_equal({ foo: 1, bar: 2 }, z.to_h) + end +end diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 670a04e5df..67bd6669c5 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -928,6 +928,16 @@ class DependenciesTest < ActiveSupport::TestCase assert ! defined?(DeleteMe) end + def test_remove_constant_does_not_trigger_loading_autoloads + constant = 'ShouldNotBeAutoloaded' + Object.class_eval do + autoload constant, File.expand_path('../autoloading_fixtures/should_not_be_required', __FILE__) + end + + assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant" + assert !defined?(ShouldNotBeAutoloaded) + end + def test_load_once_constants_should_not_be_unloaded with_autoloading_fixtures do ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 7ed71f9abc..5bb2a45c87 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -22,6 +22,15 @@ class TestJSONEncoding < ActiveSupport::TestCase end end + class CustomWithOptions + attr_accessor :foo, :bar + + def as_json(options={}) + options[:only] = %w(foo bar) + super(options) + end + end + TrueTests = [[ true, %(true) ]] FalseTests = [[ false, %(false) ]] NilTests = [[ nil, %(null) ]] @@ -248,6 +257,15 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end + def test_to_json_should_not_keep_options_around + f = CustomWithOptions.new + f.foo = "hello" + f.bar = "world" + + hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}} + assert_equal(%({"foo":{"foo":"hello","bar":"world"},"other_hash":{"foo":"other_foo","test":"other_test"}}), hash.to_json) + end + def test_struct_encoding Struct.new('UserNameAndEmail', :name, :email) Struct.new('UserNameAndDate', :name, :date) diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index b544742300..d6c31396b6 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -56,7 +56,7 @@ class MessageEncryptorTest < ActiveSupport::TestCase end def test_alternative_serialization_method - encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), :serializer => JSONSerializer.new) + encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), SecureRandom.hex(64), :serializer => JSONSerializer.new) message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) }) assert_equal encryptor.decrypt_and_verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } end diff --git a/activesupport/test/spec_type_test.rb b/activesupport/test/spec_type_test.rb index 95a982d8fd..9a6cb4ded2 100644 --- a/activesupport/test/spec_type_test.rb +++ b/activesupport/test/spec_type_test.rb @@ -4,7 +4,6 @@ require "active_record" class SomeRandomModel < ActiveRecord::Base; end class SpecTypeTest < ActiveSupport::TestCase - def assert_support actual assert_equal ActiveSupport::TestCase, actual end @@ -13,7 +12,7 @@ class SpecTypeTest < ActiveSupport::TestCase assert_equal MiniTest::Spec, actual end - def test_spec_type_resolves_for_actitive_record_constants + def test_spec_type_resolves_for_active_record_constants assert_support MiniTest::Spec.spec_type(SomeRandomModel) end diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb index c02bfa8497..dfe9f3c11c 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -16,6 +16,9 @@ module ActiveSupport def options nil end + + def record(*args) + end end def test_standard_error_raised_within_setup_callback_is_puked @@ -105,5 +108,11 @@ module ActiveSupport test = tc.new test_name assert_raises(Interrupt) { test.run fr } end + + def test_pending_deprecation + assert_deprecated do + pending "should use #skip instead" + end + end end end diff --git a/activesupport/test/testing/constant_lookup_test.rb b/activesupport/test/testing/constant_lookup_test.rb index c56c032cde..19280ba74a 100644 --- a/activesupport/test/testing/constant_lookup_test.rb +++ b/activesupport/test/testing/constant_lookup_test.rb @@ -1,7 +1,7 @@ require 'abstract_unit' class Foo; end -class Bar < Foo; +class Bar < Foo def index; end def self.index; end end diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index 504fc96493..a025279e16 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -99,4 +99,66 @@ module XmlMiniTest end # TODO: test the remaining functions hidden in #to_tag. end + + class WithBackendTest < ActiveSupport::TestCase + module REXML end + module LibXML end + module Nokogiri end + + setup do + @xml = ActiveSupport::XmlMini + end + + test "#with_backend should switch backend and then switch back" do + @xml.backend = REXML + @xml.with_backend(LibXML) do + assert_equal LibXML, @xml.backend + @xml.with_backend(Nokogiri) do + assert_equal Nokogiri, @xml.backend + end + assert_equal LibXML, @xml.backend + end + assert_equal REXML, @xml.backend + end + + test "backend switch inside #with_backend block" do + @xml.with_backend(LibXML) do + @xml.backend = REXML + assert_equal REXML, @xml.backend + end + assert_equal REXML, @xml.backend + end + end + + class ThreadSafetyTest < ActiveSupport::TestCase + module REXML end + module LibXML end + + setup do + @xml = ActiveSupport::XmlMini + end + + test "#with_backend should be thread-safe" do + @xml.backend = REXML + t = Thread.new do + @xml.with_backend(LibXML) { sleep 1 } + end + sleep 0.1 while t.status != "sleep" + + # We should get `old_backend` here even while another + # thread is using `new_backend`. + assert_equal REXML, @xml.backend + end + + test "nested #with_backend should be thread-safe" do + @xml.with_backend(REXML) do + t = Thread.new do + @xml.with_backend(LibXML) { sleep 1 } + end + sleep 0.1 while t.status != "sleep" + + assert_equal REXML, @xml.backend + end + end + end end diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css index 9f5e101d1c..589c96e0e9 100644 --- a/guides/assets/stylesheets/main.css +++ b/guides/assets/stylesheets/main.css @@ -26,11 +26,13 @@ dl { margin: 0 0 1.5em 0; } dl dt { font-weight: bold; } dd { margin-left: 1.5em;} -pre,code { margin: 1.5em 0; overflow: auto; color: #222;} -pre,code { - font-size: 1em; - font-family: "Anonymous Pro", "Inconsolata", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; - line-height: 1.5; +pre, code { + font-size: 1em; + font-family: "Anonymous Pro", "Inconsolata", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + line-height: 1.5; + margin: 1.5em 0; + overflow: auto; + color: #222; } pre,tt,code,.note>p { white-space: pre-wrap; /* css-3 */ @@ -92,14 +94,14 @@ body { line-height: 1.5em; background: #fff; color: #999; - } +} .wrapper { text-align: left; margin: 0 auto; max-width: 960px; padding: 0 1em; - } +} .red-button { display: inline-block; @@ -164,7 +166,6 @@ body { .more-info:last-child:after { content: ""; } - } @media screen and (max-width: 1024px) { @@ -218,7 +219,7 @@ body { color: #FFF; padding: 1.5em 0; z-index: 99; - } +} #feature { background: #d5e9f6 url(../images/feature_tile.gif) repeat-x; @@ -229,12 +230,12 @@ body { #container { color: #333; padding: 0.5em 0 1.5em 0; - } +} #mainCol { max-width: 630px; margin-left: 2em; - } +} #subCol { position: absolute; @@ -247,7 +248,7 @@ body { font-size: 0.9285em; line-height: 1.3846em; margin-right: 1em; - } +} @media screen and (max-width: 800px) { @@ -265,7 +266,7 @@ body { #footer { padding: 2em 0; background: #222 url(../images/footer_tile.gif) repeat-x; - } +} #footer .wrapper { padding-left: 1em; max-width: 960px; @@ -284,12 +285,11 @@ body { a, a:link, a:visited { color: #ee3f3f; text-decoration: underline; - } +} #mainCol a, #subCol a, #feature a {color: #980905;} #mainCol a code, #subCol a code, #feature a code {color: #980905;} - /* Navigation --------------------------------------- */ @@ -313,7 +313,6 @@ a, a:link, a:visited { background: #980905; position: relative; color: white; - cursor: pointer; } .guides-index .guides-index-item { @@ -345,7 +344,6 @@ a, a:link, a:visited { } @media screen and (max-width: 480px) { - .nav { float: none; width: 100%; @@ -408,7 +406,8 @@ a, a:link, a:visited { padding: 0; } #guides dt {padding:0; margin: 0.5em 0 0;} -#guides a {color: #FFF; background: none !important;} +#guides a {color: #FFF; background: none !important; text-decoration: none;} +#guides a:hover {text-decoration: underline;} #guides .L, #guides .R {float: left; width: 50%; margin: 0; padding: 0;} #guides .R {float: right;} #guides hr { @@ -427,14 +426,14 @@ h1 { line-height: 1em; margin: 0.6em 0 .2em; font-weight: bold; - } +} h2 { font-size: 2.1428em; line-height: 1em; margin: 0.7em 0 .2333em; font-weight: bold; - } +} @media screen and (max-width: 480px) { h2 { @@ -447,7 +446,7 @@ h3 { line-height: 1.286em; margin: 0.875em 0 0.2916em; font-weight: bold; - } +} @media screen and (max-width: 480px) { h3 { @@ -460,7 +459,7 @@ h4 { line-height: 1.2em; margin: 1.6667em 0 .3887em; font-weight: bold; - } +} h5 { font-size: 1em; @@ -474,7 +473,7 @@ h6 { line-height: 1.5em; margin: 1em 0 .5em; font-weight: normal; - } +} .section { padding-bottom: 0.25em; @@ -542,13 +541,19 @@ h6 { } #mainCol dt, #subCol dt { - font-size: 1em; + font-size: 1.2857em; padding: 0.125em 0 0.25em 0; margin-bottom: 0; /*background: url(../images/book_icon.gif) no-repeat left top; padding: 0.125em 0 0.25em 28px;*/ } +@media screen and (max-width: 480px) { + #mainCol dt, #subCol dt { + font-size: 1em; + } +} + #mainCol dd.work-in-progress, #subCol dd.work-in-progress { background: #fff9d8 url(../images/tab_yellow.gif) no-repeat left top; border: none; @@ -609,10 +614,10 @@ div.code_container { } #mainCol div.todo { - background: #fff9d8 url(../images/tab_yellow.gif) no-repeat left top; - border: none; - padding: 1em 1em 0.25em 48px; - margin: 0.25em 0 1.5em 0; + background: #fff9d8 url(../images/tab_yellow.gif) no-repeat left top; + border: none; + padding: 1em 1em 0.25em 48px; + margin: 0.25em 0 1.5em 0; } .note code, .info code, .todo code {border:none; background: none; padding: 0;} @@ -640,11 +645,11 @@ div.code_container { --------------------------------------- */ .clearfix:after { - content: "."; - display: block; - height: 0; - clear: both; - visibility: hidden; + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; } .clearfix {display: inline-block;} @@ -655,13 +660,13 @@ div.code_container { /* Same bottom margin for special boxes than for regular paragraphs, this way intermediate whitespace looks uniform. */ div.code_container, div.important, div.caution, div.warning, div.note, div.info { - margin-bottom: 1.5em; + margin-bottom: 1.5em; } /* Remove bottom margin of paragraphs in special boxes, otherwise they get a spurious blank area below with the box background. */ div.important p, div.caution p, div.warning p, div.note p, div.info p { - margin-bottom: 1em; + margin-bottom: 1em; } /* Edge Badge @@ -683,19 +688,18 @@ table td, table th { padding: 9px 10px; text-align: left; } /* Mobile */ @media only screen and (max-width: 767px) { - - table.responsive { margin-bottom: 0; } - - .pinned { position: absolute; left: 0; top: 0; background: #fff; width: 35%; overflow: hidden; overflow-x: scroll; border-right: 1px solid #ccc; border-left: 1px solid #ccc; } - .pinned table { border-right: none; border-left: none; width: 100%; } - .pinned table th, .pinned table td { white-space: nowrap; } - .pinned td:last-child { border-bottom: 0; } - - div.table-wrapper { position: relative; margin-bottom: 20px; overflow: hidden; border-right: 1px solid #ccc; } - div.table-wrapper div.scrollable table { margin-left: 35%; } - div.table-wrapper div.scrollable { overflow: scroll; overflow-y: hidden; } - - table.responsive td, table.responsive th { position: relative; white-space: nowrap; overflow: hidden; } - table.responsive th:first-child, table.responsive td:first-child, table.responsive td:first-child, table.responsive.pinned td { display: none; } - + table.responsive { margin-bottom: 0; } + + .pinned { position: absolute; left: 0; top: 0; background: #fff; width: 35%; overflow: hidden; overflow-x: scroll; border-right: 1px solid #ccc; border-left: 1px solid #ccc; } + .pinned table { border-right: none; border-left: none; width: 100%; } + .pinned table th, .pinned table td { white-space: nowrap; } + .pinned td:last-child { border-bottom: 0; } + + div.table-wrapper { position: relative; margin-bottom: 20px; overflow: hidden; border-right: 1px solid #ccc; } + div.table-wrapper div.scrollable table { margin-left: 35%; } + div.table-wrapper div.scrollable { overflow: scroll; overflow-y: hidden; } + + table.responsive td, table.responsive th { position: relative; white-space: nowrap; overflow: hidden; } + table.responsive th:first-child, table.responsive td:first-child, table.responsive td:first-child, table.responsive.pinned td { display: none; } + } diff --git a/guides/code/getting_started/config/initializers/secret_token.rb b/guides/code/getting_started/config/initializers/secret_token.rb index f36ebdda18..969ecaad65 100644 --- a/guides/code/getting_started/config/initializers/secret_token.rb +++ b/guides/code/getting_started/config/initializers/secret_token.rb @@ -6,4 +6,4 @@ # no regular words or you'll be exposed to dictionary attacks. # Make sure your secret key is kept private # if you're sharing your code publicly. -Blog::Application.config.secret_token = '685a9bf865b728c6549a191c90851c1b5ec41ecb60b9e94ad79dd3f824749798aa7b5e94431901960bee57809db0947b481570f7f13376b7ca190fa28099c459' +Blog::Application.config.secret_key_base = '685a9bf865b728c6549a191c90851c1b5ec41ecb60b9e94ad79dd3f824749798aa7b5e94431901960bee57809db0947b481570f7f13376b7ca190fa28099c459' diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb index 2f36af1fb3..c3fe5b8799 100644 --- a/guides/rails_guides/markdown/renderer.rb +++ b/guides/rails_guides/markdown/renderer.rb @@ -9,7 +9,7 @@ module RailsGuides <<-HTML <div class="code_container"> <pre class="brush: #{brush_for(language)}; gutter: false; toolbar: false"> -#{ERB::Util.h(code).strip} +#{ERB::Util.h(code)} </pre> </div> HTML diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index f24a981c6e..ecb8dd04f5 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -74,9 +74,48 @@ Documentation * Guides are rewritten in GitHub Flavored Markdown. +* Guides have a responsive design. + Railties -------- +* Ensure that RAILS_ENV is set when accessing Rails.env. + +* Don't eager-load app/assets and app/views. + +* Add `.rake` to list of file extensions included by `rake notes` and `rake notes:custom`. + +* New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well. + +* Set a different cache per environment for assets pipeline through `config.assets.cache`. + +* `Rails.public_path` now returns a Pathname object. + +* Remove highly uncommon `config.assets.manifest` option for moving the manifest path. This option is now unsupported in sprockets-rails. + +* Add `config.action_controller.permit_all_parameters` to disable StrongParameters protection, it's false by default. + +* Remove `config.active_record.whitelist_attributes` and `config.active_record.mass_assignment_sanitizer` from new applications since MassAssignmentSecurity has been extracted from Rails. + +* Change `rails new` and `rails plugin new` generators to name the `.gitkeep` files as `.keep` in a more SCM-agnostic way. Change `--skip-git` option to only skip the `.gitignore` file and still generate the `.keep` files. Add `--skip-keeps` option to skip the `.keep` files. + +* Fixed support for DATABASE_URL environment variable for rake db tasks. + +* rails dbconsole now can use SSL for MySQL. The database.yml options sslca, sslcert, sslcapath, sslcipher and sslkey now affect rails dbconsole. + +* Correctly handle SCRIPT_NAME when generating routes to engine in application that's mounted at a sub-uri. With this behavior, you *should not* use default_url_options[:script_name] to set proper application's mount point by yourself. + +* `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded. + +* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance + + rails g migration AddReferencesToProducts user:references supplier:references{polymorphic} + + will generate the migration with: + + add_reference :products, :user, index: true + add_reference :products, :supplier, polymorphic: true, index: true + * Allow scaffold/model/migration generators to accept a `polymorphic` modifier for `references`/`belongs_to`, for instance ``` diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index dd8d229e6a..6f161e83ea 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -81,7 +81,7 @@ class ClientsController < ActionController::Base else # This line overrides the default rendering behavior, which # would have been to render the "create" view. - render action: "new" + render "new" end end end @@ -179,9 +179,9 @@ Your application has a session for each user in which you can store small amount All session stores use a cookie to store a unique ID for each session (you must use a cookie, Rails will not allow you to pass the session ID in the URL as this is less secure). -For most stores this ID is used to look up the session data on the server, e.g. in a database table. There is one exception, and that is the default and recommended session store - the CookieStore - which stores all session data in the cookie itself (the ID is still available to you if you need it). This has the advantage of being very lightweight and it requires zero setup in a new application in order to use the session. The cookie data is cryptographically signed to make it tamper-proof, but it is not encrypted, so anyone with access to it can read its contents but not edit it (Rails will not accept it if it has been edited). +For most stores, this ID is used to look up the session data on the server, e.g. in a database table. There is one exception, and that is the default and recommended session store - the CookieStore - which stores all session data in the cookie itself (the ID is still available to you if you need it). This has the advantage of being very lightweight and it requires zero setup in a new application in order to use the session. The cookie data is cryptographically signed to make it tamper-proof, but it is not encrypted, so anyone with access to it can read its contents but not edit it (Rails will not accept it if it has been edited). -The CookieStore can store around 4kB of data -- much less than the others -- but this is usually enough. Storing large amounts of data in the session is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the most common example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. +The CookieStore can store around 4kB of data — much less than the others — but this is usually enough. Storing large amounts of data in the session is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the most common example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. If your user sessions don't store critical data or don't need to be around for long periods (for instance if you just use the flash for messaging), you can consider using ActionDispatch::Session::CacheStore. This will store sessions using the cache implementation you have configured for your application. The advantage of this is that you can use your existing cache infrastructure for storing sessions without requiring any additional setup or administration. The downside, of course, is that the sessions will be ephemeral and could disappear at any time. @@ -219,7 +219,7 @@ Rails sets up (for the CookieStore) a secret key used for signing the session da # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. -YourApp::Application.config.secret_token = '49d3f3de9ed86c74b94ad6bd0...' +YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...' ``` NOTE: Changing the secret when using the `CookieStore` will invalidate all existing sessions. @@ -371,13 +371,13 @@ end Cookies ------- -Your application can store small amounts of data on the client -- called cookies -- that will be persisted across requests and even sessions. Rails provides easy access to cookies via the `cookies` method, which -- much like the `session` -- works like a hash: +Your application can store small amounts of data on the client — called cookies — that will be persisted across requests and even sessions. Rails provides easy access to cookies via the `cookies` method, which — much like the `session` — works like a hash: ```ruby class CommentsController < ApplicationController def new # Auto-fill the commenter's name if it has been stored in a cookie - @comment = Comment.new(name: cookies[:commenter_name]) + @comment = Comment.new(author: cookies[:commenter_name]) end def create @@ -386,7 +386,7 @@ class CommentsController < ApplicationController flash[:notice] = "Thanks for your comment!" if params[:remember_name] # Remember the commenter's name. - cookies[:commenter_name] = @comment.name + cookies[:commenter_name] = @comment.author else # Delete cookie for the commenter's name cookie, if any. cookies.delete(:commenter_name) @@ -404,7 +404,7 @@ Note that while for session values you set the key to `nil`, to delete a cookie Rendering xml and json data --------------------------- -ActionController makes it extremely easy to render `xml` or `json` data. If you generate a controller using scaffold then your controller would look something like this. +ActionController makes it extremely easy to render `xml` or `json` data. If you generate a controller using scaffolding then it would look something like this: ```ruby class UsersController < ApplicationController @@ -428,7 +428,7 @@ Filters are methods that are run before, after or "around" a controller action. Filters are inherited, so if you set a filter on `ApplicationController`, it will be run on every controller in your application. -Before filters may halt the request cycle. A common before filter is one which requires that a user is logged in for an action to be run. You can define the filter method this way: +"Before" filters may halt the request cycle. A common "before" filter is one which requires that a user is logged in for an action to be run. You can define the filter method this way: ```ruby class ApplicationController < ActionController::Base @@ -454,7 +454,7 @@ class ApplicationController < ActionController::Base 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. +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`: @@ -468,11 +468,11 @@ Now, the `LoginsController`'s `new` and `create` actions will work as before wit ### After Filters and Around Filters -In addition to before filters, you can also run filters after an action has been executed, or both before and after. +In addition to "before" filters, you can also run filters after an action has been executed, or both before and after. -After filters are similar to before filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, after filters cannot stop the action from running. +"After" filters are similar to "before" filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, "after" filters cannot stop the action from running. -Around filters are responsible for running their associated actions by yielding, similar to how Rack middlewares work. +"Around" filters are responsible for running their associated actions by yielding, similar to how Rack middlewares work. For example, in a website where changes have an approval workflow an administrator could be able to preview them easily, just apply them within a transaction: @@ -494,7 +494,7 @@ class ChangesController < ActionController::Base end ``` -Note that an around filter also wraps rendering. In particular, if in the example above, the view itself reads from the database (e.g. via a scope), it will do so within the transaction and thus present the data to preview. +Note that an "around" filter also wraps rendering. In particular, if in the example above, the view itself reads from the database (e.g. via a scope), it will do so within the transaction and thus present the data to preview. You can choose not to yield and build the response yourself, in which case the action will not be run. @@ -616,6 +616,8 @@ If you want to set custom headers for a response then `response.headers` is the response.headers["Content-Type"] = "application/pdf" ``` +Note: in the above case it would make more sense to use the `content_type` setter directly. + HTTP Authentications -------------------- @@ -711,7 +713,7 @@ This will read and stream the file 4kB at the time, avoiding loading the entire If `:type` is not specified, it will be guessed from the file extension specified in `:filename`. If the content type is not registered for the extension, `application/octet-stream` will be used. -WARNING: Be careful when using data coming from the client (params, cookies, etc.) to locate the file on disk, as this is a security risk that might allow someone to gain access to files they are not meant to see. +WARNING: Be careful when using data coming from the client (params, cookies, etc.) to locate the file on disk, as this is a security risk that might allow someone to gain access to files they are not meant to. TIP: It is not recommended that you stream static files through Rails if you can instead keep them in a public folder on your web server. It is much more efficient to let the user download the file directly using Apache or another web server, keeping the request from unnecessarily going through the whole Rails stack. @@ -824,7 +826,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 `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. Since Rails 3.1 you can now use the `force_ssl` method in your controller to enforce that: ```ruby class DinnerController @@ -832,7 +834,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 passing `:only` and `:except` to enforce the secure connection only to specific actions: ```ruby class DinnerController @@ -842,4 +844,4 @@ class DinnerController end ``` -Please note that if you found yourself adding `force_ssl` to many controllers, you may found yourself wanting to force the whole application to use HTTPS instead. In that case, you can set the `config.force_ssl` in your environment file. +Please note that if you find yourself adding `force_ssl` to many controllers, you may want to force the whole application to use HTTPS instead. In that case, you can set the `config.force_ssl` in your environment file. diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index a938db6265..8687cfea52 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -293,7 +293,7 @@ class UserMailer < ActionMailer::Base end ``` -In this case it will look for templates at `app/views/notifications` with name `another`. +In this case it will look for templates at `app/views/notifications` with name `another`. You can also specify an array of paths for `template_path`, and they will be searched in order. If you want more flexibility you can also pass a block and render specific templates or even render inline or text without using a template file: @@ -377,23 +377,7 @@ If you use this setting, you should pass the `only_path: false` option when usin Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have `welcome_email.text.erb` and `welcome_email.html.erb` in `app/views/user_mailer`, Action Mailer will automatically send a multipart email with the HTML and text versions setup as different parts. -The order of the parts getting inserted is determined by the `:parts_order` inside of the `ActionMailer::Base.default` method. If you want to explicitly alter the order, you can either change the `:parts_order` or explicitly render the parts in a different order: - -```ruby -class UserMailer < ActionMailer::Base - def welcome_email(user) - @user = user - @url = user_url(@user) - mail(to: user.email, - subject: 'Welcome to My Awesome Site') do |format| - format.html - format.text - end - end -end -``` - -Will put the HTML part first, and the plain text part second. +The order of the parts getting inserted is determined by the `:parts_order` inside of the `ActionMailer::Base.default` method. ### Sending Emails with Attachments diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index cec7e5335b..2625e237bf 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -3,11 +3,11 @@ Action View Overview In this guide you will learn: -* What Action View is, and how to use it with Rails -* How to use Action View outside of Rails +* What Action View is and how to use it with Rails * How best to use templates, partials, and layouts -* What helpers are provided by Action View, and how to make your own +* What helpers are provided by Action View and how to make your own * How to use localized views +* How to use Action View outside of Rails -------------------------------------------------------------------------------- @@ -18,7 +18,7 @@ Action View and Action Controller are the two major components of Action Pack. I Action View templates are written using embedded Ruby in tags mingled with HTML. To avoid cluttering the templates with boilerplate code, a number of helper classes provide common behavior for forms, dates, and strings. It's also easy to add new helpers to your application as it evolves. -NOTE. Some features of Action View are tied to Active Record, but that doesn't mean that Action View depends on Active Record. Action View is an independent package that can be used with any sort of backend. +NOTE: Some features of Action View are tied to Active Record, but that doesn't mean Action View depends on Active Record. Action View is an independent package that can be used with any sort of Ruby libraries. Using Action View with Rails ---------------------------- @@ -44,82 +44,14 @@ $ rails generate scaffold post There is a naming convention for views in Rails. Typically, the views share their name with the associated controller action, as you can see above. For example, the index controller action of the `posts_controller.rb` will use the `index.html.erb` view file in the `app/views/posts` directory. -The complete HTML returned to the client is composed of a combination of this ERB file, a layout template that wraps it, and all the partials that the view may reference. Later on this guide you can find a more detailed documentation of each one of this three components. - -Using Action View outside of Rails ----------------------------------- - -Action View works well with Action Record, but it can also be used with other Ruby tools. We can demonstrate this by creating a small [Rack](http://rack.rubyforge.org/) application that includes Action View functionality. This may be useful, for example, if you'd like access to Action View's helpers in a Rack application. - -Let's start by ensuring that you have the Action Pack and Rack gems installed: - -```bash -$ gem install actionpack -$ gem install rack -``` - -Now we'll create a simple "Hello World" application that uses the `titleize` method provided by Active Support. - -**hello_world.rb:** - -```ruby -require 'active_support/core_ext/string/inflections' -require 'rack' - -def hello_world(env) - [200, {"Content-Type" => "text/html"}, "hello world".titleize] -end - -Rack::Handler::Mongrel.run method(:hello_world), :Port => 4567 -``` - -We can see this all come together by starting up the application and then visiting `http://localhost:4567/` - -```bash -$ ruby hello_world.rb -``` +The complete HTML returned to the client is composed of a combination of this ERB file, a layout template that wraps it, and all the partials that the view may reference. Later on this guide you can find a more detailed documentation of each one of these three components. -TODO needs a screenshot? I have one - not sure where to put it. - -Notice how 'hello world' has been converted into 'Hello World' by the `titleize` helper method. - -Action View can also be used with [Sinatra](http://www.sinatrarb.com/) in the same way. - -Let's start by ensuring that you have the Action Pack and Sinatra gems installed: - -```bash -$ gem install actionpack -$ gem install sinatra -``` - -Now we'll create the same "Hello World" application in Sinatra. - -**hello_world.rb:** - -```ruby -require 'action_view' -require 'sinatra' - -get '/' do - erb 'hello world'.titleize -end -``` - -Then, we can run the application: - -```bash -$ ruby hello_world.rb -``` - -Once the application is running, you can see Sinatra and Action View working together by visiting `http://localhost:4567/` - -TODO needs a screenshot? I have one - not sure where to put it. Templates, Partials and Layouts ------------------------------- As mentioned before, the final HTML output is a composition of three Rails elements: `Templates`, `Partials` and `Layouts`. -Find below a brief overview of each one of them. +Below is a brief overview of each one of them. ### Templates @@ -129,18 +61,18 @@ Rails supports multiple template systems and uses a file extension to distinguis #### ERB -Within an ERB template Ruby code can be included using both `<% %>` and `<%= %>` tags. The `<% %>` are used to execute Ruby code that does not return anything, such as conditions, loops or blocks, and the `<%= %>` tags are used when you want output. +Within an ERB template, Ruby code can be included using both `<% %>` and `<%= %>` tags. The `<% %>` tags are used to execute Ruby code that does not return anything, such as conditions, loops or blocks, and the `<%= %>` tags are used when you want output. Consider the following loop for names: ```html+erb -<b>Names of all the people</b> +<h1>Names of all the people</h1> <% @people.each do |person| %> Name: <%= person.name %><br/> <% end %> ``` -The loop is setup in regular embedding tags `<% %>` and the name is written using the output embedding tag `<%= %>`. Note that this is not just a usage suggestion, for Regular output functions like print or puts won't work with ERB templates. So this would be wrong: +The loop is set up in regular embedding tags (`<% %>`) and the name is written using the output embedding tags (`<%= %>`). Note that this is not just a usage suggestion, for regular output functions like `print` or `puts` won't work with ERB templates. So this would be wrong: ```html+erb <%# WRONG %> @@ -158,11 +90,11 @@ Here are some basic examples: ```ruby xml.em("emphasized") xml.em { xml.b("emph & bold") } -xml.a("A Link", "href"=>"http://rubyonrails.org") -xml.target("name"=>"compile", "option"=>"fast") +xml.a("A Link", "href" => "http://rubyonrails.org") +xml.target("name" => "compile", "option" => "fast") ``` -will produce +which would produce: ```html <em>emphasized</em> @@ -189,7 +121,7 @@ would produce something like: </div> ``` -A full-length RSS example actually used on Basecamp: +Below is a full-length RSS example actually used on Basecamp: ```ruby xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do @@ -220,7 +152,7 @@ By default, Rails will compile each template to a method in order to render it. ### Partials -Partial templates – usually just called "partials" – are another device for breaking the rendering process into more manageable chunks. With a partial, you can move the code for rendering a particular piece of a response to its own file. +Partial templates – usually just called "partials" – are another device for breaking the rendering process into more manageable chunks. With partials, you can extract pieces of code from your templates to separate files and also reuse them throughout your templates. #### Naming Partials @@ -230,7 +162,7 @@ To render a partial as part of a view, you use the `render` method within the vi <%= render "menu" %> ``` -This will render a file named `_menu.html.erb` at that point within the view is being rendered. Note the leading underscore character: partials are named with a leading underscore to distinguish them from regular views, even though they are referred to without the underscore. This holds true even when you're pulling in a partial from another folder: +This will render a file named `_menu.html.erb` at that point within the view that is being rendered. Note the leading underscore character: partials are named with a leading underscore to distinguish them from regular views, even though they are referred to without the underscore. This holds true even when you're pulling in a partial from another folder: ```erb <%= render "shared/menu" %> @@ -249,7 +181,7 @@ One way to use partials is to treat them as the equivalent of subroutines: as a <p>Here are a few of our fine products:</p> <% @products.each do |product| %> - <%= render :partial => "product", :locals => { :product => product } %> + <%= render partial: "product", locals: {product: product} %> <% end %> <%= render "shared/footer" %> @@ -257,76 +189,89 @@ One way to use partials is to treat them as the equivalent of subroutines: as a Here, the `_ad_banner.html.erb` and `_footer.html.erb` partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page. -#### The :as and :object options +#### The `as` and `object` options -By default `ActionView::Partials::PartialRenderer` has its object in a local variable with the same name as the template. So, given +By default `ActionView::Partials::PartialRenderer` has its object in a local variable with the same name as the template. So, given: ```erb -<%= render :partial => "product" %> +<%= render partial: "product" %> ``` within product we'll get `@product` in the local variable `product`, as if we had written: ```erb -<%= render :partial => "product", :locals => { :product => @product } %> +<%= render partial: "product", locals: {product: @product} %> ``` -With the `:as` option we can specify a different name for said local variable. For example, if we wanted it to be `item` instead of product+ we'd do: +With the `as` option we can specify a different name for the local variable. For example, if we wanted it to be `item` instead of `product` we would do: ```erb -<%= render :partial => "product", :as => 'item' %> +<%= render partial: "product", as: "item" %> ``` -The `:object` option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere, in a different ivar or in a local variable for instance. +The `object` option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere (eg. in a different instance variable or in a local variable). For example, instead of: ```erb -<%= render :partial => "product", :locals => { :product => @item } %> +<%= render partial: "product", locals: {product: @item} %> ``` -you'd do: +we would do: ```erb -<%= render :partial => "product", :object => @item %> +<%= render partial: "product", object: @item %> ``` -The `:object` and `:as` options can be used together. +The `object` and `as` options can also be used together: + +```erb +<%= render partial: "product", object: @item, as: "item" %> +``` #### Rendering Collections -The example of partial use describes a familiar pattern where a template needs to iterate over an array and render a sub template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders a partial by the same name as the elements contained within. -So the three-lined example for rendering all the products can be rewritten with a single line: +It is very common that a template needs to iterate over a collection and render a sub-template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders a partial for each one of the elements in the array. + +So this example for rendering all the products: ```erb -<%= render :partial => "product", :collection => @products %> +<% @products.each do |product| %> + <%= render partial: "product", locals: { product: product } %> +<% end %> ``` -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. +can be rewritten in a single line: + +```erb +<%= render partial: "product", collection: @products %> +``` -You can use a shorthand syntax for rendering collections. Assuming @products is a collection of `Product` instances, you can simply write the following to produce the same result: +When a partial is called like this (eg. with a collection), 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 it you can refer to `product` to get the instance that is being rendered. + +You can use a shorthand syntax for rendering collections. Assuming `@products` is a collection of `Product` instances, you can simply write the following to produce the same result: ```erb <%= render @products %> ``` -Rails determines the name of the partial to use by looking at the model name in the collection. In fact, you can even create a heterogeneous collection and render it this way, and Rails will choose the proper partial for each member of the collection. +Rails determines the name of the partial to use by looking at the model name in the collection, `Product` in this case. In fact, you can even create a heterogeneous collection and render it this way, and Rails will choose the proper partial for each member of the collection. #### Spacer Templates You can also specify a second partial to be rendered between instances of the main partial by using the `:spacer_template` option: ```erb -<%= render @products, :spacer_template => "product_ruler" %> +<%= render @products, spacer_template: "product_ruler" %> ``` -Rails will render the `_product_ruler` partial (with no data passed in to it) between each pair of `_product` partials. +Rails will render the `_product_ruler` partial (with no data passed to it) between each pair of `_product` partials. ### Layouts TODO... -Using Templates, Partials and Layouts in "The Rails Way" +Using Templates, Partials and Layouts "The Rails Way" -------------------------------------------------------- TODO... @@ -336,21 +281,21 @@ Partial Layouts Partials can have their own layouts applied to them. These layouts are different than the ones that are specified globally for the entire action, but they work in a similar fashion. -Let's say we're displaying a post on a page where it should be wrapped in a `div` for display purposes. First, we'll create a new `Post`: +Let's say we're displaying a post on a page, that should be wrapped in a `div` for display purposes. First, we'll create a new `Post`: ```ruby -Post.create(:body => 'Partial Layouts are cool!') +Post.create(body: 'Partial Layouts are cool!') ``` -In the `show` template, we'll render the `post` partial wrapped in the `box` layout: +In the `show` template, we'll render the `_post` partial wrapped in the `box` layout: **posts/show.html.erb** ```erb -<%= render :partial => 'post', :layout => 'box', :locals => {:post => @post} %> +<%= render partial: 'post', layout: 'box', locals: {post: @post} %> ``` -The `box` layout simply wraps the `post` partial in a `div`: +The `box` layout simply wraps the `_post` partial in a `div`: **posts/_box.html.erb** @@ -360,7 +305,7 @@ The `box` layout simply wraps the `post` partial in a `div`: </div> ``` -The `post` partial wraps the post's `body` in a `div` with the `id` of the post using the `div_for` helper: +The `_post` partial wraps the post's `body` in a `div` with the `id` of the post using the `div_for` helper: **posts/_post.html.erb** @@ -370,7 +315,7 @@ The `post` partial wraps the post's `body` in a `div` with the `id` of the post <% end %> ``` -This example would output the following: +this would output the following: ```html <div class='box'> @@ -382,84 +327,31 @@ This example would output the following: Note that the partial layout has access to the local `post` variable that was passed into the `render` call. However, unlike application-wide layouts, partial layouts still have the underscore prefix. -You can also render a block of code within a partial layout instead of calling `yield`. For example, if we didn't have the `post` partial, we could do this instead: +You can also render a block of code within a partial layout instead of calling `yield`. For example, if we didn't have the `_post` partial, we could do this instead: **posts/show.html.erb** ```html+erb -<% render(:layout => 'box', :locals => {:post => @post}) do %> +<% render(layout: 'box', locals: {post: @post}) do %> <%= div_for(post) do %> <p><%= post.body %></p> <% end %> <% end %> ``` -If we're using the same `box` partial from above, his would produce the same output as the previous example. +Supposing we use the same `_box` partial from above, this would produce the same output as the previous example. View Paths ---------- TODO... -Overview of all the helpers provided by Action View ---------------------------------------------------- - -The following is only a brief overview summary of the helpers available in Action View. It's recommended that you review the API Documentation, which covers all of the helpers in more detail, but this should serve as a good starting point. - -### ActiveRecordHelper - -The Active Record Helper makes it easier to create forms for records kept in instance variables. You may also want to review the [Rails Form helpers guide](form_helpers.html). - -#### error_message_on +Overview of helpers provided by Action View +------------------------------------------- -Returns a string containing the error message attached to the method on the object if one exists. +WIP: Not all the helpers are listed here. For a full list see the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers.html) -```ruby -error_message_on "post", "title" -``` - -#### error_messages_for - -Returns a string with a DIV containing all of the error messages for the objects located as instance variables by the names given. - -```ruby -error_messages_for "post" -``` - -#### form - -Returns a form with inputs for all attributes of the specified Active Record object. For example, let's say we have a `@post` with attributes named `title` of type `String` and `body` of type `Text`. Calling `form` would produce a form to creating a new post with inputs for those attributes. - -```ruby -form("post") -``` - -```html -<form action='/posts/create' method='post'> - <p> - <label for="post_title">Title</label><br /> - <input id="post_title" name="post[title]" type="text" value="Hello World" /> - </p> - <p> - <label for="post_body">Body</label><br /> - <textarea id="post_body" name="post[body]"></textarea> - </p> - <input name="commit" type="submit" value="Create" /> -</form> -``` - -Typically, `form_for` is used instead of `form` because it doesn't automatically include all of the model's attributes. - -#### input - -Returns a default input tag for the type of object returned by the method. - -For example, if `@post` has an attribute `title` mapped to a `String` column that holds "Hello World": - -```ruby -input("post", "title") # => - <input id="post_title" name="post[title]" type="text" value="Hello World" /> -``` +The following is only a brief overview summary of the helpers available in Action View. It's recommended that you review the [API Documentation](http://api.rubyonrails.org/classes/ActionView/Helpers.html), which covers all of the helpers in more detail, but this should serve as a good starting point. ### RecordTagHelper @@ -488,7 +380,7 @@ This will generate this HTML output: You can also supply HTML attributes as an additional option hash. For example: ```html+erb -<%= content_tag_for(:tr, @post, :class => "frontpage") do %> +<%= content_tag_for(:tr, @post, class: "frontpage") do %> <td><%= @post.title %></td> <% end %> ``` @@ -525,7 +417,7 @@ Will generate this HTML output: This is actually a convenient method which calls `content_tag_for` internally with `:div` as the tag name. You can pass either an Active Record object or a collection of objects. For example: ```html+erb -<%= div_for(@post, :class => "frontpage") do %> +<%= div_for(@post, class: "frontpage") do %> <td><%= @post.title %></td> <% end %> ``` @@ -554,7 +446,7 @@ image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png Register one or more JavaScript files to be included when symbol is passed to javascript_include_tag. This method is typically intended to be called from plugin initialization to register JavaScript files that the plugin installed in `vendor/assets/javascripts`. ```ruby -ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"] +ActionView::Helpers::AssetTagHelper.register_javascript_expansion monkey: ["head", "body", "tail"] javascript_include_tag :monkey # => <script src="/assets/head.js"></script> @@ -567,7 +459,7 @@ javascript_include_tag :monkey # => Register one or more stylesheet files to be included when symbol is passed to `stylesheet_link_tag`. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in `vendor/assets/stylesheets`. ```ruby -ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"] +ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion monkey: ["head", "body", "tail"] stylesheet_link_tag :monkey # => <link href="/assets/head.css" media="screen" rel="stylesheet" /> @@ -580,7 +472,7 @@ stylesheet_link_tag :monkey # => Returns a link tag that browsers and news readers can use to auto-detect an RSS or Atom feed. ```ruby -auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "RSS Feed"}) # => +auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "RSS Feed"}) # => <link rel="alternate" type="application/rss+xml" title="RSS Feed" href="http://www.example.com/feed" /> ``` @@ -637,7 +529,7 @@ javascript_include_tag :all You can also cache multiple JavaScript files into one file, which requires less HTTP connections to download and can better be compressed by gzip (leading to faster transfers). Caching will only happen if `ActionController::Base.perform_caching` is set to true (which is the case by default for the Rails production environment, but not for the development environment). ```ruby -javascript_include_tag :all, :cache => true # => +javascript_include_tag :all, cache: true # => <script src="/javascripts/all.js"></script> ``` @@ -674,7 +566,7 @@ stylesheet_link_tag :all You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching is set to true (which is the case by default for the Rails production environment, but not for the development environment). ```ruby -stylesheet_link_tag :all, :cache => true +stylesheet_link_tag :all, cache: true # => <link href="/assets/all.css" media="screen" rel="stylesheet" /> ``` @@ -729,7 +621,7 @@ atom_feed do |feed| @posts.each do |post| feed.entry(post) do |entry| entry.title(post.title) - entry.content(post.body, :type => 'html') + entry.content(post.body, type: 'html') entry.author do |author| author.name(post.author_name) @@ -844,7 +736,7 @@ Reports the approximate distance in time between two Time or Date objects or int ```ruby distance_of_time_in_words(Time.now, Time.now + 15.seconds) # => less than a minute -distance_of_time_in_words(Time.now, Time.now + 15.seconds, :include_seconds => true) # => less than 20 seconds +distance_of_time_in_words(Time.now, Time.now + 15.seconds, include_seconds: true) # => less than 20 seconds ``` #### select_date @@ -937,7 +829,7 @@ Returns a select tag with options for each of the five years on each side of the select_year(Date.today) # Generates a select field from 1900 to 2009 that defaults to the current year -select_year(Date.today, :start_year => 1900, :end_year => 2009) +select_year(Date.today, start_year: 1900, end_year: 2009) ``` #### time_ago_in_words @@ -945,7 +837,7 @@ select_year(Date.today, :start_year => 1900, :end_year => 2009) Like `distance_of_time_in_words`, but where `to_time` is fixed to `Time.now`. ```ruby -time_ago_in_words(3.minutes.from_now) # => 3 minutes +time_ago_in_words(3.minutes.from_now) # => 3 minutes ``` #### time_select @@ -987,7 +879,7 @@ The core method of this helper, form_for, gives you the ability to create a form ```html+erb # Note: a @person variable will have been created in the controller (e.g. @person = Person.new) -<%= form_for @person, :url => { :action => "create" } do |f| %> +<%= form_for @person, url: {action: "create"} do |f| %> <%= f.text_field :first_name %> <%= f.text_field :last_name %> <%= submit_tag 'Create' %> @@ -997,7 +889,7 @@ The core method of this helper, form_for, gives you the ability to create a form The HTML generated for this would be: ```html -<form action="/persons/create" method="post"> +<form action="/people/create" method="post"> <input id="person_first_name" name="person[first_name]" type="text" /> <input id="person_last_name" name="person[last_name]" type="text" /> <input name="commit" type="submit" value="Create" /> @@ -1007,7 +899,7 @@ The HTML generated for this would be: The params object created when this form is submitted would look like: ```ruby -{"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}} +{"action" => "create", "controller" => "people", "person" => {"first_name" => "William", "last_name" => "Smith"}} ``` The params hash has a nested person value, which can therefore be accessed with params[:person] in the controller. @@ -1028,7 +920,7 @@ check_box("post", "validated") 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: ```html+erb -<%= form_for @person, :url => { :action => "update" } do |person_form| %> +<%= form_for @person, url: {action: "update"} do |person_form| %> First name: <%= person_form.text_field :first_name %> Last name : <%= person_form.text_field :last_name %> @@ -1104,7 +996,7 @@ radio_button("post", "category", "java") Returns a textarea opening and closing tag set tailored for accessing a specified attribute. ```ruby -text_area(:comment, :text, :size => "20x30") +text_area(:comment, :text, size: "20x30") # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]"> # #{@comment.text} # </textarea> @@ -1145,7 +1037,7 @@ end Sample usage (selecting the associated Author for an instance of Post, `@post`): ```ruby -collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {:prompt => true}) +collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {prompt: true}) ``` If `@post.author_id` is 1, this would return: @@ -1317,7 +1209,7 @@ Create a select tag and a series of contained option tags for the provided objec Example: ```ruby -select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true }) +select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: true}) ``` If `@post.person_id` is 1, this would become: @@ -1374,7 +1266,7 @@ 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 %> +<%= form_tag {action: "post"}, {multipart: true} do %> <label for="file">File to Upload</label> <%= file_field_tag "file" %> <%= submit_tag %> <% end %> @@ -1604,3 +1496,72 @@ end Then you could create special views like `app/views/posts/show.expert.html.erb` that would only be displayed to expert users. You can read more about the Rails Internationalization (I18n) API [here](i18n.html). + +Using Action View outside of Rails +---------------------------------- + +Action View is a Rails component, but it can also be used without Rails. We can demonstrate this by creating a small [Rack](http://rack.rubyforge.org/) application that includes Action View functionality. This may be useful, for example, if you'd like access to Action View's helpers in a Rack application. + +Let's start by ensuring that you have the Action Pack and Rack gems installed: + +```bash +$ gem install actionpack +$ gem install rack +``` + +Now we'll create a simple "Hello World" application that uses the `titleize` method provided by Active Support. + +**hello_world.rb:** + +```ruby +require 'active_support/core_ext/string/inflections' +require 'rack' + +def hello_world(env) + [200, {"Content-Type" => "text/html"}, "hello world".titleize] +end + +Rack::Handler::Mongrel.run method(:hello_world), Port: 4567 +``` + +We can see this all come together by starting up the application and then visiting `http://localhost:4567/` + +```bash +$ ruby hello_world.rb +``` + +TODO needs a screenshot? I have one - not sure where to put it. + +Notice how 'hello world' has been converted into 'Hello World' by the `titleize` helper method. + +Action View can also be used with [Sinatra](http://www.sinatrarb.com/) in the same way. + +Let's start by ensuring that you have the Action Pack and Sinatra gems installed: + +```bash +$ gem install actionpack +$ gem install sinatra +``` + +Now we'll create the same "Hello World" application in Sinatra. + +**hello_world.rb:** + +```ruby +require 'action_view' +require 'sinatra' + +get '/' do + erb 'hello world'.titleize +end +``` + +Then, we can run the application: + +```bash +$ ruby hello_world.rb +``` + +Once the application is running, you can see Sinatra and Action View working together by visiting `http://localhost:4567/` + +TODO needs a screenshot? I have one - not sure where to put it. diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md index bfb088ed03..92b51334a3 100644 --- a/guides/source/active_model_basics.md +++ b/guides/source/active_model_basics.md @@ -188,13 +188,13 @@ class Person attr_accessor :name, :email, :token - validates :name, :presence => true - validates_format_of :email, :with => /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i - validates! :token, :presence => true + validates :name, presence: true + validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i + validates! :token, presence: true end -person = Person.new(:token => "2b1f325") +person = Person.new(token: "2b1f325") person.valid? #=> false person.name = 'vishnu' person.email = 'me' diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 5bc100f326..810a0263c0 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -119,7 +119,7 @@ If you do so, you will have to define manually the class name that is hosting th ```ruby class FunnyJoke < ActiveSupport::TestCase - set_fixture_class :funny_jokes => 'Joke' + set_fixture_class funny_jokes: 'Joke' fixtures :funny_jokes ... end @@ -145,7 +145,7 @@ Active Record objects can be created from a hash, a block or have their attribut 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") + user = User.create(name: "David", occupation: "Code Artist") ``` Using the `new` method, an object can be created without being saved: @@ -188,7 +188,7 @@ Active Record provides a rich API for accessing data within a database. Below ar ```ruby # find all users named David who are Code Artists and sort by created_at in reverse chronological order - users = User.where(:name => 'David', :occupation => 'Code Artist').order('created_at DESC') + 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. diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index da56d55deb..79d00ded0a 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -35,7 +35,7 @@ end ```ruby class Order < ActiveRecord::Base - belongs_to :client, :counter_cache => true + belongs_to :client, counter_cache: true end ``` @@ -356,7 +356,7 @@ Two additional options, `:batch_size` and `:start`, are available as well. The `:batch_size` option allows you to specify the number of records to be retrieved in each batch, before being passed individually to the block. For example, to retrieve records in batches of 5000: ```ruby -User.find_each(:batch_size => 5000) do |user| +User.find_each(batch_size: 5000) do |user| NewsLetter.weekly_deliver(user) end ``` @@ -368,7 +368,7 @@ By default, records are fetched in ascending order of the primary key, which mus For example, to send newsletters only to users with the primary key starting from 2000, and to retrieve them in batches of 5000: ```ruby -User.find_each(:start => 2000, :batch_size => 5000) do |user| +User.find_each(start: 2000, batch_size: 5000) do |user| NewsLetter.weekly_deliver(user) end ``` @@ -381,7 +381,7 @@ The `find_in_batches` method is similar to `find_each`, since both retrieve batc ```ruby # Give add_invoices an array of 1000 invoices at a time -Invoice.find_in_batches(:include => :invoice_lines) do |invoices| +Invoice.find_in_batches(include: :invoice_lines) do |invoices| export.add_invoices(invoices) end ``` @@ -443,7 +443,7 @@ Similar to the `(?)` replacement style of params, you can also specify keys/valu ```ruby Client.where("created_at >= :start_date AND created_at <= :end_date", - {:start_date => params[:start_date], :end_date => params[:end_date]}) + {start_date: params[:start_date], end_date: params[:end_date]}) ``` This makes for clearer readability if you have a large number of variable conditions. @@ -457,7 +457,7 @@ NOTE: Only equality, range and subset checking are possible with Hash conditions #### Equality Conditions ```ruby -Client.where(:locked => true) +Client.where(locked: true) ``` The field name can also be a string: @@ -469,16 +469,16 @@ Client.where('locked' => true) In the case of a belongs_to relationship, an association key can be used to specify the model if an ActiveRecord object is used as the value. This method works with polymorphic relationships as well. ```ruby -Post.where(:author => author) -Author.joins(:posts).where(:posts => {:author => author}) +Post.where(author: author) +Author.joins(:posts).where(posts: {author: author}) ``` -NOTE: The values cannot be symbols. For example, you cannot do `Client.where(:status => :active)`. +NOTE: The values cannot be symbols. For example, you cannot do `Client.where(status: :active)`. #### Range Conditions ```ruby -Client.where(:created_at => (Time.now.midnight - 1.day)..Time.now.midnight) +Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight) ``` This will find all clients created yesterday by using a `BETWEEN` SQL statement: @@ -494,7 +494,7 @@ This demonstrates a shorter syntax for the examples in [Array Conditions](#array If you want to find records using the `IN` expression you can pass an array to the conditions hash: ```ruby -Client.where(:orders_count => [1,3,5]) +Client.where(orders_count: [1,3,5]) ``` This code will generate SQL like this: @@ -698,7 +698,7 @@ The `reorder` method overrides the default scope order. For example: class Post < ActiveRecord::Base .. .. - has_many :comments, :order => 'posted_at DESC' + has_many :comments, order: 'posted_at DESC' end Post.find(10).comments.reorder('name') @@ -755,12 +755,12 @@ Post.none # returns an empty Relation and fires no queries. ```ruby # The visible_posts method below is expected to return a Relation. -@posts = current_user.visible_posts.where(:name => params[:name]) +@posts = current_user.visible_posts.where(name: params[:name]) def visible_posts case role when 'Country Manager' - Post.where(:country => country) + Post.where(country: country) when 'Reviewer' Post.published when 'Bad User' @@ -954,7 +954,7 @@ Or, in English: "return all posts that have a category and at least one comment" #### Joining Nested Associations (Single Level) ```ruby -Post.joins(:comments => :guest) +Post.joins(comments: :guest) ``` This produces: @@ -970,7 +970,7 @@ Or, in English: "return all posts that have a comment made by a guest." #### Joining Nested Associations (Multiple Level) ```ruby -Category.joins(:posts => [{:comments => :guest}, :tags]) +Category.joins(posts: [{comments: :guest}, :tags]) ``` This produces: @@ -996,7 +996,7 @@ An alternative and cleaner syntax is to nest the hash conditions: ```ruby time_range = (Time.now.midnight - 1.day)..Time.now.midnight -Client.joins(:orders).where(:orders => {:created_at => time_range}) +Client.joins(:orders).where(orders: {created_at: time_range}) ``` This will find all clients who have orders that were created yesterday, again using a `BETWEEN` SQL expression. @@ -1057,7 +1057,7 @@ This loads all the posts and the associated category and comments for each post. #### Nested Associations Hash ```ruby -Category.includes(:posts => [{:comments => :guest}, :tags]).find(1) +Category.includes(posts: [{comments: :guest}, :tags]).find(1) ``` This will find the category with id 1 and eager load all of the associated posts, the associated posts' tags and comments, and every comment's guest association. @@ -1109,7 +1109,7 @@ Scopes are also chainable within scopes: ```ruby class Post < ActiveRecord::Base - scope :published, -> { where(:published => true) } + scope :published, -> { where(published: true) } scope :published_and_commented, -> { published.where("comments_count > 0") } end ``` @@ -1278,7 +1278,7 @@ second time we run this code, the block will be ignored. You can also use `find_or_create_by!` to raise an exception if the new record is invalid. Validations are not covered on this guide, but let's assume for a moment that you temporarily add ```ruby -validates :orders_count, :presence => true +validates :orders_count, presence: true ``` to your `Client` model. If you try to create a new `Client` without passing an `orders_count`, the record will be invalid and an exception will be raised: @@ -1346,7 +1346,7 @@ Client.connection.select_all("SELECT * FROM clients WHERE id = '1'") `pluck` can be used to query a single or multiple columns from the underlying table of a model. It accepts a list of column names as argument and returns an array of values of the specified columns with the corresponding data type. ```ruby -Client.where(:active => true).pluck(:id) +Client.where(active: true).pluck(:id) # SELECT id FROM clients WHERE active = 1 # => [1, 2, 3] @@ -1364,6 +1364,8 @@ Client.pluck(:id, :name) ```ruby Client.select(:id).map { |c| c.id } # or +Client.select(:id).map(&:id) +# or Client.select(:id).map { |c| [c.id, c.name] } ``` @@ -1413,7 +1415,7 @@ Client.exists?([1,2,3]) It's even possible to use `exists?` without any arguments on a model or a relation. ```ruby -Client.where(:first_name => 'Ryan').exists? +Client.where(first_name: 'Ryan').exists? ``` The above returns `true` if there is at least one client with the `first_name` 'Ryan' and `false` otherwise. @@ -1436,8 +1438,8 @@ Post.recent.any? Post.recent.many? # via a relation -Post.where(:published => true).any? -Post.where(:published => true).many? +Post.where(published: true).any? +Post.where(published: true).many? # via an association Post.first.categories.any? @@ -1459,14 +1461,14 @@ Client.count Or on a relation: ```ruby -Client.where(:first_name => 'Ryan').count +Client.where(first_name: 'Ryan').count # SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan') ``` You can also use various finder methods on a relation for performing complex calculations: ```ruby -Client.includes("orders").where(:first_name => 'Ryan', :orders => {:status => 'received'}).count +Client.includes("orders").where(first_name: 'Ryan', orders: {status: 'received'}).count ``` Which will execute: @@ -1531,7 +1533,7 @@ Running EXPLAIN You can run EXPLAIN on the queries triggered by relations. For example, ```ruby -User.where(:id => 1).joins(:posts).explain +User.where(id: 1).joins(:posts).explain ``` may yield @@ -1570,7 +1572,7 @@ may need the results of previous ones. Because of that, `explain` actually executes the query, and then asks for the query plans. For example, ```ruby -User.where(:id => 1).includes(:posts).explain +User.where(id: 1).includes(:posts).explain ``` yields diff --git a/guides/source/active_record_validations_callbacks.md b/guides/source/active_record_validations_callbacks.md index 5c27ccbf9e..0f4140b650 100644 --- a/guides/source/active_record_validations_callbacks.md +++ b/guides/source/active_record_validations_callbacks.md @@ -953,8 +953,12 @@ Below is a simple example where we change the Rails behavior to always display t ```ruby ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| - errors = Array(instance.error_message).join(',') - %(#{html_tag}<span class="validation-error"> #{errors}</span>).html_safe + if html_tag =~ /\<label/ + html_tag + else + errors = Array(instance.error_message).join(',') + %(#{html_tag}<span class="validation-error"> #{errors}</span>).html_safe + end end ``` diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 4b2cc947b5..401e6f0596 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -14,7 +14,7 @@ How to Load Core Extensions ### Stand-Alone Active Support -In order to have a near zero default footprint, Active Support does not load anything by default. It is broken in small pieces so that you may load just what you need, and also has some convenience entry points to load related extensions in one shot, even everything. +In order to have a near-zero default footprint, Active Support does not load anything by default. It is broken in small pieces so that you can load just what you need, and also has some convenience entry points to load related extensions in one shot, even everything. Thus, after a simple require like: @@ -85,11 +85,11 @@ The following values are considered to be blank in a Rails application: * empty arrays and hashes, and -* any other object that responds to `empty?` and it is empty. +* any other object that responds to `empty?` and is empty. INFO: The predicate for strings uses the Unicode-aware character class `[:space:]`, so for example U+2029 (paragraph separator) is considered to be whitespace. -WARNING: Note that numbers are not mentioned, in particular 0 and 0.0 are **not** blank. +WARNING: Note that numbers are not mentioned. In particular, 0 and 0.0 are **not** blank. For example, this method from `ActionDispatch::Session::AbstractStore` uses `blank?` for checking whether a session key is present: @@ -147,19 +147,21 @@ Some numbers which are not singletons are not duplicable either: Active Support provides `duplicable?` to programmatically query an object about this property: ```ruby +"foo".duplicable? # => true "".duplicable? # => true +0.0.duplicable? # => false false.duplicable? # => false ``` -By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, and class and module objects. +By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, class, and module objects. -WARNING. Any class can disallow duplication removing `dup` and `clone` or raising exceptions from them, only `rescue` can tell whether a given arbitrary object is duplicable. `duplicable?` depends on the hard-coded list above, but it is much faster than `rescue`. Use it only if you know the hard-coded list is enough in your use case. +WARNING: Any class can disallow duplication by removing `dup` and `clone` or raising exceptions from them. Thus only `rescue` can tell whether a given arbitrary object is duplicable. `duplicable?` depends on the hard-coded list above, but it is much faster than `rescue`. Use it only if you know the hard-coded list is enough in your use case. NOTE: Defined in `active_support/core_ext/object/duplicable.rb`. ### `deep_dup` -The `deep_dup` method returns deep copy of a given object. Normally, when you `dup` an object that contains other objects, ruby does not `dup` them. If you have an array with a string, for example, it will look like this: +The `deep_dup` method returns deep copy of a given object. Normally, when you `dup` an object that contains other objects, ruby does not `dup` them, so it creates a shallow copy of the object. If you have an array with a string, for example, it will look like this: ```ruby array = ['string'] @@ -167,18 +169,18 @@ duplicate = array.dup duplicate.push 'another-string' -# object was duplicated, so element was added only to duplicate +# the object was duplicated, so the element was added only to the duplicate array #=> ['string'] duplicate #=> ['string', 'another-string'] duplicate.first.gsub!('string', 'foo') -# first element was not duplicated, it will be changed for both arrays +# first element was not duplicated, it will be changed in both arrays array #=> ['foo'] duplicate #=> ['foo', 'another-string'] ``` -As you can see, after duplicating `Array` instance, we got another object, therefore we can modify it and the original object will stay unchanged. This is not true for array's elements, however. Since `dup` does not make deep copy, the string inside array is still the same object. +As you can see, after duplicating the `Array` instance, we got another object, therefore we can modify it and the original object will stay unchanged. This is not true for array's elements, however. Since `dup` does not make deep copy, the string inside the array is still the same object. If you need a deep copy of an object, you should use `deep_dup`. Here is an example: @@ -192,12 +194,12 @@ array #=> ['string'] duplicate #=> ['foo'] ``` -If object is not duplicable, `deep_dup` will just return this object: +If the object is not duplicable, `deep_dup` will just return it: ```ruby number = 1 -dup = number.deep_dup -number.object_id == dup.object_id # => true +duplicate = number.deep_dup +number.object_id == duplicate.object_id # => true ``` NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. @@ -1913,8 +1915,8 @@ Produce a string representation of a number as a telephone number: Produce a string representation of a number as currency: ```ruby -1234567890.50.to_s(:currency) # => $1,234,567,890.50 -1234567890.506.to_s(:currency) # => $1,234,567,890.51 +1234567890.50.to_s(:currency) # => $1,234,567,890.50 +1234567890.506.to_s(:currency) # => $1,234,567,890.51 1234567890.506.to_s(:currency, precision: 3) # => $1,234,567,890.506 ``` @@ -1934,8 +1936,8 @@ Produce a string representation of a number as a percentage: Produce a string representation of a number in delimited form: ```ruby -12345678.to_s(:delimited) # => 12,345,678 -12345678.05.to_s(:delimited) # => 12,345,678.05 +12345678.to_s(:delimited) # => 12,345,678 +12345678.05.to_s(:delimited) # => 12,345,678.05 12345678.to_s(:delimited, delimiter: ".") # => 12.345.678 12345678.to_s(:delimited, delimiter: ",") # => 12,345,678 12345678.05.to_s(:delimited, separator: " ") # => 12,345,678 05 @@ -1944,7 +1946,7 @@ Produce a string representation of a number in delimited form: Produce a string representation of a number rounded to a precision: ```ruby -111.2345.to_s(:rounded) # => 111.235 +111.2345.to_s(:rounded) # => 111.235 111.2345.to_s(:rounded, precision: 2) # => 111.23 13.to_s(:rounded, precision: 5) # => 13.00000 389.32314.to_s(:rounded, precision: 0) # => 389 diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 48b4ddb102..72e412e701 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -20,7 +20,8 @@ Write in present tense: "Returns a hash that...", rather than "Returned a hash t Start comments in upper case. Follow regular punctuation rules: ```ruby -# Declares an attribute reader backed by an internally-named instance variable. +# Declares an attribute reader backed by an internally-named +# instance variable. def attr_internal_reader(*attrs) ... end @@ -51,8 +52,8 @@ Use two spaces to indent chunks of code--that is, for markup purposes, two space Short docs do not need an explicit "Examples" label to introduce snippets; they just follow paragraphs: ```ruby -# Converts a collection of elements into a formatted string by calling -# `to_s` on all elements and joining them. +# Converts a collection of elements into a formatted string by +# calling +to_s+ on all elements and joining them. # # Blog.all.to_formatted_s # => "First PostSecond PostThird Post" ``` @@ -142,14 +143,16 @@ WARNING: Using a pair of `+...+` for fixed-width font only works with **words**; When "true" and "false" are English words rather than Ruby keywords use a regular font: ```ruby -# Runs all the validations within the specified context. Returns true if no errors are found, -# false otherwise. +# Runs all the validations within the specified context. +# Returns true if no errors are found, false otherwise. # -# If the argument is false (default is +nil+), the context is set to <tt>:create</tt> if -# <tt>new_record?</tt> is true, and to <tt>:update</tt> if it is not. +# If the argument is false (default is +nil+), the context is +# set to <tt>:create</tt> if <tt>new_record?</tt> is true, +# and to <tt>:update</tt> if it is not. # -# Validations with no <tt>:on</tt> option will run no matter the context. Validations with -# some <tt>:on</tt> option will only run in the specified context. +# Validations with no <tt>:on</tt> option will run no +# matter the context. Validations with # some <tt>:on</tt> +# option will only run in the specified context. def valid?(context = nil) ... end @@ -161,7 +164,7 @@ Description Lists In lists of options, parameters, etc. use a hyphen between the item and its description (reads better than a colon because normally options are symbols): ```ruby -# * <tt>:allow_nil</tt> - Skip validation if attribute is `nil`. +# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. ``` The description starts in upper case and ends with a full stop—it's standard English. diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 0540516a74..13df1965b6 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -214,7 +214,7 @@ The asset pipeline automatically evaluates ERB. This means that if you add an `e This writes the path to the particular asset being referenced. In this example, it would make sense to have an image in one of the asset load paths, such as `app/assets/images/image.png`, which would be referenced here. If this image is already available in `public/assets` as a fingerprinted file, then that path is referenced. -If you want to use a [data URI](http://en.wikipedia.org/wiki/Data_URI_scheme) -- a method of embedding the image data directly into the CSS file -- you can use the `asset_data_uri` helper. +If you want to use a [data URI](http://en.wikipedia.org/wiki/Data_URI_scheme) — a method of embedding the image data directly into the CSS file — you can use the `asset_data_uri` helper. ```css #logo { background: url(<%= asset_data_uri 'logo.png' %>) } @@ -256,7 +256,7 @@ $('#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. For example, a new Rails application includes a default `app/assets/javascripts/application.js` file which contains the following lines: @@ -309,7 +309,7 @@ The file extensions used on an asset determine what preprocessing is applied. Wh When these files are requested, they are processed by the processors provided by the `coffee-script` and `sass` gems and then sent back to the browser as JavaScript and CSS respectively. -Additional layers of preprocessing can be requested by adding other extensions, where each extension is processed in a right-to-left manner. These should be used in the order the processing should be applied. For example, a stylesheet called `app/assets/stylesheets/projects.css.scss.erb` is first processed as ERB, then SCSS, and finally served as CSS. The same applies to a JavaScript file -- `app/assets/javascripts/projects.js.coffee.erb` is processed as ERB, then CoffeeScript, and served as JavaScript. +Additional layers of preprocessing can be requested by adding other extensions, where each extension is processed in a right-to-left manner. These should be used in the order the processing should be applied. For example, a stylesheet called `app/assets/stylesheets/projects.css.scss.erb` is first processed as ERB, then SCSS, and finally served as CSS. The same applies to a JavaScript file — `app/assets/javascripts/projects.js.coffee.erb` is processed as ERB, then CoffeeScript, and served as JavaScript. Keep in mind that the order of these preprocessors is important. For example, if you called your JavaScript file `app/assets/javascripts/projects.js.erb.coffee` then it would be processed with the CoffeeScript interpreter first, which wouldn't understand ERB and therefore you would run into problems. @@ -350,7 +350,7 @@ When debug mode is off, Sprockets concatenates and runs the necessary preprocess <script src="/assets/application.js"></script> ``` -Assets are compiled and cached on the first request after the server is started. Sprockets sets a `must-revalidate` Cache-Control HTTP header to reduce request overhead on subsequent requests -- on these the browser gets a 304 (Not Modified) response. +Assets are compiled and cached on the first request after the server is started. Sprockets sets a `must-revalidate` Cache-Control HTTP header to reduce request overhead on subsequent requests — on these the browser gets a 304 (Not Modified) response. If any of the files in the manifest have changed between requests, the server responds with a new compiled file. @@ -575,6 +575,18 @@ group :production do end ``` +### CDNs + +If your assets are being served by a CDN, ensure they don't stick around in +your cache forever. This can cause problems. If you use +`config.action_controller.perform_caching = true`, Rack::Cache will use +`Rails.cache` to store assets. This can cause your cache to fill up quickly. + +Every cache is different, so evaluate how your CDN handles caching and make +sure that it plays nicely with the pipeline. You may find quirks related to +your specific set up, you may not. The defaults nginx uses, for example, +should give you no problems when used as an HTTP cache. + Customizing the Pipeline ------------------------ diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index cf3ae581b3..9bb5aa8bc2 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -25,25 +25,24 @@ end Now, suppose we wanted to add a new order for an existing customer. We'd need to do something like this: ```ruby -@order = Order.create(:order_date => Time.now, - :customer_id => @customer.id) +@order = Order.create(order_date: Time.now, customer_id: @customer.id) ``` Or consider deleting a customer, and ensuring that all of its orders get deleted as well: ```ruby -@orders = Order.where(:customer_id => @customer.id) +@orders = Order.where(customer_id: @customer.id) @orders.each do |order| order.destroy end @customer.destroy ``` -With Active Record associations, we can streamline these -- and other -- operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders: +With Active Record associations, we can streamline these — and other — operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders: ```ruby class Customer < ActiveRecord::Base - has_many :orders, :dependent => :destroy + has_many :orders, dependent: :destroy end class Order < ActiveRecord::Base @@ -54,10 +53,10 @@ end With this change, creating a new order for a particular customer is easier: ```ruby -@order = @customer.orders.create(:order_date => Time.now) +@order = @customer.orders.create(order_date: Time.now) ``` -Deleting a customer and all of its orders is _much_ easier: +Deleting a customer and all of its orders is *much* easier: ```ruby @customer.destroy @@ -126,7 +125,7 @@ A `has_many :through` association is often used to set up a many-to-many connect ```ruby class Physician < ActiveRecord::Base has_many :appointments - has_many :patients, :through => :appointments + has_many :patients, through: :appointments end class Appointment < ActiveRecord::Base @@ -136,7 +135,7 @@ end class Patient < ActiveRecord::Base has_many :appointments - has_many :physicians, :through => :appointments + has_many :physicians, through: :appointments end ``` @@ -157,7 +156,7 @@ The `has_many :through` association is also useful for setting up "shortcuts" th ```ruby class Document < ActiveRecord::Base has_many :sections - has_many :paragraphs, :through => :sections + has_many :paragraphs, through: :sections end class Section < ActiveRecord::Base @@ -170,7 +169,7 @@ class Paragraph < ActiveRecord::Base end ``` -With `:through => :sections` specified, Rails will now understand: +With `through: :sections` specified, Rails will now understand: ```ruby @document.paragraphs @@ -183,7 +182,7 @@ A `has_one :through` association sets up a one-to-one connection with another mo ```ruby class Supplier < ActiveRecord::Base has_one :account - has_one :account_history, :through => :account + has_one :account_history, through: :account end class Account < ActiveRecord::Base @@ -270,7 +269,7 @@ The second way to declare a many-to-many relationship is to use `has_many :throu ```ruby class Assembly < ActiveRecord::Base has_many :manifests - has_many :parts, :through => :manifests + has_many :parts, through: :manifests end class Manifest < ActiveRecord::Base @@ -280,7 +279,7 @@ end class Part < ActiveRecord::Base has_many :manifests - has_many :assemblies, :through => :manifests + has_many :assemblies, through: :manifests end ``` @@ -294,15 +293,15 @@ A slightly more advanced twist on associations is the _polymorphic association_. ```ruby class Picture < ActiveRecord::Base - belongs_to :imageable, :polymorphic => true + belongs_to :imageable, polymorphic: true end class Employee < ActiveRecord::Base - has_many :pictures, :as => :imageable + has_many :pictures, as: :imageable end class Product < ActiveRecord::Base - has_many :pictures, :as => :imageable + has_many :pictures, as: :imageable end ``` @@ -332,7 +331,7 @@ class CreatePictures < ActiveRecord::Migration def change create_table :pictures do |t| t.string :name - t.references :imageable, :polymorphic => true + t.references :imageable, polymorphic: true t.timestamps end end @@ -347,9 +346,10 @@ In designing a data model, you will sometimes find a model that should have a re ```ruby class Employee < ActiveRecord::Base - has_many :subordinates, :class_name => "Employee", - :foreign_key => "manager_id" - belongs_to :manager, :class_name => "Employee" + has_many :subordinates, class_name: "Employee", + foreign_key: "manager_id" + + belongs_to :manager, class_name: "Employee" end ``` @@ -442,7 +442,7 @@ These need to be backed up by a migration to create the `assemblies_parts` table ```ruby class CreateAssemblyPartJoinTable < ActiveRecord::Migration def change - create_table :assemblies_parts, :id => false do |t| + create_table :assemblies_parts, id: false do |t| t.integer :assembly_id t.integer :part_id end @@ -450,7 +450,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 @@ -495,14 +495,14 @@ module MyApplication module Business class Supplier < ActiveRecord::Base has_one :account, - :class_name => "MyApplication::Billing::Account" + class_name: "MyApplication::Billing::Account" end end module Billing class Account < ActiveRecord::Base belongs_to :supplier, - :class_name => "MyApplication::Business::Supplier" + class_name: "MyApplication::Business::Supplier" end end end @@ -536,11 +536,11 @@ This happens because c and o.customer are two different in-memory representation ```ruby class Customer < ActiveRecord::Base - has_many :orders, :inverse_of => :customer + has_many :orders, inverse_of: :customer end class Order < ActiveRecord::Base - belongs_to :customer, :inverse_of => :orders + belongs_to :customer, inverse_of: :orders end ``` @@ -621,8 +621,8 @@ The `association=` method assigns an associated object to this object. Behind th The `build_association` method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through this object's foreign key will be set, but the associated object will _not_ yet be saved. ```ruby -@customer = @order.build_customer(:customer_number => 123, - :customer_name => "John Doe") +@customer = @order.build_customer(customer_number: 123, + customer_name: "John Doe") ``` ##### `create_association(attributes = {})` @@ -630,8 +630,8 @@ The `build_association` method returns a new object of the associated type. This The `create_association` method returns a new object of the associated type. This object will be instantiated from the passed attributes, the link through this object's foreign key will be set, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved. ```ruby -@customer = @order.create_customer(:customer_number => 123, - :customer_name => "John Doe") +@customer = @order.create_customer(customer_number: 123, + customer_name: "John Doe") ``` @@ -641,8 +641,8 @@ While Rails uses intelligent defaults that will work well in most situations, th ```ruby class Order < ActiveRecord::Base - belongs_to :customer, :dependent => :destroy, - :counter_cache => true + belongs_to :customer, dependent: :destroy, + counter_cache: true end ``` @@ -668,7 +668,7 @@ If the name of the other model cannot be derived from the association name, you ```ruby class Order < ActiveRecord::Base - belongs_to :customer, :class_name => "Patron" + belongs_to :customer, class_name: "Patron" end ``` @@ -689,7 +689,7 @@ With these declarations, asking for the value of `@customer.orders.size` require ```ruby class Order < ActiveRecord::Base - belongs_to :customer, :counter_cache => true + belongs_to :customer, counter_cache: true end class Customer < ActiveRecord::Base has_many :orders @@ -702,7 +702,7 @@ Although the `:counter_cache` option is specified on the model that includes the ```ruby class Order < ActiveRecord::Base - belongs_to :customer, :counter_cache => :count_of_orders + belongs_to :customer, counter_cache: :count_of_orders end class Customer < ActiveRecord::Base has_many :orders @@ -723,8 +723,8 @@ By convention, Rails assumes that the column used to hold the foreign key on thi ```ruby class Order < ActiveRecord::Base - belongs_to :customer, :class_name => "Patron", - :foreign_key => "patron_id" + belongs_to :customer, class_name: "Patron", + foreign_key: "patron_id" end ``` @@ -736,11 +736,11 @@ The `:inverse_of` option specifies the name of the `has_many` or `has_one` assoc ```ruby class Customer < ActiveRecord::Base - has_many :orders, :inverse_of => :customer + has_many :orders, inverse_of: :customer end class Order < ActiveRecord::Base - belongs_to :customer, :inverse_of => :orders + belongs_to :customer, inverse_of: :orders end ``` @@ -754,7 +754,7 @@ If you set the `:touch` option to `:true`, then the `updated_at` or `updated_on` ```ruby class Order < ActiveRecord::Base - belongs_to :customer, :touch => true + belongs_to :customer, touch: true end class Customer < ActiveRecord::Base @@ -766,7 +766,7 @@ In this case, saving or destroying an order will update the timestamp on the ass ```ruby class Order < ActiveRecord::Base - belongs_to :customer, :touch => :orders_updated_at + belongs_to :customer, touch: :orders_updated_at end ``` @@ -780,8 +780,8 @@ There may be times when you wish to customize the query used by `belongs_to`. Su ```ruby class Order < ActiveRecord::Base - belongs_to :customer, -> { where :active => true }, - :dependent => :destroy + belongs_to :customer, -> { where active: true }, + dependent: :destroy end ``` @@ -798,7 +798,7 @@ The `where` method lets you specify the conditions that the associated object mu ```ruby class Order < ActiveRecord::Base - belongs_to :customer, -> { where :active => true } + belongs_to :customer, -> { where active: true } end ``` @@ -919,7 +919,7 @@ The `association=` method assigns an associated object to this object. Behind th The `build_association` method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through its foreign key will be set, but the associated object will _not_ yet be saved. ```ruby -@account = @supplier.build_account(:terms => "Net 30") +@account = @supplier.build_account(terms: "Net 30") ``` ##### `create_association(attributes = {})` @@ -927,7 +927,7 @@ The `build_association` method returns a new object of the associated type. This The `create_association` method returns a new object of the associated type. This object will be instantiated from the passed attributes, the link through its foreign key will be set, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved. ```ruby -@account = @supplier.create_account(:terms => "Net 30") +@account = @supplier.create_account(terms: "Net 30") ``` #### Options for `has_one` @@ -936,7 +936,7 @@ While Rails uses intelligent defaults that will work well in most situations, th ```ruby class Supplier < ActiveRecord::Base - has_one :account, :class_name => "Billing", :dependent => :nullify + has_one :account, class_name: "Billing", dependent: :nullify end ``` @@ -968,7 +968,7 @@ If the name of the other model cannot be derived from the association name, you ```ruby class Supplier < ActiveRecord::Base - has_one :account, :class_name => "Billing" + has_one :account, class_name: "Billing" end ``` @@ -988,7 +988,7 @@ By convention, Rails assumes that the column used to hold the foreign key on the ```ruby class Supplier < ActiveRecord::Base - has_one :account, :foreign_key => "supp_id" + has_one :account, foreign_key: "supp_id" end ``` @@ -1000,11 +1000,11 @@ The `:inverse_of` option specifies the name of the `belongs_to` association that ```ruby class Supplier < ActiveRecord::Base - has_one :account, :inverse_of => :supplier + has_one :account, inverse_of: :supplier end class Account < ActiveRecord::Base - belongs_to :supplier, :inverse_of => :account + belongs_to :supplier, inverse_of: :account end ``` @@ -1034,7 +1034,7 @@ There may be times when you wish to customize the query used by `has_one`. Such ```ruby class Supplier < ActiveRecord::Base - has_one :account, -> { where :active => true } + has_one :account, -> { where active: true } end ``` @@ -1195,7 +1195,7 @@ The `collection.delete` method removes one or more objects from the collection b @customer.orders.delete(@order1) ``` -WARNING: Additionally, objects will be destroyed if they're associated with `:dependent => :destroy`, and deleted if they're associated with `:dependent => :delete_all`. +WARNING: Additionally, objects will be destroyed if they're associated with `dependent: :destroy`, and deleted if they're associated with `dependent: :delete_all`. ##### `collection.destroy(object, ...)` @@ -1225,7 +1225,7 @@ The `collection_singular_ids=` method makes the collection contain only the obje ##### `collection.clear` -The `collection.clear` method removes every object from the collection. This destroys the associated objects if they are associated with `:dependent => :destroy`, deletes them directly from the database if `:dependent => :delete_all`, and otherwise sets their foreign keys to `NULL`. +The `collection.clear` method removes every object from the collection. This destroys the associated objects if they are associated with `dependent: :destroy`, deletes them directly from the database if `dependent: :delete_all`, and otherwise sets their foreign keys to `NULL`. ##### `collection.empty?` @@ -1258,7 +1258,7 @@ The `collection.find` method finds objects within the collection. It uses the sa The `collection.where` method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed. ```ruby -@open_orders = @customer.orders.where(:open => true) # No query yet +@open_orders = @customer.orders.where(open: true) # No query yet @open_order = @open_orders.first # Now the database will be queried ``` @@ -1271,8 +1271,8 @@ The `collection.exists?` method checks whether an object meeting the supplied co The `collection.build` method returns one or more new objects of the associated type. These objects will be instantiated from the passed attributes, and the link through their foreign key will be created, but the associated objects will _not_ yet be saved. ```ruby -@order = @customer.orders.build(:order_date => Time.now, - :order_number => "A12345") +@order = @customer.orders.build(order_date: Time.now, + order_number: "A12345") ``` ##### `collection.create(attributes = {})` @@ -1280,8 +1280,8 @@ The `collection.build` method returns one or more new objects of the associated The `collection.create` method returns a new object of the associated type. This object will be instantiated from the passed attributes, the link through its foreign key will be created, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved. ```ruby -@order = @customer.orders.create(:order_date => Time.now, - :order_number => "A12345") +@order = @customer.orders.create(order_date: Time.now, + order_number: "A12345") ``` #### Options for `has_many` @@ -1290,7 +1290,7 @@ While Rails uses intelligent defaults that will work well in most situations, th ```ruby class Customer < ActiveRecord::Base - has_many :orders, :dependent => :delete_all, :validate => :false + has_many :orders, dependent: :delete_all, validate: :false end ``` @@ -1322,7 +1322,7 @@ If the name of the other model cannot be derived from the association name, you ```ruby class Customer < ActiveRecord::Base - has_many :orders, :class_name => "Transaction" + has_many :orders, class_name: "Transaction" end ``` @@ -1344,7 +1344,7 @@ By convention, Rails assumes that the column used to hold the foreign key on the ```ruby class Customer < ActiveRecord::Base - has_many :orders, :foreign_key => "cust_id" + has_many :orders, foreign_key: "cust_id" end ``` @@ -1356,11 +1356,11 @@ The `:inverse_of` option specifies the name of the `belongs_to` association that ```ruby class Customer < ActiveRecord::Base - has_many :orders, :inverse_of => :customer + has_many :orders, inverse_of: :customer end class Order < ActiveRecord::Base - belongs_to :customer, :inverse_of => :orders + belongs_to :customer, inverse_of: :orders end ``` @@ -1390,7 +1390,7 @@ There may be times when you wish to customize the query used by `has_many`. Such ```ruby class Customer < ActiveRecord::Base - has_many :orders, -> { where :processed => true } + has_many :orders, -> { where processed: true } end ``` @@ -1414,7 +1414,7 @@ The `where` method lets you specify the conditions that the associated object mu ```ruby class Customer < ActiveRecord::Base has_many :confirmed_orders, -> { where "confirmed = 1" }, - :class_name => "Order" + class_name: "Order" end ``` @@ -1422,8 +1422,8 @@ You can also set conditions via a hash: ```ruby class Customer < ActiveRecord::Base - has_many :confirmed_orders, -> { where :confirmed => true }, - :class_name => "Order" + has_many :confirmed_orders, -> { where confirmed: true }, + class_name: "Order" end ``` @@ -1440,7 +1440,7 @@ The `group` method supplies an attribute name to group the result set by, using ```ruby class Customer < ActiveRecord::Base has_many :line_items, -> { group 'orders.id' }, - :through => :orders + through: :orders end ``` @@ -1488,7 +1488,7 @@ The `limit` method lets you restrict the total number of objects that will be fe class Customer < ActiveRecord::Base has_many :recent_orders, -> { order('order_date desc').limit(100) }, - :class_name => "Order", + class_name: "Order", end ``` @@ -1523,11 +1523,11 @@ Use the `uniq` method to keep the collection free of duplicates. This is mostly ```ruby class Person < ActiveRecord::Base has_many :readings - has_many :posts, :through => :readings + has_many :posts, through: :readings end -person = Person.create(:name => 'john') -post = Post.create(:name => 'a1') +person = Person.create(name: 'John') +post = Post.create(name: 'a1') person.posts << post person.posts << post person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">] @@ -1541,11 +1541,11 @@ Now let's set `uniq`: ```ruby class Person has_many :readings - has_many :posts, -> { uniq }, :through => :readings + has_many :posts, -> { uniq }, through: :readings end -person = Person.create(:name => 'honda') -post = Post.create(:name => 'a1') +person = Person.create(name: 'Honda') +post = Post.create(name: 'a1') person.posts << post person.posts << post person.posts.inspect # => [#<Post id: 7, name: "a1">] @@ -1722,8 +1722,7 @@ The `collection.exists?` method checks whether an object meeting the supplied co The `collection.build` method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through the join table will be created, but the associated object will _not_ yet be saved. ```ruby -@assembly = @part.assemblies.build( - {:assembly_name => "Transmission housing"}) +@assembly = @part.assemblies.build({assembly_name: "Transmission housing"}) ``` ##### `collection.create(attributes = {})` @@ -1731,8 +1730,7 @@ The `collection.build` method returns a new object of the associated type. This The `collection.create` method returns a new object of the associated type. This object will be instantiated from the passed attributes, the link through the join table will be created, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved. ```ruby -@assembly = @part.assemblies.create( - {:assembly_name => "Transmission housing"}) +@assembly = @part.assemblies.create({assembly_name: "Transmission housing"}) ``` #### Options for `has_and_belongs_to_many` @@ -1741,8 +1739,8 @@ While Rails uses intelligent defaults that will work well in most situations, th ```ruby class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, :uniq => true, - :read_only => true + has_and_belongs_to_many :assemblies, uniq: true, + read_only: true end ``` @@ -1763,9 +1761,10 @@ TIP: The `:foreign_key` and `:association_foreign_key` options are useful when s ```ruby class User < ActiveRecord::Base - has_and_belongs_to_many :friends, :class_name => "User", - :foreign_key => "this_user_id", - :association_foreign_key => "other_user_id" + has_and_belongs_to_many :friends, + class_name: "User", + foreign_key: "this_user_id", + association_foreign_key: "other_user_id" end ``` @@ -1779,7 +1778,7 @@ If the name of the other model cannot be derived from the association name, you ```ruby class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, :class_name => "Gadget" + has_and_belongs_to_many :assemblies, class_name: "Gadget" end ``` @@ -1789,9 +1788,10 @@ By convention, Rails assumes that the column in the join table used to hold the ```ruby class User < ActiveRecord::Base - has_and_belongs_to_many :friends, :class_name => "User", - :foreign_key => "this_user_id", - :association_foreign_key => "other_user_id" + has_and_belongs_to_many :friends, + class_name: "User", + foreign_key: "this_user_id", + association_foreign_key: "other_user_id" end ``` @@ -1809,7 +1809,7 @@ There may be times when you wish to customize the query used by `has_and_belongs ```ruby class Parts < ActiveRecord::Base - has_and_belongs_to_many :assemblies, -> { where :active => true } + has_and_belongs_to_many :assemblies, -> { where active: true } end ``` @@ -1842,7 +1842,7 @@ You can also set conditions via a hash: ```ruby class Parts < ActiveRecord::Base has_and_belongs_to_many :assemblies, - -> { where :factory => 'Seattle' } + -> { where factory: 'Seattle' } end ``` @@ -1929,7 +1929,7 @@ You define association callbacks by adding options to the association declaratio ```ruby class Customer < ActiveRecord::Base - has_many :orders, :before_add => :check_credit_limit + has_many :orders, before_add: :check_credit_limit def check_credit_limit(order) ... @@ -1944,7 +1944,7 @@ You can stack callbacks on a single event by passing them as an array: ```ruby class Customer < ActiveRecord::Base has_many :orders, - :before_add => [:check_credit_limit, :calculate_shipping_charges] + before_add: [:check_credit_limit, :calculate_shipping_charges] def check_credit_limit(order) ... diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 08f1ef879d..4cb76bfe5f 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -5,10 +5,10 @@ This guide will teach you what you need to know about avoiding that expensive ro After reading this guide, you should be able to use and configure: -* Page, action, and fragment caching. -* Sweepers. -* Alternative cache stores. -* Conditional GET support. +* Page, action, and fragment caching +* Sweepers +* Alternative cache stores +* Conditional GET support -------------------------------------------------------------------------------- @@ -61,7 +61,7 @@ class ProductsController < ActionController end def create - expire_page :action => :index + expire_page action: :index end end @@ -82,13 +82,13 @@ location / { You can disable gzipping by setting `:gzip` option to false (for example, if action returns image): ```ruby -caches_page :image, :gzip => false +caches_page :image, gzip: false ``` Or, you can set custom gzip compression level (level names are taken from `Zlib` constants): ```ruby -caches_page :image, :gzip => :best_speed +caches_page :image, gzip: :best_speed ``` NOTE: Page caching ignores all parameters. For example `/products?page=1` will be written out to the filesystem as `products.html` with no reference to the `page` parameter. Thus, if someone requests `/products?page=2` later, they will get the cached first page. A workaround for this limitation is to include the parameters in the page's path, e.g. `/products/page/1`. @@ -114,13 +114,13 @@ class ProductsController < ActionController end def create - expire_action :action => :index + expire_action action: :index end end ``` -You can also use `:if` (or `:unless`) to pass a Proc that specifies when the action should be cached. Also, you can use `:layout => false` to cache without layout so that dynamic information in the layout such as logged in user info or the number of items in the cart can be left uncached. This feature is available as of Rails 2.2. +You can also use `:if` (or `:unless`) to pass a Proc that specifies when the action should be cached. Also, you can use `layout: false` to cache without layout so that dynamic information in the layout such as logged in user info or the number of items in the cart can be left uncached. This feature is available as of Rails 2.2. You can modify the default action cache path by passing a `:cache_path` option. This will be passed directly to `ActionCachePath.path_for`. This is handy for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance. @@ -152,14 +152,14 @@ As an example, if you wanted to show all the orders placed on your website in re The cache block in our example will bind to the action that called it and is written out to the same place as the Action Cache, which means that if you want to cache multiple fragments per action, you should provide an `action_suffix` to the cache call: ```html+erb -<% cache(:action => 'recent', :action_suffix => 'all_products') do %> +<% cache(action: 'recent', action_suffix: 'all_products') do %> All available products: ``` and you can expire it using the `expire_fragment` method, like so: ```ruby -expire_fragment(:controller => 'products', :action => 'recent', :action_suffix => 'all_products') +expire_fragment(controller: 'products', action: 'recent', action_suffix: 'all_products') ``` If you don't want the cache block to bind to the action that called it, you can also use globally keyed fragments by calling the `cache` method with a key: @@ -206,7 +206,7 @@ class ProductSweeper < ActionController::Caching::Sweeper private def expire_cache_for(product) # Expire the index page now that we added a new product - expire_page(:controller => 'products', :action => 'index') + expire_page(controller: 'products', action: 'index') # Expire a fragment expire_fragment('all_available_products') @@ -217,7 +217,7 @@ end You may notice that the actual product gets passed to the sweeper, so if we were caching the edit action for each product, we could add an expire method which specifies the page we want to expire: ```ruby -expire_action(:controller => 'products', :action => 'edit', :id => product.id) +expire_action(controller: 'products', action: 'edit', id: product.id) ``` Then we add it to our controller to tell it to call the sweeper when certain actions are called. So, if we wanted to expire the cached content for the list and edit actions when the create action was called, we could do the following: @@ -265,7 +265,7 @@ class ProductSweeper < ActionController::Caching::Sweeper observe Product def after_create(product) - expire_action(:controller => '/products', :action => 'index') + expire_action(controller: '/products', action: 'index') end end ``` @@ -340,13 +340,11 @@ There are some common options used by all cache implementations. These can be pa This cache store keeps entries in memory in the same Ruby process. The cache store has a bounded size specified by the `:size` options to the initializer (default is 32Mb). When the cache exceeds the allotted size, a cleanup will occur and the least recently used entries will be removed. ```ruby -config.cache_store = :memory_store, { :size => 64.megabytes } +config.cache_store = :memory_store, { size: 64.megabytes } ``` If you're running multiple Ruby on Rails server processes (which is the case if you're using mongrel_cluster or Phusion Passenger), then your Rails server process instances won't be able to share cache data with each other. This cache store is not appropriate for large application deployments, but can work well for small, low traffic sites with only a couple of server processes or for development and test environments. -This is the default cache store implementation. - ### ActiveSupport::Cache::FileStore This cache store uses the file system to store entries. The path to the directory where the store files will be stored must be specified when initializing the cache. @@ -359,6 +357,8 @@ With this cache store, multiple server processes on the same host can share a ca Note that the cache will grow until the disk is full unless you periodically clear out old entries. +This is the default cache store implementation. + ### ActiveSupport::Cache::MemCacheStore This cache store uses Danga's `memcached` server to provide a centralized cache for your application. Rails uses the bundled `dalli` gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very a high performance and redundancy. @@ -394,8 +394,8 @@ In addition to the standard `:expires_in` option, the `write` method on this cac These options are passed to the `write` method as Hash options using either camelCase or underscore notation, as in the following examples: ```ruby -Rails.cache.write('key', 'value', :time_to_idle => 60.seconds, :timeToLive => 600.seconds) -caches_action :index, :expires_in => 60.seconds, :unless_exist => true +Rails.cache.write('key', 'value', time_to_idle: 60.seconds, timeToLive: 600.seconds) +caches_action :index, expires_in: 60.seconds, unless_exist: true ``` For more information about Ehcache, see [http://ehcache.org/](http://ehcache.org/) . @@ -427,7 +427,7 @@ You can use Hashes and Arrays of values as cache keys. ```ruby # This is a legal cache key -Rails.cache.read(:site => "mysite", :owners => [owner_1, owner_2]) +Rails.cache.read(site: "mysite", owners: [owner_1, owner_2]) ``` The keys you use on `Rails.cache` will not be the same as those actually used with the storage engine. They may be modified with a namespace or altered to fit technology backend constraints. This means, for instance, that you can't save values with `Rails.cache` and then try to pull them out with the `memcache-client` gem. However, you also don't need to worry about exceeding the memcached size limit or violating syntax rules. @@ -449,7 +449,7 @@ class ProductsController < ApplicationController # If the request is stale according to the given timestamp and etag value # (i.e. it needs to be processed again) then execute this block - if stale?(:last_modified => @product.updated_at.utc, :etag => @product.cache_key) + if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key) respond_to do |wants| # ... normal response processing end @@ -484,7 +484,7 @@ class ProductsController < ApplicationController def show @product = Product.find(params[:id]) - fresh_when :last_modified => @product.published_at.utc, :etag => @product + fresh_when last_modified: @product.published_at.utc, etag: @product end end ``` diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 0338ef5ad0..9521212581 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -486,7 +486,7 @@ Custom rake tasks have a `.rake` extension and are placed in `Rails.root/lib/tas ```ruby desc "I am short, but comprehensive description for my cool task" -task :task_name => [:prerequisite_task, :another_task_we_depend_on] do +task task_name: [:prerequisite_task, :another_task_we_depend_on] do # All your magic here # Any valid Ruby code is allowed end diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 0b4f183d61..ac763d6e0e 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -101,7 +101,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all modes except production, where it defaults to `:info`. -* `config.log_tags` accepts a list of methods that respond to `request` object. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications. +* `config.log_tags` accepts a list of methods that respond to `request` object. This makes it easy to tag log lines with debug information like subdomain and request id — both very helpful in debugging multi-user production applications. * `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::BufferedLogger`, with auto flushing off in production mode. @@ -113,7 +113,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If `config.cache_classes` is true, this option is ignored. -* `config.secret_token` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_token` initialized to a random key in `config/initializers/secret_token.rb`. +* `config.secret_key_base` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_key_base` initialized to a random key in `config/initializers/secret_token.rb`. * `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. Nginx or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won´t be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app. @@ -648,7 +648,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `initialize_cache` If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack. -* `set_clear_dependencies_hook` Provides a hook for `active_record.set_dispatch_hooks` to use, which will run before this initializer. This initializer -- which runs only if `cache_classes` is set to `false` -- uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. +* `set_clear_dependencies_hook` Provides a hook for `active_record.set_dispatch_hooks` to use, which will run before this initializer. This initializer — which runs only if `cache_classes` is set to `false` — uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. * `initialize_dependency_mechanism` If `config.cache_classes` is true, configures `ActiveSupport::Dependencies.mechanism` to `require` dependencies rather than `load` them. @@ -659,7 +659,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `active_support.initialize_whiny_nils` Requires `active_support/whiny_nil` if `config.whiny_nils` is true. This file will output errors such as: ``` - Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id + Called id for nil, which would mistakenly be 4 — if you really wanted the id of nil, use object_id ``` And: @@ -684,9 +684,9 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `action_view.set_configs` Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through. -* `action_controller.logger` Sets `ActionController::Base.logger` -- if it's not already set -- to `Rails.logger`. +* `action_controller.logger` Sets `ActionController::Base.logger` — if it's not already set — to `Rails.logger`. -* `action_controller.initialize_framework_caches` Sets `ActionController::Base.cache_store` -- if it's not already set -- to `Rails.cache`. +* `action_controller.initialize_framework_caches` Sets `ActionController::Base.cache_store` — if it's not already set — to `Rails.cache`. * `action_controller.set_configs` Sets up Action Controller by using the settings in `config.action_controller` by `send`'ing the method names as setters to `ActionController::Base` and passing the values through. @@ -694,7 +694,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `active_record.initialize_timezone` Sets `ActiveRecord::Base.time_zone_aware_attributes` to true, as well as setting `ActiveRecord::Base.default_timezone` to UTC. When attributes are read from the database, they will be converted into the time zone specified by `Time.zone`. -* `active_record.logger` Sets `ActiveRecord::Base.logger` -- if it's not already set -- to `Rails.logger`. +* `active_record.logger` Sets `ActiveRecord::Base.logger` — if it's not already set — to `Rails.logger`. * `active_record.set_configs` Sets up Active Record by using the settings in `config.active_record` by `send`'ing the method names as setters to `ActiveRecord::Base` and passing the values through. @@ -704,7 +704,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `active_record.set_dispatch_hooks` Resets all reloadable connections to the database if `config.cache_classes` is set to `false`. -* `action_mailer.logger` Sets `ActionMailer::Base.logger` -- if it's not already set -- to `Rails.logger`. +* `action_mailer.logger` Sets `ActionMailer::Base.logger` — if it's not already set — to `Rails.logger`. * `action_mailer.set_configs` Sets up Action Mailer by using the settings in `config.action_mailer` by `send`'ing the method names as setters to `ActionMailer::Base` and passing the values through. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 3791467584..feb32eb06f 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -9,7 +9,7 @@ This guide covers ways in which _you_ can become a part of the ongoing developme * Contributing to the Ruby on Rails documentation * Contributing 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. +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. -------------------------------------------------------------------------------- @@ -24,7 +24,7 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it was already reported. If you find no issue addressing it you can [add a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues). -At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need at least to post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself -- and others -- to replicate the bug and figure out a fix. +At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need at least to post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself — and others — to replicate the bug and figure out a fix. Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment. @@ -146,7 +146,7 @@ After applying their branch, test it out! Here are some things to think about: Once you're happy that the pull request contains a good change, comment on the GitHub issue indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like: <blockquote> -I like the way you've restructured that code in generate_finder_sql -- much nicer. The tests look good too. +I like the way you've restructured that code in generate_finder_sql — much nicer. The tests look good too. </blockquote> If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the pull request. @@ -215,7 +215,7 @@ Rails follows a simple set of coding style conventions. * Use a = b and not a=b. * Follow the conventions in the source you see used already. -The above are guidelines -- please use your best judgment in using them. +The above are guidelines — please use your best judgment in using them. ### Updating the CHANGELOG @@ -376,6 +376,44 @@ Now you need to get other people to look at your patch, just as you've looked at It’s entirely possible that the feedback you get will suggest changes. Don’t get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it’s worth making the tweaks and resubmitting. If the feedback is that your code doesn’t belong in the core, you might still think about releasing it as a gem. +#### Squashing commits + +One of the things that we may ask you to do is "squash your commits," which +will combine all of your commits into a single commit. We prefer pull requests +that are a single commit. This makes it easier to backport changes to stable +branches, squashing makes it easier to revert bad commits, and the git history +can be a bit easier to follow. Rails is a large project, and a bunch of +extraneous commits can add a lot of noise. + +In order to do this, you'll need to have a git remote that points at the main +Rails repository. This is useful anyway, but just in case you don't have it set +up, make sure that you do this first: + +```bash +$ git remote add upstream https://github.com/rails/rails.git +``` + +You can call this remote whatever you'd like, but if you don't use `upstream`, +then change the name to your own in the instructions below. + +Given that your remote branch is called `my_pull_request`, then you can do the +following: + +```bash +$ git fetch upstream +$ git checkout my_pull_request +$ git rebase upstream/master +$ git rebase -i + +< Choose 'squash' for all of your commits except the first one. > +< Edit the commit message to make sense, and describe all your changes. > + +$ git push origin my_pull_request -f +``` + +You should be able to refresh the pull request on GitHub and see that it has +been updated. + ### Backporting Changes that are merged into master are intended for the next major release of Rails. Sometimes, it might be beneficial for your changes to propagate back to the maintenance releases for older stable branches. Generally, security fixes and bug fixes are good candidates for a backport, while new features and patches that introduce a change in behavior will not be accepted. When in doubt, it is best to consult a Rails team member before backporting your changes to avoid wasted effort. diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index a72f54a1b8..d4415d9b76 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -164,7 +164,7 @@ class PostsController < ApplicationController logger.debug "The post was saved and now the user is going to be redirected..." redirect_to(@post) else - render :action => "new" + render action: "new" end end diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index c11832da61..7dfb39fb81 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -19,6 +19,8 @@ In case you can't use the Rails development box, see section above, these are th Ruby on Rails uses Git for source code control. The [Git homepage](http://git-scm.com/) has installation instructions. There are a variety of resources on the net that will help you get familiar with Git: +* [Try Git course](http://try.github.com/) is an interactive course that will teach you the basics. +* The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git * [Everyday Git](http://schacon.github.com/git/everyday.html) will teach you just enough about Git to get by. * The [PeepCode screencast](https://peepcode.com/products/git) on Git ($9) is easier to follow. * [GitHub](http://help.github.com) offers links to a variety of Git resources. @@ -51,7 +53,7 @@ $ sudo yum install libxml2 libxml2-devel libxslt libxslt-devel If you have any problems with these libraries, you should install them manually compiling the source code. Just follow the instructions at the [Red Hat/CentOS section of the Nokogiri tutorials](http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos) . -Also, SQLite3 and its development files for the `sqlite3-ruby` gem -- in Ubuntu you're done with just +Also, SQLite3 and its development files for the `sqlite3-ruby` gem — in Ubuntu you're done with just ```bash $ sudo apt-get install sqlite3 libsqlite3-dev diff --git a/guides/source/engines.md b/guides/source/engines.md index 97af423f3e..f9bbff1c4c 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -89,7 +89,7 @@ The `--mountable` option tells the generator that you want to create a "mountabl Additionally, the `--mountable` option tells the generator to mount the engine inside the dummy testing application located at `test/dummy` by adding the following to the dummy application's routes file at `test/dummy/config/routes.rb`: ```ruby -mount Blorgh::Engine, :at => "blorgh" +mount Blorgh::Engine, at: "blorgh" ``` ### Inside an engine @@ -99,7 +99,7 @@ mount Blorgh::Engine, :at => "blorgh" At the root of this brand new engine's directory lives a `blorgh.gemspec` file. When you include the engine into an application later on, you will do so with this line in the Rails application's `Gemfile`: ```ruby -gem 'blorgh', :path => "vendor/engines/blorgh" +gem 'blorgh', path: "vendor/engines/blorgh" ``` By specifying it as a gem within the `Gemfile`, Bundler will load it as such, parsing this `blorgh.gemspec` file and requiring a file within the `lib` directory called `lib/blorgh.rb`. This file requires the `blorgh/engine.rb` file (located at `lib/blorgh/engine.rb`) and defines a base module called `Blorgh`. @@ -278,7 +278,7 @@ If you'd rather play around in the console, `rails console` will also work just One final thing is that the `posts` resource for this engine should be the root of the engine. Whenever someone goes to the root path where the engine is mounted, they should be shown a list of posts. This can be made to happen if this line is inserted into the `config/routes.rb` file inside the engine: ```ruby -root :to => "posts#index" +root to: "posts#index" ``` Now people will only need to go to the root of the engine to see all the posts, rather than visiting `/posts`. This means that instead of `http://localhost:3000/blorgh/posts`, you only need to go to `http://localhost:3000/blorgh` now. @@ -438,7 +438,7 @@ gem 'devise' However, because you are developing the `blorgh` engine on your local machine, you will need to specify the `:path` option in your `Gemfile`: ```ruby -gem 'blorgh', :path => "/path/to/blorgh" +gem 'blorgh', path: "/path/to/blorgh" ``` As described earlier, by placing the gem in the `Gemfile` it will be loaded when Rails is loaded, as it will first require `lib/blorgh.rb` in the engine and then `lib/blorgh/engine.rb`, which is the file that defines the major pieces of functionality for the engine. @@ -446,7 +446,7 @@ As described earlier, by placing the gem in the `Gemfile` it will be loaded when To make the engine's functionality accessible from within an application, it needs to be mounted in that application's `config/routes.rb` file: ```ruby -mount Blorgh::Engine, :at => "/blog" +mount Blorgh::Engine, at: "/blog" ``` This line will mount the engine at `/blog` in the application. Making it accessible at `http://localhost:3000/blog` when the application runs with `rails server`. @@ -523,7 +523,7 @@ To do all this, you'll need to add the `attr_accessor` for `author_name`, the as ```ruby attr_accessor :author_name -belongs_to :author, :class_name => "User" +belongs_to :author, class_name: "User" before_save :set_author @@ -563,7 +563,7 @@ Run this migration using this command: $ rake db:migrate ``` -Now with all the pieces in place, an action will take place that will associate an author -- represented by a record in the `users` table -- with a post, represented by the `blorgh_posts` table from the engine. +Now with all the pieces in place, an action will take place that will associate an author — represented by a record in the `users` table — with a post, represented by the `blorgh_posts` table from the engine. Finally, the author's name should be displayed on the post's page. Add this code above the "Title" output inside `app/views/blorgh/posts/show.html.erb`: @@ -622,7 +622,7 @@ This method works like its brothers `attr_accessor` and `cattr_accessor`, but pr The next step is switching the `Blorgh::Post` model over to this new setting. For the `belongs_to` association inside this model (`app/models/blorgh/post.rb`), it will now become this: ```ruby -belongs_to :author, :class_name => Blorgh.user_class +belongs_to :author, class_name: Blorgh.user_class ``` The `set_author` method also located in this class should also use this class: @@ -665,7 +665,7 @@ There are now no strict dependencies on what the class is, only what the API for Within an engine, there may come a time where you wish to use things such as initializers, internationalization or other configuration options. The great news is that these things are entirely possible because a Rails engine shares much the same functionality as a Rails application. In fact, a Rails application's functionality is actually a superset of what is provided by engines! -If you wish to use an initializer -- code that should run before the engine is loaded -- the place for it is the `config/initializers` folder. This directory's functionality is explained in the [Initializers section](http://guides.rubyonrails.org/configuring.html#initializers) of the Configuring guide, and works precisely the same way as the `config/initializers` directory inside an application. Same goes for if you want to use a standard initializer. +If you wish to use an initializer — code that should run before the engine is loaded — the place for it is the `config/initializers` folder. This directory's functionality is explained in the [Initializers section](http://guides.rubyonrails.org/configuring.html#initializers) of the Configuring guide, and works precisely the same way as the `config/initializers` directory inside an application. Same goes for if you want to use a standard initializer. For locales, simply place the locale files in the `config/locales` directory, just like you would in an application. @@ -678,7 +678,7 @@ The `test` directory should be treated like a typical Rails testing environment, ### Functional tests -A matter worth taking into consideration when writing functional tests is that the tests are going to be running on an application -- the `test/dummy` application -- rather than your engine. This is due to the setup of the testing environment; an engine needs an application as a host for testing its main functionality, especially controllers. This means that if you were to make a typical `GET` to a controller in a controller's functional test like this: +A matter worth taking into consideration when writing functional tests is that the tests are going to be running on an application — the `test/dummy` application — rather than your engine. This is due to the setup of the testing environment; an engine needs an application as a host for testing its main functionality, especially controllers. This means that if you were to make a typical `GET` to a controller in a controller's functional test like this: ```ruby get :index @@ -687,7 +687,7 @@ get :index It may not function correctly. This is because the application doesn't know how to route these requests to the engine unless you explicitly tell it **how**. To do this, you must pass the `:use_route` option (as a parameter) on these requests also: ```ruby -get :index, :use_route => :blorgh +get :index, use_route: :blorgh ``` This tells the application that you still want to perform a `GET` request to the `index` action of this controller, just that you want to use the engine's route to get there, rather than the application. @@ -791,7 +791,7 @@ module Blorgh::Concerns::Models::Post # executed in the module's context (blorgh/concerns/models/post). included do attr_accessor :author_name - belongs_to :author, :class_name => "User" + belongs_to :author, class_name: "User" before_save :set_author diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index f5db76f217..fc317d4773 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -57,7 +57,7 @@ One of the most basic forms you see on the web is a search form. This form conta To create this form you will use `form_tag`, `label_tag`, `text_field_tag`, and `submit_tag`, respectively. Like this: ```erb -<%= form_tag("/search", :method => "get") do %> +<%= form_tag("/search", method: "get") do %> <%= label_tag(:q, "Search for:") %> <%= text_field_tag(:q) %> <%= submit_tag("Search") %> @@ -87,14 +87,14 @@ The `form_tag` helper accepts 2 arguments: the path for the action and an option As with the `link_to` helper, the path argument doesn't have to be a string; it can be a hash of URL parameters recognizable by Rails' routing mechanism, which will turn the hash into a valid URL. However, since both arguments to `form_tag` are hashes, you can easily run into a problem if you would like to specify both. For instance, let's say you write this: ```ruby -form_tag(:controller => "people", :action => "search", :method => "get", :class => "nifty_form") +form_tag(controller: "people", action: "search", method: "get", class: "nifty_form") # => '<form accept-charset="UTF-8" action="/people/search?method=get&class=nifty_form" method="post">' ``` Here, `method` and `class` are appended to the query string of the generated URL because even though you mean to write two hashes, you really only specified one. So you need to tell Ruby which is which by delimiting the first hash (or both) with curly brackets. This will generate the HTML you expect: ```ruby -form_tag({:controller => "people", :action => "search"}, :method => "get", :class => "nifty_form") +form_tag({controller: "people", action: "search"}, method: "get", class: "nifty_form") # => '<form accept-charset="UTF-8" action="/people/search" method="get" class="nifty_form">' ``` @@ -155,7 +155,7 @@ NOTE: Always use labels for checkbox and radio buttons. They associate text with Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, color fields, datetime fields, datetime-local fields, month fields, week fields, URL fields and email fields: ```erb -<%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %> +<%= text_area_tag(:message, "Hi, nice site", size: "24x6") %> <%= password_field_tag(:password) %> <%= hidden_field_tag(:parent_id, "5") %> <%= search_field(:user, :name) %> @@ -236,9 +236,9 @@ end The corresponding view `app/views/articles/new.html.erb` using `form_for` looks like this: ```erb -<%= form_for @article, :url => { :action => "create" }, :html => {:class => "nifty_form"} do |f| %> +<%= form_for @article, url: {action: "create"}, html => {class: "nifty_form"} do |f| %> <%= f.text_field :title %> - <%= f.text_area :body, :size => "60x12" %> + <%= f.text_area :body, size: "60x12" %> <%= f.submit "Create" %> <% end %> ``` @@ -267,7 +267,7 @@ The helper methods called on the form builder are identical to the model object You can create a similar binding without actually creating `<form>` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for creating both like so: ```erb -<%= form_for @person, :url => { :action => "create" } do |person_form| %> +<%= form_for @person, url: {action: "create"} do |person_form| %> <%= person_form.text_field :name %> <%= fields_for @person.contact_detail do |contact_details_form| %> <%= contact_details_form.text_field :phone_number %> @@ -288,7 +288,7 @@ The object yielded by `fields_for` is a form builder like the one yielded by `fo ### Relying on Record Identification -The Article model is directly available to users of the application, so -- following the best practices for developing with Rails -- you should declare it **a resource**: +The Article model is directly available to users of the application, so — following the best practices for developing with Rails — you should declare it **a resource**: ```ruby resources :articles @@ -301,13 +301,13 @@ When dealing with RESTful resources, calls to `form_for` can get significantly e ```ruby ## Creating a new article # long-style: -form_for(@article, :url => articles_path) +form_for(@article, url: articles_path) # same thing, short-style (record identification gets used): form_for(@article) ## Editing an existing article # long-style: -form_for(@article, :url => article_path(@article), :html => { :method => "patch" }) +form_for(@article, url: article_path(@article), html: {method: "patch"}) # short-style: form_for(@article) ``` @@ -342,7 +342,7 @@ The Rails framework encourages RESTful design of your applications, which means Rails works around this issue by emulating other methods over POST with a hidden input named `"_method"`, which is set to reflect the desired method: ```ruby -form_tag(search_path, :method => "patch") +form_tag(search_path, method: "patch") ``` output: @@ -379,7 +379,7 @@ Here you have a list of cities whose names are presented to the user. Internally ### The Select and Option Tags -The most generic helper is `select_tag`, which -- as the name implies -- simply generates the `SELECT` tag that encapsulates an options string: +The most generic helper is `select_tag`, which — as the name implies — simply generates the `SELECT` tag that encapsulates an options string: ```erb <%= select_tag(:city_id, '<option value="1">Lisbon</option>...') %> @@ -419,14 +419,14 @@ output: Whenever Rails sees that the internal value of an option being generated matches this value, it will add the `selected` attribute to that option. -TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to `options_for_select` -- you must pass 2. Be aware of values extracted from the `params` hash as they are all strings. +TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to `options_for_select` — you must pass 2. Be aware of values extracted from the `params` hash as they are all strings. WARNING: when `:inlude_blank` or `:prompt:` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one and `multiple` is not true. You can add arbitrary attributes to the options using hashes: ```html+erb -<%= options_for_select([['Lisbon', 1, :'data-size' => '2.8 million'], ['Madrid', 2, :'data-size' => '3.2 million']], 2) %> +<%= options_for_select([['Lisbon', 1, 'data-size': '2.8 million'], ['Madrid', 2, 'data-size': '3.2 million']], 2) %> output: @@ -441,7 +441,7 @@ In most cases form controls will be tied to a specific database model and as you ```ruby # controller: -@person = Person.new(:city_id => 2) +@person = Person.new(city_id: 2) ``` ```erb @@ -449,7 +449,7 @@ In most cases form controls will be tied to a specific database model and as you <%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %> ``` -Notice that the third parameter, the options array, is the same kind of argument you pass to `options_for_select`. One advantage here is that you don't have to worry about pre-selecting the correct city if the user already has one -- Rails will do this for you by reading from the `@person.city_id` attribute. +Notice that the third parameter, the options array, is the same kind of argument you pass to `options_for_select`. One advantage here is that you don't have to worry about pre-selecting the correct city if the user already has one — Rails will do this for you by reading from the `@person.city_id` attribute. As with other helpers, if you were to use the `select` helper on a form builder scoped to the `@person` object, the syntax would be: @@ -512,7 +512,7 @@ Both of these families of helpers will create a series of select boxes for the d The `select_*` family of helpers take as their first argument an instance of Date, Time or DateTime that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example ```erb -<%= select_date Date.today, :prefix => :start_date %> +<%= select_date Date.today, prefix: :start_date %> ``` outputs (with actual option values omitted for brevity) @@ -585,7 +585,7 @@ A common task is uploading some sort of file, whether it's a picture of a person The following two forms both upload a file. ```erb -<%= form_tag({:action => :upload}, :multipart => true) do %> +<%= form_tag({action: :upload}, multipart: true) do %> <%= file_field_tag 'picture' %> <% end %> @@ -617,7 +617,7 @@ NOTE: If the user has not selected a file the corresponding parameter will be an ### Dealing with Ajax -Unlike other forms making an asynchronous file upload form is not as simple as providing `form_for` with `:remote => true`. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. +Unlike other forms making an asynchronous file upload form is not as simple as providing `form_for` with `remote: true`. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission. Customizing Form Builders ------------------------- @@ -633,7 +633,7 @@ As mentioned previously the object yielded by `form_for` and `fields_for` is an can be replaced with ```erb -<%= form_for @person, :builder => LabellingFormBuilder do |f| %> +<%= form_for @person, builder: LabellingFormBuilder do |f| %> <%= f.text_field :first_name %> <% end %> ``` @@ -648,12 +648,12 @@ class LabellingFormBuilder < ActionView::Helpers::FormBuilder end ``` -If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `:builder => LabellingFormBuilder` option. +If you reuse this frequently you could define a `labeled_form_for` helper that automatically applies the `builder: LabellingFormBuilder` option. The form builder used also determines what happens when you do ```erb -<%= render :partial => f %> +<%= render partial: f %> ``` If `f` is an instance of FormBuilder then this will render the `form` partial, setting the partial's object to the form builder. If the form builder is of class LabellingFormBuilder then the `labelling_form` partial would be rendered instead. @@ -737,7 +737,7 @@ You might want to render a form with a set of edit fields for each of a person's <%= form_for @person do |person_form| %> <%= person_form.text_field :name %> <% @person.addresses.each do |address| %> - <%= person_form.fields_for address, :index => address do |address_form|%> + <%= person_form.fields_for address, index: address do |address_form|%> <%= address_form.text_field :city %> <% end %> <% end %> @@ -765,7 +765,7 @@ Rails knows that all these inputs should be part of the person hash because you To create more intricate nestings, you can specify the first part of the input name (`person[address]` in the previous example) explicitly, for example ```erb -<%= fields_for 'person[address][primary]', address, :index => address do |address_form| %> +<%= fields_for 'person[address][primary]', address, index: address do |address_form| %> <%= address_form.text_field :city %> <% end %> ``` @@ -778,7 +778,7 @@ will create inputs like As a general rule the final input name is the concatenation of the name given to `fields_for`/`form_for`, the index value and the name of the attribute. You can also pass an `:index` option directly to helpers such as `text_field`, but it is usually less repetitive to specify this at the form builder level rather than on individual input controls. -As a shortcut you can append [] to the name and omit the `:index` option. This is the same as specifying `:index => address` so +As a shortcut you can append [] to the name and omit the `:index` option. This is the same as specifying `index: address` so ```erb <%= fields_for 'person[address][primary][]', address do |address_form| %> @@ -791,10 +791,10 @@ produces exactly the same output as the previous example. Forms to external resources --------------------------- -If you need to post some data to an external resource it is still great to build your form using rails form helpers. But sometimes you need to set an `authenticity_token` for this resource. You can do it by passing an `:authenticity_token => 'your_external_token'` parameter to the `form_tag` options: +If you need to post some data to an external resource it is still great to build your form using rails form helpers. But sometimes you need to set an `authenticity_token` for this resource. You can do it by passing an `authenticity_token: 'your_external_token'` parameter to the `form_tag` options: ```erb -<%= form_tag 'http://farfar.away/form', :authenticity_token => 'external_token') do %> +<%= form_tag 'http://farfar.away/form', authenticity_token: 'external_token') do %> Form contents <% end %> ``` @@ -802,7 +802,7 @@ If you need to post some data to an external resource it is still great to build Sometimes when you submit data to an external resource, like payment gateway, fields you can use in your form are limited by an external API. So you may want not to generate an `authenticity_token` hidden field at all. For doing this just pass `false` to the `:authenticity_token` option: ```erb -<%= form_tag 'http://farfar.away/form', :authenticity_token => false) do %> +<%= form_tag 'http://farfar.away/form', authenticity_token: false) do %> Form contents <% end %> ``` @@ -810,7 +810,7 @@ Sometimes when you submit data to an external resource, like payment gateway, fi The same technique is available for the `form_for` too: ```erb -<%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f| %> +<%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %> Form contents <% end %> ``` @@ -818,7 +818,7 @@ The same technique is available for the `form_for` too: Or if you don't want to render an `authenticity_token` field: ```erb -<%= form_for @invoice, :url => external_url, :authenticity_token => false do |f| %> +<%= form_for @invoice, url: external_url, authenticity_token: false do |f| %> Form contents <% end %> ``` @@ -902,7 +902,7 @@ end The keys of the `:addresses_attributes` hash are unimportant, they need merely be different for each address. -If the associated object is already saved, `fields_for` autogenerates a hidden input with the `id` of the saved record. You can disable this by passing `:include_id => false` to `fields_for`. You may wish to do this if the autogenerated input is placed in a location where an input tag is not valid HTML or when using an ORM where children do not have an id. +If the associated object is already saved, `fields_for` autogenerates a hidden input with the `id` of the saved record. You can disable this by passing `include_id: false` to `fields_for`. You may wish to do this if the autogenerated input is placed in a location where an input tag is not valid HTML or when using an ORM where children do not have an id. ### The Controller @@ -910,12 +910,12 @@ You do not need to write any specific controller code to use nested attributes. ### Removing Objects -You can allow users to delete associated objects by passing `allow_destroy => true` to `accepts_nested_attributes_for` +You can allow users to delete associated objects by passing `allow_destroy: true` to `accepts_nested_attributes_for` ```ruby class Person < ActiveRecord::Base has_many :addresses - accepts_nested_attributes_for :addresses, :allow_destroy => true + accepts_nested_attributes_for :addresses, allow_destroy: true end ``` @@ -944,7 +944,7 @@ It is often useful to ignore sets of fields that the user has not filled in. You ```ruby class Person < ActiveRecord::Base has_many :addresses - accepts_nested_attributes_for :addresses, :reject_if => lambda {|attributes| attributes['kind'].blank?} + accepts_nested_attributes_for :addresses, reject_if: lambda {|attributes| attributes['kind'].blank?} end ``` diff --git a/guides/source/generators.md b/guides/source/generators.md index d56bbe853c..d1ba19e078 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -164,7 +164,7 @@ Rails own generators are flexible enough to let you customize scaffolding. They config.generators do |g| g.orm :active_record g.template_engine :erb - g.test_framework :test_unit, :fixture => true + g.test_framework :test_unit, fixture: true end ``` @@ -206,7 +206,7 @@ Our first customization on the workflow will be to stop generating stylesheets a config.generators do |g| g.orm :active_record g.template_engine :erb - g.test_framework :test_unit, :fixture => false + g.test_framework :test_unit, fixture: false g.stylesheets false end ``` @@ -253,7 +253,7 @@ Which is what we expected. We can now tell scaffold to use our new helper genera config.generators do |g| g.orm :active_record g.template_engine :erb - g.test_framework :test_unit, :fixture => false + g.test_framework :test_unit, fixture: false g.stylesheets false g.helper :my_helper end @@ -292,7 +292,7 @@ Now, when the helper generator is invoked and TestUnit is configured as the test ```ruby # Search for :helper instead of :my_helper -hook_for :test_framework, :as => :helper +hook_for :test_framework, as: :helper ``` And now you can re-run scaffold for another resource and see it generating tests as well! @@ -316,7 +316,7 @@ and revert the last change in `config/application.rb`: config.generators do |g| g.orm :active_record g.template_engine :erb - g.test_framework :test_unit, :fixture => false + g.test_framework :test_unit, fixture: false g.stylesheets false end ``` @@ -334,7 +334,7 @@ We can easily simulate this behavior by changing our `config/application.rb` onc config.generators do |g| g.orm :active_record g.template_engine :erb - g.test_framework :shoulda, :fixture => false + g.test_framework :shoulda, fixture: false g.stylesheets false # Add a fallback! @@ -376,18 +376,18 @@ Fallbacks allow your generators to have a single responsibility, increasing code Application Templates --------------------- -Now that you've seen how generators can be used _inside_ an application, did you know they can also be used to _generate_ applications too? This kind of generator is referred as a "template". +Now that you've seen how generators can be used _inside_ an application, did you know they can also be used to _generate_ applications too? This kind of generator is referred as a "template". This is a brief overview of the Templates API. For detailed documentation see the [Rails Application Templates guide](rails_application_templates.html). ```ruby -gem("rspec-rails", :group => "test") -gem("cucumber-rails", :group => "test") +gem "rspec-rails", group: "test" +gem "cucumber-rails", group: "test" if yes?("Would you like to install Devise?") - gem("devise") - generate("devise:install") + gem "devise" + generate "devise:install" model_name = ask("What would you like the user model to be called? [user]") model_name = "user" if model_name.blank? - generate("devise", model_name) + generate "devise", model_name end ``` @@ -421,8 +421,8 @@ NOTE: Methods provided by Thor are not covered this guide and can be found in [T Specifies a gem dependency of the application. ```ruby -gem("rspec", :group => "test", :version => "2.1.0") -gem("devise", "1.1.5") +gem "rspec", group: "test", version: "2.1.0" +gem "devise", "1.1.5" ``` Available options are: @@ -434,13 +434,13 @@ Available options are: Any additional options passed to this method are put on the end of the line: ```ruby -gem("devise", :git => "git://github.com/plataformatec/devise", :branch => "master") +gem "devise", git: "git://github.com/plataformatec/devise", branch: "master" ``` The above code will put the following line into `Gemfile`: ```ruby -gem "devise", :git => "git://github.com/plataformatec/devise", :branch => "master" +gem "devise", git: "git://github.com/plataformatec/devise", branch: "master" ``` ### `gem_group` @@ -466,7 +466,7 @@ add_source "http://gems.github.com" Injects a block of code into a defined position in your file. ```ruby -inject_into_file 'name_of_file.rb', :after => "#The code goes below this line. Don't forget the Line break at the end\n" do <<-'RUBY' +inject_into_file 'name_of_file.rb', after: "#The code goes below this line. Don't forget the Line break at the end\n" do <<-'RUBY' puts "Hello World" RUBY end @@ -503,7 +503,7 @@ Available options are: * `:env` - Specify an environment for this configuration option. If you wish to use this option with the block syntax the recommended syntax is as follows: ```ruby -application(nil, :env => "development") do +application(nil, env: "development") do "config.asset_host = 'http://localhost:3000'" end ``` @@ -514,9 +514,9 @@ Runs the specified git command: ```ruby git :init -git :add => "." -git :commit => "-m First commit!" -git :add => "onefile.rb", :rm => "badfile.cxx" +git add: "." +git commit: "-m First commit!" +git add: "onefile.rb", rm: "badfile.cxx" ``` The values of the hash here being the arguments or options passed to the specific git command. As per the final example shown here, multiple git commands can be specified at a time, but the order of their running is not guaranteed to be the same as the order that they were specified in. @@ -526,13 +526,13 @@ The values of the hash here being the arguments or options passed to the specifi Places a file into `vendor` which contains the specified code. ```ruby -vendor("sekrit.rb", '#top secret stuff') +vendor "sekrit.rb", '#top secret stuff' ``` This method also takes a block: ```ruby -vendor("seeds.rb") do +vendor "seeds.rb" do "puts 'in ur app, seeding ur database'" end ``` @@ -542,13 +542,13 @@ end Places a file into `lib` which contains the specified code. ```ruby -lib("special.rb", 'p Rails.root') +lib "special.rb", "p Rails.root" ``` This method also takes a block: ```ruby -lib("super_special.rb") do +lib "super_special.rb" do puts "Super special!" end ``` @@ -558,15 +558,15 @@ end Creates a Rake file in the `lib/tasks` directory of the application. ```ruby -rakefile("test.rake", 'hello there') +rakefile "test.rake", "hello there" ``` This method also takes a block: ```ruby -rakefile("test.rake") do +rakefile "test.rake" do %Q{ - task :rock => :environment do + task rock: :environment do puts "Rockin'" end } @@ -578,13 +578,13 @@ end Creates an initializer in the `config/initializers` directory of the application: ```ruby -initializer("begin.rb", "puts 'this is the beginning'") +initializer "begin.rb", "puts 'this is the beginning'" ``` This method also takes a block: ```ruby -initializer("begin.rb") do +initializer "begin.rb" do puts "Almost done!" end ``` @@ -594,7 +594,7 @@ end Runs the specified generator where the first argument is the generator name and the remaining arguments are passed directly to the generator. ```ruby -generate("scaffold", "forums title:string description:text") +generate "scaffold", "forums title:string description:text" ``` @@ -603,7 +603,7 @@ generate("scaffold", "forums title:string description:text") Runs the specified Rake task. ```ruby -rake("db:migrate") +rake "db:migrate" ``` Available options are: @@ -624,7 +624,7 @@ capify! Adds text to the `config/routes.rb` file: ```ruby -route("resources :people") +route "resources :people" ``` ### `readme` @@ -632,5 +632,5 @@ route("resources :people") Output the contents of a file in the template's `source_path`, usually a README. ```ruby -readme("README") +readme "README" ``` diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index efb35416f8..76556761f7 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -263,11 +263,11 @@ It will look a little basic for now, but that's ok. We'll look at improving the ### Laying down the ground work -The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at `/posts/new`. If you attempt to navigate to that now -- by visiting <http://localhost:3000/posts/new> -- Rails will give you a routing error: +The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at `/posts/new`. If you attempt to navigate to that now — by visiting <http://localhost:3000/posts/new> — Rails will give you a routing error:  -This is because there is nowhere inside the routes for the application -- defined inside `config/routes.rb` -- that defines this route. By default, Rails has no routes configured at all, besides the root route you defined earlier, and so you must define your routes as you need them. +This is because there is nowhere inside the routes for the application — defined inside `config/routes.rb` — that defines this route. By default, Rails has no routes configured at all, besides the root route you defined earlier, and so you must define your routes as you need them. To do this, you're going to need to create a route inside `config/routes.rb` file, on a new line between the `do` and the `end` for the `draw` method: @@ -325,7 +325,7 @@ That's quite a lot of text! Let's quickly go through and understand what each pa The first part identifies what template is missing. In this case, it's the `posts/new` template. Rails will first look for this template. If not found, then it will attempt to load a template called `application/new`. It looks for one here because the `PostsController` inherits from `ApplicationController`. -The next part of the message contains a hash. The `:locale` key in this hash simply indicates what spoken language template should be retrieved. By default, this is the English -- or "en" -- template. The next key, `:formats` specifies the format of template to be served in response. The default format is `:html`, and so Rails is looking for an HTML template. The final key, `:handlers`, is telling us what _template handlers_ could be used to render our template. `:erb` is most commonly used for HTML templates, `:builder` is used for XML templates, and `:coffee` uses CoffeeScript to build JavaScript templates. +The next part of the message contains a hash. The `:locale` key in this hash simply indicates what spoken language template should be retrieved. By default, this is the English — or "en" — template. The next key, `:formats` specifies the format of template to be served in response. The default format is `:html`, and so Rails is looking for an HTML template. The final key, `:handlers`, is telling us what _template handlers_ could be used to render our template. `:erb` is most commonly used for HTML templates, `:builder` is used for XML templates, and `:coffee` uses CoffeeScript to build JavaScript templates. The final part of this message tells us where Rails has looked for the templates. Templates within a basic Rails application like this are kept in a single location, but in more complex applications it could be many different paths. @@ -368,7 +368,7 @@ If you refresh the page now, you'll see the exact same form as in the example. B When you call `form_for`, you pass it an identifying object for this form. In this case, it's the symbol `:post`. This tells the `form_for` helper what this form is for. Inside the block for this method, the -`FormBuilder` object -- represented by `f` -- is used to build two labels and two text fields, one each for the title and text of a post. Finally, a call to `submit` on the `f` object will create a submit button for the form. +`FormBuilder` object — represented by `f` — is used to build two labels and two text fields, one each for the title and text of a post. Finally, a call to `submit` on the `f` object will create a submit button for the form. There's one problem with this form though. If you inspect the HTML that is generated, by viewing the source of the page, you will see that the `action` attribute for the form is pointing at `/posts/new`. This is a problem because this route goes to the very page that you're on right at the moment, and that route should only be used to display the form for a new post. @@ -653,7 +653,7 @@ Let's add links to the other views as well, starting with adding this "New Post" <%= link_to 'New post', action: :new %> ``` -This link will allow you to bring up the form that lets you create a new post. You should also add a link to this template -- `app/views/posts/new.html.erb` -- to go back to the `index` action. Do this by adding this underneath the form in this template: +This link will allow you to bring up the form that lets you create a new post. You should also add a link to this template — `app/views/posts/new.html.erb` — to go back to the `index` action. Do this by adding this underneath the form in this template: ```erb <%= form_for :post do |f| %> diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 9d8287ab7c..5ffd955f66 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -13,8 +13,8 @@ So, in the process of _internationalizing_ your Rails application you have to: In the process of _localizing_ your application you'll probably want to do the following three things: -* Replace or supplement Rails' default locale -- e.g. date and time formats, month names, Active Record model names, etc. -* Abstract strings in your application into keyed dictionaries -- e.g. flash messages, static text in your views, etc. +* Replace or supplement Rails' default locale — e.g. date and time formats, month names, Active Record model names, etc. +* Abstract strings in your application into keyed dictionaries — e.g. flash messages, static text in your views, etc. * Store the resulting dictionaries somewhere This guide will walk you through the I18n API and contains a tutorial on how to internationalize a Rails application from the start. @@ -31,13 +31,13 @@ Internationalization is a complex problem. Natural languages differ in so many w * providing support for English and similar languages out of the box * making it easy to customize and extend everything for other languages -As part of this solution, **every static string in the Rails framework** -- e.g. Active Record validation messages, time and date formats -- **has been internationalized**, so _localization_ of a Rails application means "over-riding" these defaults. +As part of this solution, **every static string in the Rails framework** — e.g. Active Record validation messages, time and date formats — **has been internationalized**, so _localization_ of a Rails application means "over-riding" these defaults. ### The Overall Architecture of the Library Thus, the Ruby I18n gem is split into two parts: -* The public API of the i18n framework -- a Ruby module with public methods that define how the library works +* The public API of the i18n framework — a Ruby module with public methods that define how the library works * A default backend (which is intentionally named _Simple_ backend) that implements these methods As a user you should always only access the public methods on the I18n module, but it is useful to know about the capabilities of the backend. @@ -260,7 +260,7 @@ NOTE: Have a look at two plugins which simplify work with routes in this way: Sv ### Setting the Locale from the Client Supplied Information -In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' preferred language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites -- see the box about _sessions_, _cookies_ and RESTful architecture above. +In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' preferred language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites — see the box about _sessions_, _cookies_ and RESTful architecture above. #### Using `Accept-Language` @@ -285,11 +285,11 @@ Of course, in a production environment you would need much more robust code, and #### Using GeoIP (or Similar) Database -Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry). The mechanics of the code would be very similar to the code above -- you would need to query the database for the user's IP, and look up your preferred locale for the country/region/city returned. +Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry). The mechanics of the code would be very similar to the code above — you would need to query the database for the user's IP, and look up your preferred locale for the country/region/city returned. #### User Profile -You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above -- you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value. +You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above — you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value. Internationalizing your Application ----------------------------------- @@ -392,7 +392,7 @@ en: ### Adding Date/Time Formats -OK! Now let's add a timestamp to the view, so we can demo the **date/time localization** feature as well. To localize the time format you pass the Time object to `I18n.l` or (preferably) use Rails' `#l` helper. You can pick a format by passing the `:format` option -- by default the `:default` format is used. +OK! Now let's add a timestamp to the view, so we can demo the **date/time localization** feature as well. To localize the time format you pass the Time object to `I18n.l` or (preferably) use Rails' `#l` helper. You can pick a format by passing the `:format` option — by default the `:default` format is used. ```erb # app/views/home/index.html.erb diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb index 74805b2754..71fe94a870 100644 --- a/guides/source/index.html.erb +++ b/guides/source/index.html.erb @@ -9,8 +9,7 @@ Ruby on Rails Guides <% content_for :index_section do %> <div id="subCol"> <dl> - <dd class="kindle">Rails Guides are also available for the <%= link_to 'Kindle', 'https://kindle.amazon.com' %> -and <%= link_to 'Free Kindle Reading Apps', 'http://www.amazon.com/gp/kindle/kcp' %> for the iPad, + <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="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> diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 5f4f7e8511..141876b5a3 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -81,7 +81,7 @@ If we want to display the properties of all the books in our view, we can do so <td><%= book.content %></td> <td><%= link_to "Show", book %></td> <td><%= link_to "Edit", edit_book_path(book) %></td> - <td><%= link_to "Remove", book, :method => :delete, :data => { :confirm => "Are you sure?" } %></td> + <td><%= link_to "Remove", book, method: :delete, data: { confirm: "Are you sure?" } %></td> </tr> <% end %> </table> @@ -104,7 +104,7 @@ TIP: If you want to see the exact results of a call to `render` without needing Perhaps the simplest thing you can do with `render` is to render nothing at all: ```ruby -render :nothing => true +render nothing: true ``` If you look at the response for this using cURL, you will see the following: @@ -130,7 +130,7 @@ TIP: You should probably be using the `head` method, discussed later in this gui #### Rendering an Action's View -If you want to render the view that corresponds to a different action within the same template, you can use `render` with the name of the view: +If you want to render the view that corresponds to a different template within the same controller, you can use `render` with the name of the view: ```ruby def update @@ -166,7 +166,7 @@ def update if @book.update_attributes(params[:book]) redirect_to(@book) else - render :action => "edit" + render action: "edit" end end ``` @@ -184,7 +184,7 @@ render "products/show" Rails knows that this view belongs to a different controller because of the embedded slash character in the string. If you want to be explicit, you can use the `:template` option (which was required on Rails 2.2 and earlier): ```ruby -render :template => "products/show" +render template: "products/show" ``` #### Rendering an Arbitrary File @@ -198,13 +198,12 @@ render "/u/apps/warehouse_app/current/app/views/products/show" Rails determines that this is a file render because of the leading slash character. To be explicit, you can use the `:file` option (which was required on Rails 2.2 and earlier): ```ruby -render :file => - "/u/apps/warehouse_app/current/app/views/products/show" +render file: "/u/apps/warehouse_app/current/app/views/products/show" ``` The `:file` option takes an absolute file-system path. Of course, you need to have rights to the view that you're using to render the content. -NOTE: By default, the file is rendered without using the current layout. If you want Rails to put the file into the current layout, you need to add the `:layout => true` option. +NOTE: By default, the file is rendered without using the current layout. If you want Rails to put the file into the current layout, you need to add the `layout: true` option. TIP: If you're running Rails on Microsoft Windows, you should use the `:file` option to render a file, because Windows filenames do not have the same format as Unix filenames. @@ -216,19 +215,19 @@ In fact, in the BooksController class, inside of the update action where we want ```ruby render :edit -render :action => :edit +render action: :edit render "edit" render "edit.html.erb" -render :action => "edit" -render :action => "edit.html.erb" +render action: "edit" +render action: "edit.html.erb" render "books/edit" render "books/edit.html.erb" -render :template => "books/edit" -render :template => "books/edit.html.erb" +render template: "books/edit" +render template: "books/edit.html.erb" render "/path/to/rails/app/views/books/edit" render "/path/to/rails/app/views/books/edit.html.erb" -render :file => "/path/to/rails/app/views/books/edit" -render :file => "/path/to/rails/app/views/books/edit.html.erb" +render file: "/path/to/rails/app/views/books/edit" +render file: "/path/to/rails/app/views/books/edit.html.erb" ``` Which one you use is really a matter of style and convention, but the rule of thumb is to use the simplest one that makes sense for the code you are writing. @@ -238,8 +237,7 @@ Which one you use is really a matter of style and convention, but the rule of th The `render` method can do without a view completely, if you're willing to use the `:inline` option to supply ERB as part of the method call. This is perfectly valid: ```ruby -render :inline => - "<% products.each do |p| %><p><%= p.name %></p><% end %>" +render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>" ``` WARNING: There is seldom any good reason to use this option. Mixing ERB into your controllers defeats the MVC orientation of Rails and will make it harder for other developers to follow the logic of your project. Use a separate erb view instead. @@ -247,8 +245,7 @@ WARNING: There is seldom any good reason to use this option. Mixing ERB into you By default, inline rendering uses ERB. You can force it to use Builder instead with the `:type` option: ```ruby -render :inline => - "xml.p {'Horrid coding practice!'}", :type => :builder +render inline: "xml.p {'Horrid coding practice!'}", type: :builder ``` #### Rendering Text @@ -256,19 +253,19 @@ render :inline => You can send plain text - with no markup at all - back to the browser by using the `:text` option to `render`: ```ruby -render :text => "OK" +render text: "OK" ``` TIP: Rendering pure text is most useful when you're responding to Ajax or web service requests that are expecting something other than proper HTML. -NOTE: By default, if you use the `:text` option, the text is rendered without using the current layout. If you want Rails to put the text into the current layout, you need to add the `:layout => true` option. +NOTE: By default, if you use the `:text` option, the text is rendered without using the current layout. If you want Rails to put the text into the current layout, you need to add the `layout: true` option. #### Rendering JSON JSON is a JavaScript data format used by many Ajax libraries. Rails has built-in support for converting objects to JSON and rendering that JSON back to the browser: ```ruby -render :json => @product +render json: @product ``` TIP: You don't need to call `to_json` on the object that you want to render. If you use the `:json` option, `render` will automatically call `to_json` for you. @@ -278,7 +275,7 @@ TIP: You don't need to call `to_json` on the object that you want to render. If Rails also has built-in support for converting objects to XML and rendering that XML back to the caller: ```ruby -render :xml => @product +render xml: @product ``` TIP: You don't need to call `to_xml` on the object that you want to render. If you use the `:xml` option, `render` will automatically call `to_xml` for you. @@ -288,7 +285,7 @@ TIP: You don't need to call `to_xml` on the object that you want to render. If y Rails can render vanilla JavaScript: ```ruby -render :js => "alert('Hello Rails');" +render js: "alert('Hello Rails');" ``` This will send the supplied string to the browser with a MIME type of `text/javascript`. @@ -307,7 +304,7 @@ Calls to the `render` method generally accept four options: By default, Rails will serve the results of a rendering operation with the MIME content-type of `text/html` (or `application/json` if you use the `:json` option, or `application/xml` for the `:xml` option.). There are times when you might like to change this, and you can do so by setting the `:content_type` option: ```ruby -render :file => filename, :content_type => "application/rss" +render file: filename, content_type: "application/rss" ``` ##### The `:layout` Option @@ -317,13 +314,13 @@ With most of the options to `render`, the rendered content is displayed as part You can use the `:layout` option to tell Rails to use a specific file as the layout for the current action: ```ruby -render :layout => "special_layout" +render layout: "special_layout" ``` You can also tell Rails to render with no layout at all: ```ruby -render :layout => false +render layout: false ``` ##### The `:status` Option @@ -331,8 +328,8 @@ render :layout => false Rails will automatically generate a response with the correct HTTP status code (in most cases, this is `200 OK`). You can use the `:status` option to change this: ```ruby -render :status => 500 -render :status => :forbidden +render status: 500 +render status: :forbidden ``` Rails understands both numeric and symbolic status codes. @@ -342,7 +339,7 @@ Rails understands both numeric and symbolic status codes. You can use the `:location` option to set the HTTP `Location` header: ```ruby -render :xml => photo, :location => photo_url(photo) +render xml: photo, location: photo_url(photo) ``` #### Finding Layouts @@ -409,7 +406,7 @@ Layouts specified at the controller level support the `:only` and `:except` opti ```ruby class ProductsController < ApplicationController - layout "product", :except => [:index, :rss] + layout "product", except: [:index, :rss] end ``` @@ -454,7 +451,7 @@ Layout declarations cascade downward in the hierarchy, and more specific layout def index @old_posts = Post.older - render :layout => "old" + render layout: "old" end # ... end @@ -478,9 +475,9 @@ For example, here's some code that will trigger this error: def show @book = Book.find(params[:id]) if @book.special? - render :action => "special_show" + render action: "special_show" end - render :action => "regular_show" + render action: "regular_show" end ``` @@ -490,9 +487,9 @@ If `@book.special?` evaluates to `true`, Rails will start the rendering process def show @book = Book.find(params[:id]) if @book.special? - render :action => "special_show" and return + render action: "special_show" and return end - render :action => "regular_show" + render action: "regular_show" end ``` @@ -504,7 +501,7 @@ Note that the implicit render done by ActionController detects if `render` has b def show @book = Book.find(params[:id]) if @book.special? - render :action => "special_show" + render action: "special_show" end end ``` @@ -530,7 +527,7 @@ redirect_to :back Rails uses HTTP status code 302, a temporary redirect, when you call `redirect_to`. If you'd like to use a different status code, perhaps 301, a permanent redirect, you can use the `:status` option: ```ruby -redirect_to photos_path, :status => 301 +redirect_to photos_path, status: 301 ``` Just like the `:status` option for `render`, `:status` for `redirect_to` accepts both numeric and symbolic header designations. @@ -549,7 +546,7 @@ end def show @book = Book.find_by_id(params[:id]) if @book.nil? - render :action => "index" + render action: "index" end end ``` @@ -564,7 +561,7 @@ end def show @book = Book.find_by_id(params[:id]) if @book.nil? - redirect_to :action => :index + redirect_to action: :index end end ``` @@ -584,7 +581,7 @@ def show @book = Book.find_by_id(params[:id]) if @book.nil? @books = Book.all - render "index", :alert => "Your book was not found!" + render "index", alert: "Your book was not found!" end end ``` @@ -615,7 +612,7 @@ Cache-Control: no-cache Or you can use other HTTP headers to convey other information: ```ruby -head :created, :location => photo_path(@photo) +head :created, location: photo_path(@photo) ``` Which would produce: @@ -661,8 +658,8 @@ WARNING: The asset tag helpers do _not_ verify the existence of the assets at th The `auto_discovery_link_tag` helper builds HTML that most browsers and newsreaders can use to detect the presence of RSS or Atom feeds. It takes the type of the link (`:rss` or `:atom`), a hash of options that are passed through to url_for, and a hash of options for the tag: ```erb -<%= auto_discovery_link_tag(:rss, {:action => "feed"}, - {:title => "RSS Feed"}) %> +<%= auto_discovery_link_tag(:rss, {action: "feed"}, + {title: "RSS Feed"}) %> ``` There are three tag options available for the `auto_discovery_link_tag`: @@ -759,20 +756,20 @@ Note that your defaults of choice will be included first, so they will be availa You can supply the `:recursive` option to load files in subfolders of `public/javascripts` as well: ```erb -<%= javascript_include_tag :all, :recursive => true %> +<%= javascript_include_tag :all, recursive: true %> ``` -If you're loading multiple JavaScript files, you can create a better user experience by combining multiple files into a single download. To make this happen in production, specify `:cache => true` in your `javascript_include_tag`: +If you're loading multiple JavaScript files, you can create a better user experience by combining multiple files into a single download. To make this happen in production, specify `cache: true` in your `javascript_include_tag`: ```erb -<%= javascript_include_tag "main", "columns", :cache => true %> +<%= javascript_include_tag "main", "columns", cache: true %> ``` By default, the combined file will be delivered as `javascripts/all.js`. You can specify a location for the cached asset file instead: ```erb <%= javascript_include_tag "main", "columns", - :cache => "cache/main/display" %> + cache: "cache/main/display" %> ``` You can even use dynamic paths such as `cache/#{current_site}/main/display`. @@ -810,7 +807,7 @@ To include `http://example.com/main.css`: By default, the `stylesheet_link_tag` creates links with `media="screen" rel="stylesheet"`. You can override any of these defaults by specifying an appropriate option (`:media`, `:rel`): ```erb -<%= stylesheet_link_tag "main_print", :media => "print" %> +<%= stylesheet_link_tag "main_print", media: "print" %> ``` If the asset pipeline is disabled, the `all` option links every CSS file in `public/stylesheets`: @@ -822,20 +819,20 @@ If the asset pipeline is disabled, the `all` option links every CSS file in `pub You can supply the `:recursive` option to link files in subfolders of `public/stylesheets` as well: ```erb -<%= stylesheet_link_tag :all, :recursive => true %> +<%= stylesheet_link_tag :all, recursive: true %> ``` -If you're loading multiple CSS files, you can create a better user experience by combining multiple files into a single download. To make this happen in production, specify `:cache => true` in your `stylesheet_link_tag`: +If you're loading multiple CSS files, you can create a better user experience by combining multiple files into a single download. To make this happen in production, specify `cache: true` in your `stylesheet_link_tag`: ```erb -<%= stylesheet_link_tag "main", "columns", :cache => true %> +<%= stylesheet_link_tag "main", "columns", cache: true %> ``` By default, the combined file will be delivered as `stylesheets/all.css`. You can specify a location for the cached asset file instead: ```erb <%= stylesheet_link_tag "main", "columns", - :cache => "cache/main/display" %> + cache: "cache/main/display" %> ``` You can even use dynamic paths such as `cache/#{current_site}/main/display`. @@ -859,28 +856,28 @@ You can supply a path to the image if you like: You can supply a hash of additional HTML options: ```erb -<%= image_tag "icons/delete.gif", {:height => 45} %> +<%= image_tag "icons/delete.gif", {height: 45} %> ``` You can supply alternate text for the image which will be used if the user has images turned off in their browser. If you do not specify an alt text explicitly, it defaults to the file name of the file, capitalized and with no extension. For example, these two image tags would return the same code: ```erb <%= image_tag "home.gif" %> -<%= image_tag "home.gif", :alt => "Home" %> +<%= image_tag "home.gif", alt: "Home" %> ``` You can also specify a special size tag, in the format "{width}x{height}": ```erb -<%= image_tag "home.gif", :size => "50x20" %> +<%= image_tag "home.gif", size: "50x20" %> ``` In addition to the above special tags, you can supply a final hash of standard HTML options, such as `:class`, `:id` or `:name`: ```erb -<%= image_tag "home.gif", :alt => "Go Home", - :id => "HomeImage", - :class => "nav_bar" %> +<%= image_tag "home.gif", alt: "Go Home", + id: "HomeImage", + class: "nav_bar" %> ``` #### Linking to Videos with the `video_tag` @@ -897,15 +894,15 @@ Produces <video src="/videos/movie.ogg" /> ``` -Like an `image_tag` you can supply a path, either absolute, or relative to the `public/videos` directory. Additionally you can specify the `:size => "#{width}x#{height}"` option just like an `image_tag`. Video tags can also have any of the HTML options specified at the end (`id`, `class` et al). +Like an `image_tag` you can supply a path, either absolute, or relative to the `public/videos` directory. Additionally you can specify the `size: "#{width}x#{height}"` option just like an `image_tag`. Video tags can also have any of the HTML options specified at the end (`id`, `class` et al). The video tag also supports all of the `<video>` HTML options through the HTML options hash, including: -* `:poster => "image_name.png"`, provides an image to put in place of the video before it starts playing. -* `:autoplay => true`, starts playing the video on page load. -* `:loop => true`, loops the video once it gets to the end. -* `:controls => true`, provides browser supplied controls for the user to interact with the video. -* `:autobuffer => true`, the video will pre load the file for the user on page load. +* `poster: "image_name.png"`, provides an image to put in place of the video before it starts playing. +* `autoplay: true`, starts playing the video on page load. +* `loop: true`, loops the video once it gets to the end. +* `controls: true`, provides browser supplied controls for the user to interact with the video. +* `autobuffer: true`, the video will pre load the file for the user on page load. You can also specify multiple videos to play by passing an array of videos to the `video_tag`: @@ -937,9 +934,9 @@ You can also supply a hash of additional options, such as `:id`, `:class` etc. Like the `video_tag`, the `audio_tag` has special options: -* `:autoplay => true`, starts playing the audio on page load -* `:controls => true`, provides browser supplied controls for the user to interact with the audio. -* `:autobuffer => true`, the audio will pre load the file for the user on page load. +* `autoplay: true`, starts playing the audio on page load +* `controls: true`, provides browser supplied controls for the user to interact with the audio. +* `autobuffer: true`, the audio will pre load the file for the user on page load. ### Understanding `yield` @@ -1041,7 +1038,7 @@ TIP: For content that is shared among all pages in your application, you can use A partial can use its own layout file, just as a view can use a layout. For example, you might call a partial like this: ```erb -<%= render :partial => "link_area", :layout => "graybar" %> +<%= render partial: "link_area", layout: "graybar" %> ``` This would look for a partial named `_link_area.html.erb` and render it using the layout `_graybar.html.erb`. Note that layouts for partials follow the same leading-underscore naming as regular partials, and are placed in the same folder with the partial that they belong to (not in the master `layouts` folder). @@ -1057,7 +1054,7 @@ You can also pass local variables into partials, making them even more powerful ```html+erb <h1>New zone</h1> <%= error_messages_for :zone %> - <%= render :partial => "form", :locals => { :zone => @zone } %> + <%= render partial: "form", locals: {zone: @zone} %> ``` * `edit.html.erb` @@ -1065,7 +1062,7 @@ You can also pass local variables into partials, making them even more powerful ```html+erb <h1>Editing zone</h1> <%= error_messages_for :zone %> - <%= render :partial => "form", :locals => { :zone => @zone } %> + <%= render partial: "form", locals: {zone: @zone} %> ``` * `_form.html.erb` @@ -1087,7 +1084,7 @@ Although the same partial will be rendered into both views, Action View's submit Every partial also has a local variable with the same name as the partial (minus the underscore). You can pass an object in to this local variable via the `:object` option: ```erb -<%= render :partial => "customer", :object => @new_customer %> +<%= render partial: "customer", object: @new_customer %> ``` Within the `customer` partial, the `customer` variable will refer to `@new_customer` from the parent view. @@ -1110,7 +1107,7 @@ Partials are very useful in rendering collections. When you pass a collection to ```html+erb <h1>Products</h1> - <%= render :partial => "product", :collection => @products %> + <%= render partial: "product", collection: @products %> ``` * `_product.html.erb` @@ -1163,28 +1160,28 @@ In the event that the collection is empty, `render` will return nil, so it shoul To use a custom local variable name within the partial, specify the `:as` option in the call to the partial: ```erb -<%= render :partial => "product", :collection => @products, :as => :item %> +<%= render partial: "product", collection: @products, as: :item %> ``` With this change, you can access an instance of the `@products` collection as the `item` local variable within the partial. -You can also pass in arbitrary local variables to any partial you are rendering with the `:locals => {}` option: +You can also pass in arbitrary local variables to any partial you are rendering with the `locals: {}` option: ```erb -<%= render :partial => "products", :collection => @products, - :as => :item, :locals => {:title => "Products Page"} %> +<%= render partial: "products", collection: @products, + as: :item, locals: {title: "Products Page"} %> ``` Would render a partial `_products.html.erb` once for each instance of `product` in the `@products` instance variable passing the instance to the partial as a local variable called `item` and to each partial, make the local variable `title` available with the value `Products Page`. -TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by `_counter`. For example, if you're rendering `@products`, within the partial you can refer to `product_counter` to tell you how many times the partial has been rendered. This does not work in conjunction with the `:as => :value` option. +TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by `_counter`. For example, if you're rendering `@products`, within the partial you can refer to `product_counter` to tell you how many times the partial has been rendered. This does not work in conjunction with the `as: :value` option. You can also specify a second partial to be rendered between instances of the main partial by using the `:spacer_template` option: #### Spacer Templates ```erb -<%= render :partial => @products, :spacer_template => "product_ruler" %> +<%= render partial: @products, spacer_template: "product_ruler" %> ``` Rails will render the `_product_ruler` partial (with no data passed in to it) between each pair of `_product` partials. @@ -1194,7 +1191,7 @@ Rails will render the `_product_ruler` partial (with no data passed in to it) be When rendering collections it is also possible to use the `:layout` option: ```erb -<%= render :partial => "product", :collection => @products, :layout => "special_layout" %> +<%= render partial: "product", collection: @products, layout: "special_layout" %> ``` The layout will be rendered together with the partial for each item in the collection. The current object and object_counter variables will be available in the layout as well, the same way they do within the partial. @@ -1235,9 +1232,9 @@ On pages generated by `NewsController`, you want to hide the top menu and add a <div id="right_menu">Right menu items here</div> <%= content_for?(:news_content) ? yield(:news_content) : yield %> <% end %> - <%= render :template => "layouts/application" %> + <%= render template: "layouts/application" %> ``` That's it. The News views will use the new layout, hiding the top menu and adding a new right menu inside the "content" div. -There are several ways of getting similar results with different sub-templating schemes using this technique. Note that there is no limit in nesting levels. One can use the `ActionView::render` method via `render :template => 'layouts/news'` to base a new layout on the News layout. If you are sure you will not subtemplate the `News` layout, you can replace the `content_for?(:news_content) ? yield(:news_content) : yield` with simply `yield`. +There are several ways of getting similar results with different sub-templating schemes using this technique. Note that there is no limit in nesting levels. One can use the `ActionView::render` method via `render template: 'layouts/news'` to base a new layout on the News layout. If you are sure you will not subtemplate the `News` layout, you can replace the `content_for?(:news_content) ? yield(:news_content) : yield` with simply `yield`. diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 314ee3f5cd..a1131f1f79 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -64,9 +64,9 @@ bad data in the database or populate new fields: class AddReceiveNewsletterToUsers < ActiveRecord::Migration def up change_table :users do |t| - t.boolean :receive_newsletter, :default => false + t.boolean :receive_newsletter, default: false end - User.update_all :receive_newsletter => true + User.update_all receive_newsletter: true end def down @@ -215,7 +215,7 @@ columns of types not supported by Active Record when using the non-sexy syntax s ```ruby create_table :products do |t| - t.column :name, 'polygon', :null => false + t.column :name, 'polygon', null: false end ``` @@ -349,7 +349,7 @@ generates ```ruby class AddUserRefToProducts < ActiveRecord::Migration def change - add_reference :products, :user, :index => true + add_reference :products, :user, index: true end end ``` @@ -377,8 +377,8 @@ will produce a migration that looks like this ```ruby class AddDetailsToProducts < ActiveRecord::Migration def change - add_column :products, :price, :precision => 5, :scale => 2 - add_reference :products, :user, :polymorphic => true, :index => true + add_column :products, :price, precision: 5, scale: 2 + add_reference :products, :user, polymorphic: true, index: true end end ``` @@ -408,7 +408,7 @@ are two ways of doing it. The first (traditional) form looks like ```ruby create_table :products do |t| - t.column :name, :string, :null => false + t.column :name, :string, null: false end ``` @@ -418,20 +418,20 @@ of that type. Subsequent parameters are the same. ```ruby create_table :products do |t| - t.string :name, :null => false + t.string :name, null: false end ``` By default, `create_table` will create a primary key called `id`. You can change the name of the primary key with the `:primary_key` option (don't forget to update the corresponding model) or, if you don't want a primary key at all (for -example for a HABTM join table), you can pass the option `:id => false`. If you +example for a HABTM join table), you can pass the option `id: false`. If you need to pass database specific options you can place an SQL fragment in the `:options` option. For example, ```ruby -create_table :products, :options => "ENGINE=BLACKHOLE" do |t| - t.string :name, :null => false +create_table :products, options: "ENGINE=BLACKHOLE" do |t| + t.string :name, null: false end ``` @@ -453,7 +453,7 @@ These columns have the option `:null` set to `false` by default. You can pass the option `:table_name` with you want to customize the table name. For example, ```ruby -create_join_table :products, :categories, :table_name => :categorization +create_join_table :products, :categories, table_name: :categorization ``` will create a `categorization` table. @@ -462,7 +462,7 @@ By default, `create_join_table` will create two columns with no options, but you options using the `:column_options` option. For example, ```ruby -create_join_table :products, :categories, :column_options => {:null => true} +create_join_table :products, :categories, column_options: {null: true} ``` will create the `product_id` and `category_id` with the `:null` option as `true`. @@ -523,7 +523,7 @@ of the columns required: ```ruby create_table :products do |t| - t.references :attachment, :polymorphic => {:default => 'Photo'} + t.references :attachment, polymorphic: {default: 'Photo'} end ``` @@ -533,7 +533,7 @@ index directly, instead of using `add_index` after the `create_table` call: ```ruby create_table :products do |t| - t.references :category, :index => true + t.references :category, index: true end ``` @@ -795,7 +795,7 @@ column. class AddFlagToProduct < ActiveRecord::Migration def change add_column :products, :flag, :boolean - Product.update_all :flag => false + Product.update_all flag: false end end ``` @@ -804,7 +804,7 @@ end # app/model/product.rb class Product < ActiveRecord::Base - validates :flag, :presence => true + validates :flag, presence: true end ``` @@ -818,7 +818,7 @@ column. class AddFuzzToProduct < ActiveRecord::Migration def change add_column :products, :fuzz, :string - Product.update_all :fuzz => 'fuzzy' + Product.update_all fuzz: 'fuzzy' end end ``` @@ -827,7 +827,7 @@ end # app/model/product.rb class Product < ActiveRecord::Base - validates :flag, :fuzz, :presence => true + validates :flag, :fuzz, presence: true end ``` @@ -870,7 +870,7 @@ class AddFlagToProduct < ActiveRecord::Migration def change add_column :products, :flag, :boolean Product.reset_column_information - Product.update_all :flag => false + Product.update_all flag: false end end ``` @@ -885,7 +885,7 @@ class AddFuzzToProduct < ActiveRecord::Migration def change add_column :products, :fuzz, :string Product.reset_column_information - Product.update_all :fuzz => 'fuzzy' + Product.update_all fuzz: 'fuzzy' end end ``` @@ -1000,7 +1000,7 @@ the database. As such, features such as triggers or foreign key constraints, which push some of that intelligence back into the database, are not heavily used. -Validations such as `validates :foreign_key, :uniqueness => true` are one way in +Validations such as `validates :foreign_key, uniqueness: true` are one way in which models can enforce data integrity. The `:dependent` option on associations allows models to automatically destroy child objects when the parent is destroyed. Like anything which operates at the application level, these cannot diff --git a/guides/source/performance_testing.md b/guides/source/performance_testing.md index 67ab7cb5b8..248a9643c8 100644 --- a/guides/source/performance_testing.md +++ b/guides/source/performance_testing.md @@ -515,7 +515,7 @@ Add Ruby-Prof to your applications' Gemfile if you want to benchmark/profile under MRI or REE: ```ruby -gem 'ruby-prof', git: 'git://github.com/wycats/ruby-prof.git' +gem 'ruby-prof' ``` Now run `bundle install` and you're ready to go. diff --git a/guides/source/plugins.md b/guides/source/plugins.md index 263f5b1351..c657281741 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -237,7 +237,7 @@ end # test/dummy/app/models/wickwall.rb class Wickwall < ActiveRecord::Base - acts_as_yaffle :yaffle_text_field => :last_tweet + acts_as_yaffle yaffle_text_field: :last_tweet end ``` @@ -402,7 +402,7 @@ Gem plugins currently in development can easily be shared from any Git repositor commit the code to a Git repository (like GitHub) and add a line to the Gemfile of the application in question: ```ruby -gem 'yaffle', :git => 'git://github.com/yaffle_watcher/yaffle.git' +gem 'yaffle', git: 'git://github.com/yaffle_watcher/yaffle.git' ``` After running `bundle install`, your gem functionality will be available to the application. diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index ee5fbcfd52..6cd19eb8e9 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -36,17 +36,17 @@ Rails templates API is very self explanatory and easy to understand. Here's an e # template.rb run "rm public/index.html" generate(:scaffold, "person name:string") -route "root :to => 'people#index'" +route "root to: 'people#index'" rake("db:migrate") git :init -git :add => "." -git :commit => %Q{ -m 'Initial commit' } +git add: "." +git commit: %Q{ -m 'Initial commit' } ``` The following sections outlines the primary methods provided by the API: -### gem(name, options = {}) +### gem(*args) Adds a `gem` entry for the supplied gem to the generated application’s `Gemfile`. @@ -85,6 +85,18 @@ For example, if you need to source a gem from "http://code.whytheluckystiff.net" add_source "http://code.whytheluckystiff.net" ``` +### environment/application(data=nil, options={}, &block) + +Adds a line inside the `Application` class for `config/application.rb`. + +If `options[:env]` is specified, the line is appended to the corresponding file in `config/environments`. + +```ruby +environment 'config.action_mailer.default_url_options = {host: 'http://yourwebsite.example.com'}, env: 'production' +``` + +A block can be used in place of the `data` argument. + ### vendor/lib/file/initializer(filename, data = nil, &block) Adds an initializer to the generated application’s `config/initializers` directory. @@ -136,7 +148,7 @@ end The above creates `lib/tasks/bootstrap.rake` with a `boot:strap` rake task. -### generate(what, args) +### generate(what, *args) Runs the supplied rails generator with given arguments. @@ -163,15 +175,15 @@ rake "db:migrate" You can also run rake tasks with a different Rails environment: ```ruby -rake "db:migrate", :env => 'production' +rake "db:migrate", env: 'production' ``` ### route(routing_code) -This adds a routing entry to the `config/routes.rb` file. In above steps, we generated a person scaffold and also removed `public/index.html`. Now to make `PeopleController#index` as the default page for the application: +Adds a routing entry to the `config/routes.rb` file. In above steps, we generated a person scaffold and also removed `public/index.html`. Now to make `PeopleController#index` as the default page for the application: ```ruby -route "root :to => 'person#index'" +route "root to: 'person#index'" ``` ### inside(dir) @@ -203,7 +215,7 @@ CODE These methods let you ask questions from templates and decide the flow based on the user’s answer. Lets say you want to freeze rails only if the user want to: ```ruby -rake("rails:freeze:gems") if yes?("Freeze rails gems ?") +rake("rails:freeze:gems") if yes?("Freeze rails gems?") # no?(question) acts just the opposite. ``` @@ -213,6 +225,6 @@ Rails templates let you run any git command: ```ruby git :init -git :add => "." -git :commit => "-a -m 'Initial commit'" +git add: "." +git commit: "-a -m 'Initial commit'" ``` diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index ba96c0c0a9..afd1638ed9 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -15,7 +15,7 @@ WARNING: This guide assumes a working knowledge of Rack protocol and Rack concep Introduction to Rack -------------------- -bq. Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call. +Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call. - [Rack API Documentation](http://rack.rubyforge.org/doc/) @@ -58,7 +58,7 @@ Here's how it loads the middlewares: ```ruby def middleware middlewares = [] - middlewares << [Rails::Rack::Debugger] if options[:debugger] + middlewares << [Rails::Rack::Debugger] if options[:debugger] middlewares << [::Rack::ContentLength] Hash.new(middlewares) end @@ -101,7 +101,7 @@ Action Dispatcher Middleware Stack Many of Action Dispatchers's internal components are implemented as Rack middlewares. `Rails::Application` uses `ActionDispatch::MiddlewareStack` to combine various internal and external middlewares to form a complete Rails Rack application. -NOTE: `ActionDispatch::MiddlewareStack` is Rails' equivalent of `Rack::Builder`, but built for better flexibility and more features to meet Rails' requirements. +NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`, but built for better flexibility and more features to meet Rails' requirements. ### Inspecting Middleware Stack @@ -132,11 +132,11 @@ use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser -use ActionDispatch::Head +use Rack::Head use Rack::ConditionalGet use Rack::ETag use ActionDispatch::BestStandardsSupport -run ApplicationName::Application.routes +run MyApp::Application.routes ``` Purpose of each of this middlewares is explained in the [Internal Middlewares](#internal-middleware-stack) section. diff --git a/guides/source/routing.md b/guides/source/routing.md index 469fcf49fb..53f037c25b 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -27,17 +27,17 @@ GET /patients/17 it asks the router to match it to a controller action. If the first matching route is ```ruby -get "/patients/:id" => "patients#show" +get '/patients/:id', to: 'patients#show' ``` -the request is dispatched to the `patients` controller's `show` action with `{ id: "17" }` in `params`. +the request is dispatched to the `patients` controller's `show` action with `{ id: '17' }` in `params`. ### Generating Paths and URLs from Code You can also generate paths and URLs. If the route above is modified to be ```ruby -get "/patients/:id" => "patients#show", as: "patient" +get '/patients/:id', to: 'patients#show', as: 'patient' ``` If your application contains this code: @@ -47,7 +47,7 @@ If your application contains this code: ``` ```erb -<%= link_to "Patient Record", patient_path(@patient) %> +<%= link_to 'Patient Record', patient_path(@patient) %> ``` The router will generate the path `/patients/17`. This reduces the brittleness of your view and makes your code easier to understand. Note that the id does not need to be specified in the route helper. @@ -73,7 +73,7 @@ it asks the router to map it to a controller action. If the first matching route resources :photos ``` -Rails would dispatch that request to the `destroy` method on the `photos` controller with `{ id: "17" }` in `params`. +Rails would dispatch that request to the `destroy` method on the `photos` controller with `{ id: '17' }` in `params`. ### CRUD, Verbs, and Actions @@ -131,7 +131,7 @@ resources :videos Sometimes, you have a resource that clients always look up without referencing an ID. For example, you would like `/profile` to always show the profile of the currently logged in user. In this case, you can use a singular resource to map `/profile` (rather than `/profile/:id`) to the `show` action. ```ruby -get "profile" => "users#show" +get 'profile', to: 'users#show' ``` This resourceful route @@ -186,7 +186,7 @@ This will create a number of routes for each of the `posts` and `comments` contr If you want to route `/posts` (without the prefix `/admin`) to `Admin::PostsController`, you could use ```ruby -scope module: "admin" do +scope module: 'admin' do resources :posts, :comments end ``` @@ -194,13 +194,13 @@ end or, for a single case ```ruby -resources :posts, module: "admin" +resources :posts, module: 'admin' ``` If you want to route `/admin/posts` to `PostsController` (without the `Admin::` module prefix), you could use ```ruby -scope "/admin" do +scope '/admin' do resources :posts, :comments end ``` @@ -208,7 +208,7 @@ end or, for a single case ```ruby -resources :posts, path: "/admin/posts" +resources :posts, path: '/admin/posts' ``` In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `PostsController`: @@ -324,31 +324,31 @@ end When using `magazine_ad_path`, you can pass in instances of `Magazine` and `Ad` instead of the numeric IDs. ```erb -<%= link_to "Ad details", magazine_ad_path(@magazine, @ad) %> +<%= link_to 'Ad details', magazine_ad_path(@magazine, @ad) %> ``` You can also use `url_for` with a set of objects, and Rails will automatically determine which route you want: ```erb -<%= link_to "Ad details", url_for([@magazine, @ad]) %> +<%= link_to 'Ad details', url_for([@magazine, @ad]) %> ``` In this case, Rails will see that `@magazine` is a `Magazine` and `@ad` is an `Ad` and will therefore use the `magazine_ad_path` helper. In helpers like `link_to`, you can specify just the object in place of the full `url_for` call: ```erb -<%= link_to "Ad details", [@magazine, @ad] %> +<%= link_to 'Ad details', [@magazine, @ad] %> ``` If you wanted to link to just a magazine: ```erb -<%= link_to "Magazine details", @magazine %> +<%= link_to 'Magazine details', @magazine %> ``` For other actions, you just need to insert the action name as the first element of the array: ```erb -<%= link_to "Edit Ad", [:edit, @magazine, @ad] %> +<%= link_to 'Edit Ad', [:edit, @magazine, @ad] %> ``` This allows you to treat instances of your models as URLs, and is a key advantage to using the resourceful style. @@ -462,7 +462,7 @@ You can specify static segments when creating a route: get ':controller/:action/:id/with_user/:user_id' ``` -This route would respond to paths such as `/photos/show/1/with_user/2`. In this case, `params` would be `{ controller: "photos", action: "show", id: "1", user_id: "2" }`. +This route would respond to paths such as `/photos/show/1/with_user/2`. In this case, `params` would be `{ controller: 'photos', action: 'show', id: '1', user_id: '2' }`. ### The Query String @@ -472,14 +472,14 @@ The `params` will also include any parameters from the query string. For example get ':controller/:action/:id' ``` -An incoming path of `/photos/show/1?user_id=2` will be dispatched to the `show` action of the `Photos` controller. `params` will be `{ controller: "photos", action: "show", id: "1", user_id: "2" }`. +An incoming path of `/photos/show/1?user_id=2` will be dispatched to the `show` action of the `Photos` controller. `params` will be `{ controller: 'photos', action: 'show', id: '1', user_id: '2' }`. ### Defining Defaults You do not need to explicitly use the `:controller` and `:action` symbols within a route. You can supply them as defaults: ```ruby -get 'photos/:id' => 'photos#show' +get 'photos/:id', to: 'photos#show' ``` With this route, Rails will match an incoming path of `/photos/12` to the `show` action of `PhotosController`. @@ -487,7 +487,7 @@ With this route, Rails will match an incoming path of `/photos/12` to the `show` You can also define other defaults in a route by supplying a hash for the `:defaults` option. This even applies to parameters that you do not specify as dynamic segments. For example: ```ruby -get 'photos/:id' => 'photos#show', defaults: { format: 'jpg' } +get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' } ``` Rails would match `photos/12` to the `show` action of `PhotosController`, and set `params[:format]` to `"jpg"`. @@ -497,7 +497,7 @@ Rails would match `photos/12` to the `show` action of `PhotosController`, and se You can specify a name for any route using the `:as` option. ```ruby -get 'exit' => 'sessions#destroy', as: :logout +get 'exit', to: 'sessions#destroy', as: :logout ``` This will create `logout_path` and `logout_url` as named helpers in your application. Calling `logout_path` will return `/exit` @@ -505,7 +505,7 @@ This will create `logout_path` and `logout_url` as named helpers in your applica You can also use this to override routing methods defined by resources, like this: ```ruby -get ':username', to: "users#show", as: :user +get ':username', to: 'users#show', as: :user ``` This will define a `user_path` method that will be available in controllers, helpers and views that will go to a route such as `/bob`. Inside the `show` action of `UsersController`, `params[:username]` will contain the username for the user. Change `:username` in the route definition if you do not want your parameter name to be `:username`. @@ -515,13 +515,13 @@ This will define a `user_path` method that will be available in controllers, hel In general, you should use the `get`, `post`, `put` and `delete` methods to constrain a route to a particular verb. You can use the `match` method with the `:via` option to match multiple verbs at once: ```ruby -match 'photos' => 'photos#show', via: [:get, :post] +match 'photos', to: 'photos#show', via: [:get, :post] ``` You can match all verbs to a particular route using `via: :all`: ```ruby -match 'photos' => 'photos#show', via: :all +match 'photos', to: 'photos#show', via: :all ``` You should avoid routing all verbs to an action unless you have a good reason to, as routing both `GET` requests and `POST` requests to a single action has security implications. @@ -531,19 +531,19 @@ You should avoid routing all verbs to an action unless you have a good reason to You can use the `:constraints` option to enforce a format for a dynamic segment: ```ruby -get 'photos/:id' => 'photos#show', constraints: { id: /[A-Z]\d{5}/ } +get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{5}/ } ``` This route would match paths such as `/photos/A12345`. You can more succinctly express the same route this way: ```ruby -get 'photos/:id' => 'photos#show', id: /[A-Z]\d{5}/ +get 'photos/:id', to: 'photos#show', id: /[A-Z]\d{5}/ ``` `:constraints` takes regular expressions with the restriction that regexp anchors can't be used. For example, the following route will not work: ```ruby -get '/:id' => 'posts#show', constraints: {id: /^\d/} +get '/:id', to: 'posts#show', constraints: {id: /^\d/} ``` However, note that you don't need to use anchors because all routes are anchored at the start. @@ -551,8 +551,8 @@ However, note that you don't need to use anchors because all routes are anchored For example, the following routes would allow for `posts` with `to_param` values like `1-hello-world` that always begin with a number and `users` with `to_param` values like `david` that never begin with a number to share the root namespace: ```ruby -get '/:id' => 'posts#show', constraints: { id: /\d.+/ } -get '/:username' => 'users#show' +get '/:id', to: 'posts#show', constraints: { id: /\d.+/ } +get '/:username', to: 'users#show' ``` ### Request-Based Constraints @@ -562,14 +562,14 @@ You can also constrain a route based on any method on the <a href="action_contro You specify a request-based constraint the same way that you specify a segment constraint: ```ruby -get "photos", constraints: {subdomain: "admin"} +get 'photos', constraints: {subdomain: 'admin'} ``` You can also specify constraints in a block form: ```ruby namespace :admin do - constraints subdomain: "admin" do + constraints subdomain: 'admin' do resources :photos end end @@ -591,7 +591,7 @@ class BlacklistConstraint end TwitterClone::Application.routes.draw do - get "*path" => "blacklist#index", + get '*path', to: 'blacklist#index', constraints: BlacklistConstraint.new end ``` @@ -600,7 +600,7 @@ You can also specify constraints as a lambda: ```ruby TwitterClone::Application.routes.draw do - get "*path" => "blacklist#index", + get '*path', to: 'blacklist#index', constraints: lambda { |request| Blacklist.retrieve_ips.include?(request.remote_ip) } end ``` @@ -612,7 +612,7 @@ Both the `matches?` method and the lambda gets the `request` object as an argume Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example ```ruby -get 'photos/*other' => 'photos#unknown' +get 'photos/*other', to: 'photos#unknown' ``` This route would match `photos/12` or `/photos/long/path/to/12`, setting `params[:other]` to `"12"` or `"long/path/to/12"`. @@ -620,35 +620,35 @@ This route would match `photos/12` or `/photos/long/path/to/12`, setting `params Wildcard segments can occur anywhere in a route. For example, ```ruby -get 'books/*section/:title' => 'books#show' +get 'books/*section/:title', to: 'books#show' ``` -would match `books/some/section/last-words-a-memoir` with `params[:section]` equals `"some/section"`, and `params[:title]` equals `"last-words-a-memoir"`. +would match `books/some/section/last-words-a-memoir` with `params[:section]` equals `'some/section'`, and `params[:title]` equals `'last-words-a-memoir'`. Technically a route can have even more than one wildcard segment. The matcher assigns segments to parameters in an intuitive way. For example, ```ruby -get '*a/foo/*b' => 'test#index' +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"`. +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 routes will always match the optional format segment by default. For example if you have this route: ```ruby -get '*pages' => 'pages#show' +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: +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 -get '*pages' => 'pages#show', format: false +get '*pages', to: 'pages#show', format: false ``` NOTE: If you want to make the format segment mandatory, so it cannot be omitted, you can supply `format: true` like this: ```ruby -get '*pages' => 'pages#show', format: true +get '*pages', to: 'pages#show', format: true ``` ### Redirection @@ -656,20 +656,20 @@ get '*pages' => 'pages#show', format: true You can redirect any path to another path using the `redirect` helper in your router: ```ruby -get "/stories" => redirect("/posts") +get '/stories', to: redirect('/posts') ``` You can also reuse dynamic segments from the match in the path to redirect to: ```ruby -get "/stories/:name" => redirect("/posts/%{name}") +get '/stories/:name', to: redirect('/posts/%{name}') ``` You can also provide a block to redirect, which receives the params and the request object: ```ruby -get "/stories/:name" => redirect {|params, req| "/posts/#{params[:name].pluralize}" } -get "/stories" => redirect {|p, req| "/posts/#{req.subdomain}" } +get '/stories/:name', to: redirect {|params, req| "/posts/#{params[:name].pluralize}" } +get '/stories', to: redirect {|p, req| "/posts/#{req.subdomain}" } ``` Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible. @@ -678,19 +678,19 @@ In all of these cases, if you don't provide the leading host (`http://www.exampl ### Routing to Rack Applications -Instead of a String, like `"posts#index"`, which corresponds to the `index` action in the `PostsController`, you can specify any <a href="rails_on_rack.html">Rack application</a> as the endpoint for a matcher. +Instead of a String, like `'posts#index'`, which corresponds to the `index` action in the `PostsController`, you can specify any <a href="rails_on_rack.html">Rack application</a> as the endpoint for a matcher. ```ruby -match "/application.js" => Sprockets, via: :all +match '/application.js', to: Sprockets, via: :all ``` As long as `Sprockets` responds to `call` and returns a `[status, headers, body]`, the router won't know the difference between the Rack application and an action. This is an appropriate use of `via: :all`, as you will want to allow your Rack application to handle all verbs as it considers appropriate. -NOTE: For the curious, `"posts#index"` actually expands out to `PostsController.action(:index)`, which returns a valid Rack application. +NOTE: For the curious, `'posts#index'` actually expands out to `PostsController.action(:index)`, which returns a valid Rack application. ### Using `root` -You can specify what Rails should route `"/"` to with the `root` method: +You can specify what Rails should route `'/'` to with the `root` method: ```ruby root to: 'pages#main' @@ -706,7 +706,7 @@ NOTE: The `root` route only routes `GET` requests to the action. You can specify unicode character routes directly. For example ```ruby -match 'こんにちは' => 'welcome#index' +get 'こんにちは', to: 'welcome#index' ``` Customizing Resourceful Routes @@ -719,7 +719,7 @@ While the default routes and helpers generated by `resources :posts` will usuall The `:controller` option lets you explicitly specify a controller to use for the resource. For example: ```ruby -resources :photos, controller: "images" +resources :photos, controller: 'images' ``` will recognize incoming paths beginning with `/photos` but route to the `Images` controller: @@ -764,7 +764,7 @@ TIP: By default the `:id` parameter doesn't accept dots - this is because the do The `:as` option lets you override the normal naming for the named route helpers. For example: ```ruby -resources :photos, as: "images" +resources :photos, as: 'images' ``` will recognize incoming paths beginning with `/photos` and route the requests to `PhotosController`, but use the value of the :as option to name the helpers. @@ -799,7 +799,7 @@ NOTE: The actual action names aren't changed by this option. The two paths shown TIP: If you find yourself wanting to change this option uniformly for all of your routes, you can use a scope. ```ruby -scope path_names: { new: "make" } do +scope path_names: { new: 'make' } do # rest of your routes end ``` @@ -809,8 +809,8 @@ end You can use the `:as` option to prefix the named route helpers that Rails generates for a route. Use this option to prevent name collisions between routes using a path scope. ```ruby -scope "admin" do - resources :photos, as: "admin_photos" +scope 'admin' do + resources :photos, as: 'admin_photos' end resources :photos @@ -821,7 +821,7 @@ This will provide route helpers such as `admin_photos_path`, `new_admin_photo_pa To prefix a group of route helpers, use `:as` with `scope`: ```ruby -scope "admin", as: "admin" do +scope 'admin', as: 'admin' do resources :photos, :accounts end @@ -835,7 +835,7 @@ NOTE: The `namespace` scope will automatically add `:as` as well as `:module` an You can prefix routes with a named parameter also: ```ruby -scope ":username" do +scope ':username' do resources :posts end ``` @@ -867,8 +867,8 @@ TIP: If your application has many RESTful routes, using `:only` and `:except` to Using `scope`, we can alter path names generated by resources: ```ruby -scope(path_names: { new: "neu", edit: "bearbeiten" }) do - resources :categories, path: "kategorien" +scope(path_names: { new: 'neu', edit: 'bearbeiten' }) do + resources :categories, path: 'kategorien' end ``` @@ -952,8 +952,8 @@ Routes should be included in your testing strategy (just like the rest of your a `assert_generates` asserts that a particular set of options generate a particular path and can be used with default routes or custom routes. ```ruby -assert_generates "/photos/1", { controller: "photos", action: "show", id: "1" } -assert_generates "/about", controller: "pages", action: "about" +assert_generates '/photos/1', { controller: 'photos', action: 'show', id: '1' } +assert_generates '/about', controller: 'pages', action: 'about' ``` #### The `assert_recognizes` Assertion @@ -961,13 +961,13 @@ assert_generates "/about", controller: "pages", action: "about" `assert_recognizes` is the inverse of `assert_generates`. It asserts that a given path is recognized and routes it to a particular spot in your application. ```ruby -assert_recognizes({ controller: "photos", action: "show", id: "1" }, "/photos/1") +assert_recognizes({ controller: 'photos', action: 'show', id: '1' }, '/photos/1') ``` You can supply a `:method` argument to specify the HTTP verb: ```ruby -assert_recognizes({ controller: "photos", action: "create" }, { path: "photos", method: :post }) +assert_recognizes({ controller: 'photos', action: 'create' }, { path: 'photos', method: :post }) ``` #### The `assert_routing` Assertion @@ -975,5 +975,5 @@ assert_recognizes({ controller: "photos", action: "create" }, { path: "photos", The `assert_routing` assertion checks the route both ways: it tests that the path generates the options, and that the options generate the path. Thus, it combines the functions of `assert_generates` and `assert_recognizes`. ```ruby -assert_routing({ path: "photos", method: :post }, { controller: "photos", action: "create" }) +assert_routing({ path: 'photos', method: :post }, { controller: 'photos', action: 'create' }) ``` diff --git a/guides/source/security.md b/guides/source/security.md index 5ef68d2272..4902f83f8a 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -187,11 +187,11 @@ In the <a href="#sessions">session chapter</a> you have learned that most Rails * Bob's session at www.webapp.com is still alive, because he didn't log out a few minutes ago. * By viewing the post, the browser finds an image tag. It tries to load the suspected image from www.webapp.com. As explained before, it will also send along the cookie with the valid session id. * The web application at www.webapp.com verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image. -* Bob doesn't notice the attack -- but a few days later he finds out that project number one is gone. +* Bob doesn't notice the attack — but a few days later he finds out that project number one is gone. It is important to notice that the actual crafted image or link doesn't necessarily have to be situated in the web application's domain, it can be anywhere – in a forum, blog post or email. -CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) -- less than 0.1% in 2006 -- but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in my (and others) security contract work – _CSRF is an important security issue_. +CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) — less than 0.1% in 2006 — but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in my (and others) security contract work – _CSRF is an important security issue_. ### CSRF Countermeasures diff --git a/guides/source/testing.md b/guides/source/testing.md index 2e4ada43c3..f898456d39 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -299,7 +299,7 @@ Now to get this test to pass we can add a model level validation for the _title_ ```ruby class Post < ActiveRecord::Base - validates :title, :presence => true + validates :title, presence: true end ``` @@ -397,7 +397,7 @@ Rails adds some custom assertions of its own to the `test/unit` framework: | `assert_recognizes(expected_options, path, extras={}, message=nil)` | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| | `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| | `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range| -| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(:controller => "weblog")` will also match the redirection of `redirect_to(:controller => "weblog", :action => "show")` and so on.| +| `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on.| | `assert_template(expected = nil, message=nil)` | Asserts that the request was rendered with the appropriate template file.| You'll see the usage of some of these assertions in the next chapter. @@ -457,7 +457,7 @@ Let us modify `test_should_create_post` test in `posts_controller_test.rb` so th ```ruby test "should create post" do assert_difference('Post.count') do - post :create, :post => { :title => 'Some title'} + post :create, post: {title: 'Some title'} end assert_redirected_to post_path(assigns(:post)) @@ -518,7 +518,7 @@ method: test "index should render correct template and layout" do get :index assert_template :index - assert_template :layout => "layouts/application" + assert_template layout: "layouts/application" end ``` @@ -528,7 +528,7 @@ things clearer. On the other hand, you have to include the "layouts" directory n file in this standard layout directory. Hence, ```ruby -assert_template :layout => "application" +assert_template layout: "application" ``` will not work. @@ -541,7 +541,7 @@ Hence: ```ruby test "new should render correct layout" do get :new - assert_template :layout => "layouts/application", :partial => "_form" + assert_template layout: "layouts/application", partial: "_form" end ``` @@ -554,7 +554,7 @@ Here's another example that uses `flash`, `assert_redirected_to`, and `assert_di ```ruby test "should create post" do assert_difference('Post.count') do - post :create, :post => { :title => 'Hi', :body => 'This is my first post.'} + post :create, post: {title: 'Hi', body: 'This is my first post.'} end assert_redirected_to post_path(assigns(:post)) assert_equal 'Post was successfully created.', flash[:notice] @@ -686,7 +686,7 @@ class UserFlowsTest < ActionDispatch::IntegrationTest get "/login" assert_response :success - post_via_redirect "/login", :username => users(:avs).username, :password => users(:avs).password + post_via_redirect "/login", username: users(:avs).username, password: users(:avs).password assert_equal '/welcome', path assert_equal 'Welcome avs!', flash[:notice] @@ -742,7 +742,7 @@ class UserFlowsTest < ActionDispatch::IntegrationTest sess.extend(CustomDsl) u = users(user) sess.https! - sess.post "/login", :username => u.username, :password => u.password + sess.post "/login", username: u.username, password: u.password assert_equal '/welcome', path sess.https!(false) end @@ -803,13 +803,13 @@ class PostsControllerTest < ActionController::TestCase end test "should show post" do - get :show, :id => @post.id + get :show, id: @post.id assert_response :success end test "should destroy post" do assert_difference('Post.count', -1) do - delete :destroy, :id => @post.id + delete :destroy, id: @post.id end assert_redirected_to posts_path @@ -841,18 +841,18 @@ class PostsControllerTest < ActionController::TestCase end test "should show post" do - get :show, :id => @post.id + get :show, id: @post.id assert_response :success end test "should update post" do - patch :update, :id => @post.id, :post => { } + patch :update, id: @post.id, post: {} assert_redirected_to post_path(assigns(:post)) end test "should destroy post" do assert_difference('Post.count', -1) do - delete :destroy, :id => @post.id + delete :destroy, id: @post.id end assert_redirected_to posts_path @@ -874,7 +874,7 @@ Like everything else in your Rails application, it is recommended that you test ```ruby test "should route to post" do - assert_routing '/posts/1', { :controller => "posts", :action => "show", :id => "1" } + assert_routing '/posts/1', {controller: "posts", action: "show", id: "1"} end ``` @@ -885,7 +885,7 @@ Testing mailer classes requires some specific tools to do a thorough job. ### Keeping the Postman in Check -Your mailer classes -- like every other part of your Rails application -- should be tested to ensure that it is working as expected. +Your mailer classes — like every other part of your Rails application — should be tested to ensure that it is working as expected. The goals of testing your mailer classes are to ensure that: @@ -955,7 +955,7 @@ require 'test_helper' class UserControllerTest < ActionController::TestCase test "invite friend" do assert_difference 'ActionMailer::Base.deliveries.size', +1 do - post :invite_friend, :email => 'friend@example.com' + post :invite_friend, email: 'friend@example.com' end invite_email = ActionMailer::Base.deliveries.last diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 05a1714966..bf81776006 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -69,6 +69,8 @@ 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 your config/initializers/session_store.rb. You will also need to add Myapp::Application.config.secret_key_base = 'some secret' in your config/initializers/secret_token.rb, but 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 has deprecated `ActionController::Base.page_cache_extension` option. Use @@ -83,13 +85,13 @@ Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routi 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' +get Rack::Utils.escape('こんにちは'), controller: 'welcome', action: 'index' ``` becomes ```ruby -get 'こんにちは', :controller => 'welcome', :action => 'index' +get 'こんにちは', controller: 'welcome', action: 'index' ``` ### Active Support @@ -248,7 +250,7 @@ Add this file with the following contents, if you wish to wrap parameters into a # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActiveSupport.on_load(:action_controller) do - wrap_parameters :format => [:json] + wrap_parameters format: [:json] end # Disable root element in JSON by default. @@ -263,7 +265,7 @@ You need to change your session key to something new, or remove all sessions: ```ruby # in config/initializers/session_store.rb -AppName::Application.config.session_store :cookie_store, :key => 'SOMETHINGNEW' +AppName::Application.config.session_store :cookie_store, key: 'SOMETHINGNEW' ``` or diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 011303c311..10b9dddd02 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -322,7 +322,7 @@ this: end ``` -Notice the format.js in the respond_to block; that allows the controller to +Notice the format.js in the `respond_to` block; that allows the controller to respond to your Ajax request. You then have a corresponding `app/views/users/create.js.erb` view file that generates the actual JavaScript code that will be sent and executed on the client side. @@ -379,7 +379,7 @@ $(document).on "page:change", -> For more details, including other events you can bind to, check out [the Turbolinks -README](https://github.com/rails/turbolinks/blob/ec9ca4d6cf9626e03a672f3b9e7968c816aff94e/README.md). +README](https://github.com/rails/turbolinks/blob/master/README.md). Other Resources --------------- diff --git a/rails.gemspec b/rails.gemspec index 97f6dfeac8..ba94354bf1 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -1,4 +1,4 @@ -version = File.read(File.expand_path("../RAILS_VERSION",__FILE__)).strip +version = File.read(File.expand_path('../RAILS_VERSION', __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -8,8 +8,9 @@ Gem::Specification.new do |s| s.description = 'Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration.' s.required_ruby_version = '>= 1.9.3' - s.required_rubygems_version = ">= 1.8.11" - s.license = 'MIT' + s.required_rubygems_version = '>= 1.8.11' + + s.license = 'MIT' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' @@ -19,11 +20,12 @@ Gem::Specification.new do |s| s.executables = [] s.files = Dir['guides/**/*'] - s.add_dependency('activesupport', version) - s.add_dependency('actionpack', version) - s.add_dependency('activerecord', version) - s.add_dependency('actionmailer', version) - s.add_dependency('railties', version) - s.add_dependency('bundler', '~> 1.2') - s.add_dependency('sprockets-rails', '~> 2.0.0.rc1') + s.add_dependency 'activesupport', version + s.add_dependency 'actionpack', version + s.add_dependency 'activerecord', version + s.add_dependency 'actionmailer', version + s.add_dependency 'railties', version + + s.add_dependency 'bundler', '>= 1.2.2', '< 2.0' + s.add_dependency 'sprockets-rails', '~> 2.0.0.rc1' end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index cc77d08684..7a68cf0a2e 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,22 @@ ## Rails 4.0.0 (unreleased) ## +* Engines with a dummy app include the rake tasks of dependencies in the app namespace. + Fix #8229 + + *Yves Senn* + +* Add sqlserver.yml template file to satisfy '-d sqlserver' being passed to 'rails new'. + Fix #6882 + + *Robert Nesius* + +* Rake test:uncommitted finds git directory in ancestors *Nicolas Despres* + +* Add dummy app Rake tasks when --skip-test-unit and --dummy-path is passed to the plugin generator. + Fix #8121 + + *Yves Senn* + * Ensure that RAILS_ENV is set when accessing Rails.env *Steve Klabnik* * Don't eager-load app/assets and app/views *Elia Schito* @@ -9,7 +26,7 @@ * New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well. *Mike Moore* -* Set a different cache per environment for assets pipeline +* Set a different cache per environment for assets pipeline through `config.assets.cache`. *Guillermo Iguaran* diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 9ef001c7d0..ae3993fbd8 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -1,5 +1,7 @@ require 'fileutils' require 'active_support/queueing' +# FIXME remove DummyKeyGenerator and this require in 4.1 +require 'active_support/key_generator' require 'rails/engine' module Rails @@ -106,32 +108,59 @@ module Rails def key_generator # number of iterations selected based on consultation with the google security # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220 - @key_generator ||= ActiveSupport::KeyGenerator.new(config.secret_token, iterations: 1000) + @caching_key_generator ||= begin + if config.secret_key_base + key_generator = ActiveSupport::KeyGenerator.new(config.secret_key_base, iterations: 1000) + ActiveSupport::CachingKeyGenerator.new(key_generator) + else + ActiveSupport::DummyKeyGenerator.new(config.secret_token) + end + end end # Stores some of the Rails initial environment parameters which # will be used by middlewares and engines to configure themselves. # Currently stores: # - # * "action_dispatch.parameter_filter" => config.filter_parameters, - # * "action_dispatch.secret_token" => config.secret_token, - # * "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions, - # * "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local, - # * "action_dispatch.logger" => Rails.logger, - # * "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner + # * "action_dispatch.parameter_filter" => config.filter_parameters + # * "action_dispatch.secret_token" => config.secret_token, + # * "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions + # * "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local + # * "action_dispatch.logger" => Rails.logger + # * "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner + # * "action_dispatch.key_generator" => key_generator + # * "action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt + # * "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt + # * "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt + # * "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt # # These parameters will be used by middlewares and engines to configure themselves # def env_config - @env_config ||= super.merge({ - "action_dispatch.parameter_filter" => config.filter_parameters, - "action_dispatch.secret_token" => config.secret_token, - "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions, - "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local, - "action_dispatch.logger" => Rails.logger, - "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner, - "action_dispatch.key_generator" => key_generator - }) + @env_config ||= begin + if config.secret_key_base.nil? + ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base. " + + "This should be used instead of the old deprecated config.secret_token. " + + "Set config.secret_key_base instead of config.secret_token in config/initializers/secret_token.rb" + if config.secret_token.blank? + raise "You must set config.secret_key_base in your app's config" + end + end + + super.merge({ + "action_dispatch.parameter_filter" => config.filter_parameters, + "action_dispatch.secret_token" => config.secret_token, + "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions, + "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local, + "action_dispatch.logger" => Rails.logger, + "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner, + "action_dispatch.key_generator" => key_generator, + "action_dispatch.http_auth_salt" => config.action_dispatch.http_auth_salt, + "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt, + "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt, + "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt + }) + end end ## Rails internal API diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index cc21213f1c..89afeaeec5 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -10,7 +10,7 @@ module Rails :cache_classes, :cache_store, :consider_all_requests_local, :console, :eager_load, :exceptions_app, :file_watcher, :filter_parameters, :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, - :railties_order, :relative_url_root, :secret_token, + :railties_order, :relative_url_root, :secret_key_base, :secret_token, :serve_static_assets, :ssl_options, :static_cache_control, :session_options, :time_zone, :reload_classes_only_on_change, :queue, :queue_consumer, :beginning_of_week @@ -46,6 +46,8 @@ module Rails @queue = ActiveSupport::SynchronousQueue.new @queue_consumer = nil @eager_load = nil + @secret_token = nil + @secret_key_base = nil @assets = ActiveSupport::OrderedOptions.new @assets.enabled = false @@ -103,6 +105,10 @@ module Rails def database_configuration require 'erb' YAML.load ERB.new(IO.read(paths["config/database"].first)).result + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{paths["config/database"].first}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" end def log_level diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index c84fa832f5..4a5674236d 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -5,8 +5,8 @@ require 'rbconfig' module Rails class DBConsole - attr_reader :config, :arguments - + attr_reader :arguments + def self.start new.start end @@ -59,7 +59,7 @@ module Rails args << "-#{options['mode']}" if options['mode'] args << "-header" if options['header'] - args << File.expand_path(config['database'], Rails.root) + args << File.expand_path(config['database'], Rails.respond_to?(:root) ? Rails.root : nil) find_cmd_and_exec('sqlite3', *args) @@ -82,17 +82,13 @@ module Rails def config @config ||= begin cfg = begin - cfg = YAML.load(ERB.new(IO.read("config/database.yml")).result) + YAML.load(ERB.new(IO.read("config/database.yml")).result) rescue SyntaxError, StandardError require APP_PATH Rails.application.config.database_configuration end - unless cfg[environment] - abort "No database is configured for the environment '#{environment}'" - end - - cfg[environment] + cfg[environment] || abort("No database is configured for the environment '#{environment}'") end end @@ -108,7 +104,7 @@ module Rails def parse_arguments(arguments) options = {} - + OptionParser.new do |opt| opt.banner = "Usage: rails dbconsole [environment] [options]" opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v| @@ -123,7 +119,7 @@ module Rails opt.on("--header") do |h| options['header'] = h end - + opt.on("-h", "--help", "Show this help message.") do puts opt exit @@ -142,7 +138,7 @@ module Rails env = arguments.first options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env end - + options end @@ -154,7 +150,7 @@ module Rails full_path_command = nil found = commands.detect do |cmd| - dir = dirs_on_path.detect do |path| + dirs_on_path.detect do |path| full_path_command = File.join(path, cmd) File.executable? full_path_command end diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index 0cc672e01c..62d82cc005 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -41,7 +41,7 @@ ENV["RAILS_ENV"] = options[:environment] require APP_PATH Rails.application.require_environment! - Rails.application.load_runner +Rails.application.load_runner if code_or_file.nil? $stderr.puts "Run '#{$0} -h' for help." diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 80fdc06cd2..0b897d736d 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -72,6 +72,7 @@ module Rails console = ActiveSupport::Logger.new($stdout) console.formatter = Rails.logger.formatter + console.level = Rails.logger.level Rails.logger.extend(ActiveSupport::Logger.broadcast(console)) end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 2c2bb1c714..f6721c617f 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -407,6 +407,8 @@ module Rails end end + self.isolated = false + delegate :middleware, :root, :paths, to: :config delegate :engine_name, :isolated?, to: "self.class" diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml new file mode 100644 index 0000000000..b52b733c56 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml @@ -0,0 +1,57 @@ +# SQL Server (2005 or higher recommended) +# +# Install the adapters and driver +# gem install tiny_tds +# gem install activerecord-sqlserver-adapter +# +# Ensure the activerecord adapter and db driver gems are defined in your Gemfile +# gem 'tiny_tds' +# gem 'activerecord-sqlserver-adapter' +# +# You should make sure freetds is configured correctly first. +# freetds.conf contains host/port/protocol_versions settings. +# http://freetds.schemamania.org/userguide/freetdsconf.htm +# +# A typical Microsoft server +# [mssql] +# host = mssqlserver.yourdomain.com +# port = 1433 +# tds version = 7.1 + +# If you can connect with "tsql -S servername", your basic FreeTDS installation is working. +# 'man tsql' for more info +# Set timeout to a larger number if valid queries against a live db fail + +development: + adapter: sqlserver + encoding: utf8 + reconnect: false + database: <%= app_name %>_development + username: <%= app_name %> + password: + timeout: 25 + dataserver: from_freetds.conf + + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + adapter: sqlserver + encoding: utf8 + reconnect: false + database: <%= app_name %>_test + username: <%= app_name %> + password: + timeout: 25 + dataserver: from_freetds.conf + +production: + adapter: sqlserver + encoding: utf8 + reconnect: false + database: <%= app_name %>_production + username: <%= app_name %> + password: + timeout: 25 + dataserver: from_freetds.conf diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 9df53bd6c9..593d2acfc7 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -57,7 +57,8 @@ # config.action_controller.asset_host = "http://assets.example.com" <%- unless options.skip_sprockets? -%> - # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added). + # Precompile additional assets. + # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # config.assets.precompile += %w( search.js ) <%- end -%> @@ -65,9 +66,6 @@ # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false - # Enable threaded mode. - # config.threadsafe! - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation can not be found). config.i18n.fallbacks = true diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt index 3c5611ca59..e5caab3672 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt @@ -7,6 +7,6 @@ # no regular words or you'll be exposed to dictionary attacks. # You can use `rake secret` to generate a secure secret key. -# Make sure your secret_token is kept private +# Make sure your secret_key_base is kept private # if you're sharing your code publicly. -<%= app_const %>.config.secret_token = '<%= app_secret %>' +<%= app_const %>.config.secret_key_base = '<%= app_secret %>' diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt index 4a099a4ce2..df07de9922 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -<%= app_const %>.config.session_store :cookie_store, key: <%= "'_#{app_name}_session'" %> +<%= app_const %>.config.session_store :encrypted_cookie_store, key: <%= "'_#{app_name}_session'" %> diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 4a0bcc35a4..48ce3e86a1 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -225,7 +225,7 @@ task default: :test end def create_test_dummy_files - return if options[:skip_test_unit] && options[:dummy_path] == 'test/dummy' + return unless with_dummy_app? create_dummy_app end @@ -279,6 +279,10 @@ task default: :test options[:mountable] end + def with_dummy_app? + options[:skip_test_unit].blank? || options[:dummy_path] != 'test/dummy' + end + def self.banner "rails plugin new #{self.arguments.map(&:usage).join(' ')} [options]" end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile index 1369140537..65a5bae712 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile @@ -14,7 +14,7 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -<% if full? && !options[:skip_active_record] && !options[:skip_test_unit] -%> +<% if full? && !options[:skip_active_record] && with_dummy_app? -%> APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__) load 'rails/tasks/engine.rake' <% end %> diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb index 8a8ba04a70..310c975262 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb @@ -11,7 +11,7 @@ require "action_mailer/railtie" <%= comment_if :skip_test_unit %>require "rails/test_unit/railtie" <% end -%> -Bundler.require +Bundler.require(*Rails.groups) require "<%= name %>" <%= application_definition %> diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb index 121205b254..a0e5553e44 100644 --- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb +++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb @@ -32,7 +32,7 @@ module Rails # route prepends two spaces onto the front of the string that is passed, this corrects that route route_string[2..-1] end - + private def route_string @route_string ||= "" diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index 3f59bb8733..7be2333981 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -24,8 +24,8 @@ module Rails def call_app(request, env) # Put some space between requests in development logs. if Rails.env.development? - Rails.logger.info '' - Rails.logger.info '' + Rails.logger.debug '' + Rails.logger.debug '' end Rails.logger.info started_request_message(request) diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index cd59fbe599..9ad3a4e6d6 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -88,7 +88,7 @@ namespace :test do def t.file_list if File.directory?(".svn") changed_since_checkin = silence_stderr { `svn status` }.split.map { |path| path.chomp[7 .. -1] } - elsif File.directory?(".git") + elsif system "git rev-parse --git-dir 2>&1 >/dev/null" changed_since_checkin = silence_stderr { `git ls-files --modified --others --exclude-standard` }.split.map { |path| path.chomp } else abort "Not a Subversion or Git checkout." diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 6d28947e83..e39430560f 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -1,4 +1,4 @@ -version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip +version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY @@ -6,24 +6,27 @@ Gem::Specification.new do |s| s.version = version s.summary = 'Tools for creating, working with, and running Rails applications.' s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.' + s.required_ruby_version = '>= 1.9.3' - s.license = 'MIT' - s.author = 'David Heinemeier Hansson' - s.email = 'david@loudthinking.com' - s.homepage = 'http://www.rubyonrails.org' + s.license = 'MIT' + + s.author = 'David Heinemeier Hansson' + s.email = 'david@loudthinking.com' + s.homepage = 'http://www.rubyonrails.org' - s.files = Dir['CHANGELOG.md', 'README.rdoc', 'bin/**/*', 'lib/**/{*,.[a-z]*}'] - s.require_path = 'lib' + s.files = Dir['CHANGELOG.md', 'README.rdoc', 'bin/**/*', 'lib/**/{*,.[a-z]*}'] + s.require_path = 'lib' - s.bindir = 'bin' - s.executables = ['rails'] + s.bindir = 'bin' + s.executables = ['rails'] s.rdoc_options << '--exclude' << '.' - s.add_dependency('rake', '>= 0.8.7') - s.add_dependency('thor', '>= 0.15.4', '< 2.0') - s.add_dependency('rdoc', '~> 3.4') - s.add_dependency('activesupport', version) - s.add_dependency('actionpack', version) + s.add_dependency 'activesupport', version + s.add_dependency 'actionpack', version + + s.add_dependency 'rake', '>= 0.8.7' + s.add_dependency 'thor', '>= 0.15.4', '< 2.0' + s.add_dependency 'rdoc', '~> 3.4' end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index dfcf5aa27d..2ea1d2aff4 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -14,5 +14,6 @@ require 'rails/all' module TestApp class Application < Rails::Application config.root = File.dirname(__FILE__) + config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' end end diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index f86877db34..f98915d1cc 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -83,6 +83,32 @@ module ApplicationTests end end + def test_precompile_does_not_hit_the_database + app_file "app/assets/javascripts/application.js", "alert();" + app_file "app/assets/javascripts/foo/application.js", "alert();" + app_file "app/controllers/user_controller.rb", <<-eoruby + class UserController < ApplicationController; end + eoruby + app_file "app/models/user.rb", <<-eoruby + class User < ActiveRecord::Base; end + eoruby + + ENV['RAILS_ENV'] = 'production' + ENV['DATABASE_URL'] = 'postgresql://baduser:badpass@127.0.0.1/dbname' + + precompile! + + files = Dir["#{app_path}/public/assets/application-*.js"] + files << Dir["#{app_path}/public/assets/foo/application-*.js"].first + files.each do |file| + assert_not_nil file, "Expected application.js asset to be generated, but none found" + assert_equal "alert();".strip, File.read(file).strip + end + ensure + ENV.delete 'RAILS_ENV' + ENV.delete 'DATABASE_URL' + end + test "precompile application.js and application.css and all other non JS/CSS files" do app_file "app/assets/javascripts/application.js", "alert();" app_file "app/assets/stylesheets/application.css", "body{}" diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index c4c1100f19..b9d18f4582 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -225,21 +225,24 @@ module ApplicationTests assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path end - test "config.secret_token is sent in env" do + test "Use key_generator when secret_key_base is set" do make_basic_app do |app| - app.config.secret_token = 'b3c631c314c0bbca50c1b2843150fe33' + app.config.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' app.config.session_store :disabled end class ::OmgController < ActionController::Base def index cookies.signed[:some_key] = "some_value" - render text: env["action_dispatch.secret_token"] + render text: cookies[:some_key] end end get "/" - assert_equal 'b3c631c314c0bbca50c1b2843150fe33', last_response.body + + secret = app.key_generator.generate_key('signed cookie') + verifier = ActiveSupport::MessageVerifier.new(secret) + assert_equal 'some_value', verifier.verify(last_response.body) end test "protect from forgery is the default in a new app" do @@ -568,7 +571,6 @@ module ApplicationTests assert_respond_to app, :env_config assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters - assert_equal app.env_config['action_dispatch.secret_token'], app.config.secret_token assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions assert_equal app.env_config['action_dispatch.logger'], Rails.logger assert_equal app.env_config['action_dispatch.backtrace_cleaner'], Rails.backtrace_cleaner diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 528bec58ef..40d1655c9b 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -219,7 +219,7 @@ module ApplicationTests orig_rails_env, Rails.env = Rails.env, 'development' ActiveRecord::Base.establish_connection assert ActiveRecord::Base.connection - assert_match /#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database] + assert_match(/#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database]) ensure ActiveRecord::Base.remove_connection ENV["DATABASE_URL"] = orig_database_url if orig_database_url @@ -236,7 +236,7 @@ module ApplicationTests ENV["DATABASE_URL"] = "sqlite3://:@localhost/#{database_url_db_name}" ActiveRecord::Base.establish_connection assert ActiveRecord::Base.connection - assert_match /#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database] + assert_match(/#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database]) ensure ActiveRecord::Base.remove_connection ENV["DATABASE_URL"] = orig_database_url if orig_database_url diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb index 9d97bae9ae..fde13eeb94 100644 --- a/railties/test/application/middleware/remote_ip_test.rb +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -1,4 +1,6 @@ require 'isolation/abstract_unit' +# FIXME remove DummyKeyGenerator and this require in 4.1 +require 'active_support/key_generator' module ApplicationTests class RemoteIpTest < ActiveSupport::TestCase @@ -8,7 +10,7 @@ module ApplicationTests remote_ip = nil env = Rack::MockRequest.env_for("/").merge(env).merge!( 'action_dispatch.show_exceptions' => false, - 'action_dispatch.secret_token' => 'b3c631c314c0bbca50c1b2843150fe33' + 'action_dispatch.key_generator' => ActiveSupport::DummyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33') ) endpoint = Proc.new do |e| diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb index 5ce41caf61..a5fdfbf887 100644 --- a/railties/test/application/middleware/session_test.rb +++ b/railties/test/application/middleware/session_test.rb @@ -128,5 +128,164 @@ module ApplicationTests get '/foo/read_cookie' # Cookie shouldn't be changed assert_equal '"1"', last_response.body end + + test "session using encrypted cookie store" do + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + get ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + def write_session + session[:foo] = 1 + render nothing: true + end + + def read_session + render text: session[:foo] + end + + def read_encrypted_cookie + render text: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render text: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + config.session_store :encrypted_cookie_store, key: '_myapp_session' + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_session' + get '/foo/read_session' + assert_equal '1', last_response.body + + get '/foo/read_encrypted_cookie' + assert_equal '1', last_response.body + + secret = app.key_generator.generate_key('encrypted cookie') + sign_secret = app.key_generator.generate_key('signed encrypted cookie') + encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + + get '/foo/read_raw_cookie' + assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo'] + end + + test "session using upgrade signature to encryption cookie store works the same way as encrypted cookie store" do + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + get ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + def write_session + session[:foo] = 1 + render nothing: true + end + + def read_session + render text: session[:foo] + end + + def read_encrypted_cookie + render text: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render text: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + config.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session' + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_session' + get '/foo/read_session' + assert_equal '1', last_response.body + + get '/foo/read_encrypted_cookie' + assert_equal '1', last_response.body + + secret = app.key_generator.generate_key('encrypted cookie') + sign_secret = app.key_generator.generate_key('signed encrypted cookie') + encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + + get '/foo/read_raw_cookie' + assert_equal 1, encryptor.decrypt_and_verify(last_response.body)['foo'] + end + + test "session using upgrade signature to encryption cookie store upgrades session to encrypted mode" do + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + get ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + def write_raw_session + # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} + cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749" + render nothing: true + end + + def write_session + session[:foo] = session[:foo] + 1 + render nothing: true + end + + def read_session + render text: session[:foo] + end + + def read_encrypted_cookie + render text: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render text: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + config.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session' + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_raw_session' + get '/foo/read_session' + assert_equal '1', last_response.body + + get '/foo/write_session' + get '/foo/read_session' + assert_equal '2', last_response.body + + get '/foo/read_encrypted_cookie' + assert_equal '2', last_response.body + + secret = app.key_generator.generate_key('encrypted cookie') + sign_secret = app.key_generator.generate_key('signed encrypted cookie') + encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + + get '/foo/read_raw_cookie' + assert_equal 2, encryptor.decrypt_and_verify(last_response.body)['foo'] + end end end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index c5a68a5152..c6aea03d8c 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -106,6 +106,35 @@ module ApplicationTests end end + def test_rake_test_uncommitted_always_find_git_in_parent_dir + app_name = File.basename(app_path) + app_dir = File.dirname(app_path) + moved_app_name = app_name + '_moved' + moved_app_path = "#{app_path}/#{moved_app_name}" + + Dir.chdir(app_dir) do + # Go from "./app/" to "./app/app_moved" + FileUtils.mv(app_name, moved_app_name) + FileUtils.mkdir(app_name) + FileUtils.mv(moved_app_name, app_name) + # Initialize the git repository and start the test. + Dir.chdir(app_name) do + `git init` + Dir.chdir(moved_app_name){ `rake db:migrate` } + silence_stderr { Dir.chdir(moved_app_name) { `rake test:uncommitted` } } + assert_equal 0, $?.exitstatus + end + end + end + + def test_rake_test_uncommitted_fails_with_no_scm + Dir.chdir(app_path){ `rake db:migrate` } + Dir.chdir(app_path) do + silence_stderr { `rake test:uncommitted` } + assert_equal 1, $?.exitstatus + end + end + def test_rake_routes_calls_the_route_inspector app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb index 2a48adae5c..0905757442 100644 --- a/railties/test/application/url_generation_test.rb +++ b/railties/test/application/url_generation_test.rb @@ -14,7 +14,7 @@ module ApplicationTests require "action_controller/railtie" class MyApp < Rails::Application - config.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4" config.session_store :cookie_store, key: "_myapp_session" config.active_support.deprecation = :log config.eager_load = false diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb index e047d4882d..f99ea13022 100644 --- a/railties/test/commands/console_test.rb +++ b/railties/test/commands/console_test.rb @@ -110,7 +110,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase def start(argv = []) rails_console = Rails::Console.new(app, parse_arguments(argv)) - @output = output = capture(:stdout) { rails_console.start } + @output = capture(:stdout) { rails_console.start } end def app diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb index d45bdaabf5..6316584825 100644 --- a/railties/test/commands/dbconsole_test.rb +++ b/railties/test/commands/dbconsole_test.rb @@ -116,6 +116,14 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase assert !aborted end + def test_sqlite3_db_without_defined_rails_root + Rails.stubs(:respond_to?) + Rails.expects(:respond_to?).with(:root).once.returns(false) + dbconsole.expects(:find_cmd_and_exec).with('sqlite3', Rails.root.join('../config/db.sqlite3').to_s) + start(adapter: 'sqlite3', database: 'config/db.sqlite3') + assert !aborted + end + def test_oracle dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user@db') start(adapter: 'oracle', database: 'db', username: 'user', password: 'secret') diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 62e1f3531e..5ea31f2e0f 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -341,7 +341,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_new_hash_style run_generator [destination_root] assert_file "config/initializers/session_store.rb" do |file| - assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file) + assert_match(/config.session_store :encrypted_cookie_store, key: '_.+_session'/, file) end end diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 4b168ae110..9e7626647e 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -244,13 +244,9 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase /module TestApp\n class ProductLinesControllerTest < ActionController::TestCase/ # Views - %w( - index - edit - new - show - _form - ).each { |view| assert_file "app/views/test_app/product_lines/#{view}.html.erb" } + %w(index edit new show _form).each do |view| + assert_file "app/views/test_app/product_lines/#{view}.html.erb" + end assert_no_file "app/views/layouts/test_app/product_lines.html.erb" # Helpers @@ -316,13 +312,9 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase /module TestApp\n class Admin::RolesControllerTest < ActionController::TestCase/ # Views - %w( - index - edit - new - show - _form - ).each { |view| assert_file "app/views/test_app/admin/roles/#{view}.html.erb" } + %w(index edit new show _form).each do |view| + assert_file "app/views/test_app/admin/roles/#{view}.html.erb" + end assert_no_file "app/views/layouts/admin/roles.html.erb" # Helpers @@ -389,13 +381,9 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase /module TestApp\n class Admin::User::Special::RolesControllerTest < ActionController::TestCase/ # Views - %w( - index - edit - new - show - _form - ).each { |view| assert_file "app/views/test_app/admin/user/special/roles/#{view}.html.erb" } + %w(index edit new show _form).each do |view| + assert_file "app/views/test_app/admin/user/special/roles/#{view}.html.erb" + end assert_no_file "app/views/layouts/admin/user/special/roles.html.erb" # Helpers diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 6974db5751..ab78800a4e 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -66,6 +66,12 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_no_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile"))) end + def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files + run_generator [destination_root, "-T", "--mountable", '--dummy-path', 'my_dummy_app'] + + assert_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile"))) + end + def test_ensure_that_plugin_options_are_not_passed_to_app_generator FileUtils.cd(Rails.root) assert_no_match(/It works from file!.*It works_from_file/, run_generator([destination_root, "-m", "lib/template.rb"])) diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 38454dfb8b..8cacca668f 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -63,12 +63,9 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase def test_views_are_generated run_generator - %w( - index - edit - new - show - ).each { |view| assert_file "app/views/users/#{view}.html.erb" } + %w(index edit new show).each do |view| + assert_file "app/views/users/#{view}.html.erb" + end assert_no_file "app/views/layouts/users.html.erb" end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index efe47cdfcb..54d5a9db6f 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -67,13 +67,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end # Views - %w( - index - edit - new - show - _form - ).each { |view| assert_file "app/views/product_lines/#{view}.html.erb" } + %w(index edit new show _form).each do |view| + assert_file "app/views/product_lines/#{view}.html.erb" + end assert_no_file "app/views/layouts/product_lines.html.erb" # Helpers @@ -187,13 +183,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase /class Admin::RolesControllerTest < ActionController::TestCase/ # Views - %w( - index - edit - new - show - _form - ).each { |view| assert_file "app/views/admin/roles/#{view}.html.erb" } + %w(index edit new show _form).each do |view| + assert_file "app/views/admin/roles/#{view}.html.erb" + end assert_no_file "app/views/layouts/admin/roles.html.erb" # Helpers diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index e59488f97d..0cb65f8e0d 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -119,7 +119,7 @@ module TestHelpers add_to_config <<-RUBY config.eager_load = false - config.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4" config.session_store :cookie_store, key: "_myapp_session" config.active_support.deprecation = :log config.action_controller.allow_forgery_protection = false @@ -138,7 +138,7 @@ module TestHelpers app = Class.new(Rails::Application) app.config.eager_load = false - app.config.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + app.config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4" app.config.session_store :cookie_store, key: "_myapp_session" app.config.active_support.deprecation = :log |