diff options
43 files changed, 742 insertions, 214 deletions
@@ -7,15 +7,13 @@ gemspec # ensure correct loading order gem 'mocha', '~> 0.14', require: false -gem 'rack', github: 'rack/rack', branch: 'master' gem 'rack-cache', '~> 1.2' gem 'jquery-rails', '~> 3.1.0' -gem 'turbolinks', github: 'rails/turbolinks', branch: 'master' gem 'coffee-rails', '~> 4.0.0' -gem 'rails-html-sanitizer', github: 'rails/rails-html-sanitizer' -gem 'rails-deprecated_sanitizer', github: 'rails/rails-deprecated_sanitizer' -#temporary gem until a new version of loofah is released -gem 'loofah', github: 'kaspth/loofah', branch: 'single-scrub' +gem 'rails-html-sanitizer' + +# TODO: remove this before the 4.2.0.beta1 release +gem 'turbolinks', github: 'rails/turbolinks', branch: 'master' gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: 'master' # require: false so bcrypt is loaded only when has_secure_password is used. @@ -38,7 +36,6 @@ end gem 'dalli', '>= 2.2.1' # ActiveJob -gem 'globalid', github: 'rails/globalid' gem 'resque', require: false gem 'resque-scheduler', require: false gem 'sidekiq', require: false diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index 8452348e11..bc72c20d87 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -23,5 +23,5 @@ Gem::Specification.new do |s| s.add_dependency 'actionview', version s.add_dependency 'mail', ['~> 2.5', '>= 2.5.4'] - s.add_dependency 'rails-dom-testing' + s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.2' end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 1c84bac3ff..e2731d0ee5 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,38 @@ +* `ActionController::Parameters` will stop inheriting from `Hash` and + `HashWithIndifferentAccess` in the next major release. If you use any method + that is not available on `ActionController::Parameters` you should consider + calling `#to_h` to convert it to a `Hash` first before calling that method. + + *Prem Sichanugrist* + +* `ActionController::Parameters#to_h` now returns a `Hash` with unpermitted + keys removed. This change is to reflect on a security concern where some + method performed on an `ActionController::Parameters` may yield a `Hash` + object which does not maintain `permitted?` status. If you would like to + get a `Hash` with all the keys intact, duplicate and mark it as permitted + before calling `#to_h`. + + params = ActionController::Parameters.new({ + name: 'Senjougahara Hitagi', + oddity: 'Heavy stone crab' + }) + params.to_h + # => {} + + unsafe_params = params.dup.permit! + unsafe_params.to_h + # => {"name"=>"Senjougahara Hitagi", "oddity"=>"Heavy stone crab"} + + safe_params = params.permit(:name) + safe_params.to_h + # => {"name"=>"Senjougahara Hitagi"} + + This change is consider a stopgap as we cannot change the code to stop + `ActionController::Parameters` to inherit from `HashWithIndifferentAccess` + in the next minor release. + + *Prem Sichanugrist* + * Deprecated TagAssertions. *Kasper Timm Hansen* diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 722e874c7e..a39b3e86d4 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -23,7 +23,8 @@ Gem::Specification.new do |s| s.add_dependency 'rack', '~> 1.6.0.beta' s.add_dependency 'rack-test', '~> 0.6.2' - s.add_dependency 'rails-deprecated_sanitizer' + s.add_dependency 'rails-deprecated_sanitizer', '~> 1.0', '>= 1.0.2' + s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.2' s.add_dependency 'actionview', version s.add_development_dependency 'activemodel', version diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 0efa0fb259..7afbd767ce 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -9,7 +9,7 @@ module ActionController #:nodoc: end # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks - # by including a token in the rendered html for your application. This token is + # by including a token in the rendered HTML for your application. This token is # stored as a random string in the session, to which an attacker does not have # access. When a request reaches your application, \Rails verifies the received # token with the token in the session. Only HTML and JavaScript requests are checked, @@ -44,7 +44,7 @@ module ActionController #:nodoc: # # The token parameter is named <tt>authenticity_token</tt> by default. The name and # value of this token must be added to every layout that renders forms by including - # <tt>csrf_meta_tags</tt> in the html +head+. + # <tt>csrf_meta_tags</tt> in the HTML +head+. # # Learn more about CSRF attacks and securing your application in the # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html]. diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index bc27ecaa20..7038f0997f 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -141,6 +141,37 @@ module ActionController @permitted = self.class.permit_all_parameters end + # Returns a safe +Hash+ representation of this parameter with all + # unpermitted keys removed. + # + # params = ActionController::Parameters.new({ + # name: 'Senjougahara Hitagi', + # oddity: 'Heavy stone crab' + # }) + # params.to_h # => {} + # + # safe_params = params.permit(:name) + # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} + def to_h + if permitted? + to_hash + else + slice(*self.class.always_permitted_parameters).permit!.to_h + end + end + + # Convert all hashes in values into parameters, then yield each pair like + # the same way as <tt>Hash#each_pair</tt> + def each_pair(&block) + super do |key, value| + convert_hashes_to_parameters(key, value) + end + + super + end + + alias_method :each, :each_pair + # Attribute that keeps track of converted arrays, if any, to avoid double # looping in the common use case permit + mass-assignment. Defined in a # method to instantiate it only if needed. @@ -176,7 +207,6 @@ module ActionController # Person.new(params) # => #<Person id: nil, name: "Francesco"> def permit! each_pair do |key, value| - value = convert_hashes_to_parameters(key, value) Array.wrap(value).each do |v| v.permit! if v.respond_to? :permit! end @@ -331,11 +361,56 @@ module ActionController # params.slice(:a, :b) # => {"a"=>1, "b"=>2} # params.slice(:d) # => {} def slice(*keys) - self.class.new(super).tap do |new_instance| - new_instance.permitted = @permitted + new_instance_with_inherited_permitted_status(super) + end + + # Removes and returns the key/value pairs matching the given keys. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.extract!(:a, :b) # => {"a"=>1, "b"=>2} + # params # => {"c"=>3} + def extract!(*keys) + new_instance_with_inherited_permitted_status(super) + end + + # Returns a new <tt>ActionController::Parameters</tt> with the results of + # running +block+ once for every value. The keys are unchanged. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.transform_values { |x| x * 2 } + # # => {"a"=>2, "b"=>4, "c"=>6} + def transform_values + if block_given? + new_instance_with_inherited_permitted_status(super) + else + super + end + end + + # This method is here only to make sure that the returned object has the + # correct +permitted+ status. It should not matter since the parent of + # this object is +HashWithIndifferentAccess+ + def transform_keys # :nodoc: + if block_given? + new_instance_with_inherited_permitted_status(super) + else + super end end + # Deletes and returns a key-value pair from +Parameters+ whose key is equal + # to key. If the key is not found, returns the default value. If the + # optional code block is given and the key is not found, pass in the key + # and return the result of block. + def delete(key, &block) + convert_hashes_to_parameters(key, super, false) + end + + # Equivalent to Hash#keep_if, but returns nil if no changes were made. + def select!(&block) + convert_value_to_parameters(super) + end + # Returns an exact copy of the <tt>ActionController::Parameters</tt> # instance. +permitted+ state is kept on the duped object. # @@ -356,6 +431,12 @@ module ActionController end private + def new_instance_with_inherited_permitted_status(hash) + self.class.new(hash).tap do |new_instance| + new_instance.permitted = @permitted + end + end + def convert_hashes_to_parameters(key, value, assign_if_converted=true) converted = convert_value_to_parameters(value) self[key] = converted if assign_if_converted && !converted.equal?(value) diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 8c035c3c6c..f35289253b 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -292,7 +292,7 @@ module ActionDispatch # Override Rack's GET method to support indifferent access def GET @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {})) - rescue TypeError => e + rescue TypeError, Rack::Utils::InvalidParameterError => e raise ActionController::BadRequest.new(:query, e) end alias :query_parameters :GET @@ -300,7 +300,7 @@ module ActionDispatch # Override Rack's POST method to support indifferent access def POST @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {})) - rescue TypeError => e + rescue TypeError, Rack::Utils::InvalidParameterError => e raise ActionController::BadRequest.new(:request, e) end alias :request_parameters :POST diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index 6c8944e067..040cb215b7 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -1,4 +1,14 @@ module ActionDispatch + # When called, this middleware renders an error page. By default if an HTML + # response is expected it will render static error pages from the `/public` + # directory. For example when this middleware receives a 500 response it will + # render the template found in `/public/500.html`. + # If an internationalized locale is set, this middleware will attempt to render + # the template in `/public/500.<locale>.html`. If an internationalized template + # is not found it will fall back on `/public/500.html`. + # + # When a request with a content type other than HTML is made, this middleware + # will attempt to convert error information into the appropriate response type. class PublicExceptions attr_accessor :public_path diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 2764584fe9..25e32cdef8 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -2,6 +2,16 @@ require 'rack/utils' require 'active_support/core_ext/uri' module ActionDispatch + # This middleware returns a file's contents from disk in the body response. + # When initialized it can accept an optional 'Cache-Control' header which + # will be set when a response containing a file's contents is delivered. + # + # This middleware will render the file specified in `env["PATH_INFO"]` + # where the base path is in the +root+ directory. For example if the +root+ + # is set to `public/` then a request with `env["PATH_INFO"]` of + # `assets/application.js` will return a response with contents of a file + # located at `public/assets/application.js` if the file exists. If the file + # does not exist a 404 "File not Found" response will be returned. class FileHandler def initialize(root, cache_control) @root = root.chomp('/') @@ -45,6 +55,15 @@ module ActionDispatch end end + # This middleware will attempt to return the contents of a file's body from + # disk in the response. If a file is not found on disk, the request will be + # delegated to the application stack. This middleware is commonly initialized + # to serve assets from a server's `public/` directory. + # + # This middleware verifies the path to ensure that only files + # living in the root directory can be rendered. A request cannot + # produce a directory traversal using this middleware. Only 'GET' and 'HEAD' + # requests will result in a file being returned. class Static def initialize(app, path, cache_control=nil) @app = app diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb new file mode 100644 index 0000000000..97875c3cbb --- /dev/null +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -0,0 +1,125 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' +require 'active_support/core_ext/hash/transform_values' + +class ParametersAccessorsTest < ActiveSupport::TestCase + setup do + @params = ActionController::Parameters.new( + person: { + age: '32', + name: { + first: 'David', + last: 'Heinemeier Hansson' + }, + addresses: [{city: 'Chicago', state: 'Illinois'}] + } + ) + end + + test "[] retains permitted status" do + @params.permit! + assert @params[:person].permitted? + assert @params[:person][:name].permitted? + end + + test "[] retains unpermitted status" do + assert_not @params[:person].permitted? + assert_not @params[:person][:name].permitted? + end + + test "each carries permitted status" do + @params.permit! + @params.each { |key, value| assert(value.permitted?) if key == "person" } + end + + test "each carries unpermitted status" do + @params.each { |key, value| assert_not(value.permitted?) if key == "person" } + end + + test "each_pair carries permitted status" do + @params.permit! + @params.each_pair { |key, value| assert(value.permitted?) if key == "person" } + end + + test "each_pair carries unpermitted status" do + @params.each_pair { |key, value| assert_not(value.permitted?) if key == "person" } + end + + test "except retains permitted status" do + @params.permit! + assert @params.except(:person).permitted? + assert @params[:person].except(:name).permitted? + end + + test "except retains unpermitted status" do + assert_not @params.except(:person).permitted? + assert_not @params[:person].except(:name).permitted? + end + + test "fetch retains permitted status" do + @params.permit! + assert @params.fetch(:person).permitted? + assert @params[:person].fetch(:name).permitted? + end + + test "fetch retains unpermitted status" do + assert_not @params.fetch(:person).permitted? + assert_not @params[:person].fetch(:name).permitted? + end + + test "reject retains permitted status" do + assert_not @params.reject { |k| k == "person" }.permitted? + end + + test "reject retains unpermitted status" do + @params.permit! + assert @params.reject { |k| k == "person" }.permitted? + end + + test "select retains permitted status" do + @params.permit! + assert @params.select { |k| k == "person" }.permitted? + end + + test "select retains unpermitted status" do + assert_not @params.select { |k| k == "person" }.permitted? + end + + test "slice retains permitted status" do + @params.permit! + assert @params.slice(:person).permitted? + end + + test "slice retains unpermitted status" do + assert_not @params.slice(:person).permitted? + end + + test "transform_keys retains permitted status" do + @params.permit! + assert @params.transform_keys { |k| k }.permitted? + end + + test "transform_keys retains unpermitted status" do + assert_not @params.transform_keys { |k| k }.permitted? + end + + test "transform_values retains permitted status" do + @params.permit! + assert @params.transform_values { |v| v }.permitted? + end + + test "transform_values retains unpermitted status" do + assert_not @params.transform_values { |v| v }.permitted? + end + + test "values_at retains permitted status" do + @params.permit! + assert @params.values_at(:person).first.permitted? + assert @params[:person].values_at(:name).first.permitted? + end + + test "values_at retains unpermitted status" do + assert_not @params.values_at(:person).first.permitted? + assert_not @params[:person].values_at(:name).first.permitted? + end +end diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb new file mode 100644 index 0000000000..744d8664be --- /dev/null +++ b/actionpack/test/controller/parameters/mutators_test.rb @@ -0,0 +1,99 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' +require 'active_support/core_ext/hash/transform_values' + +class ParametersMutatorsTest < ActiveSupport::TestCase + setup do + @params = ActionController::Parameters.new( + person: { + age: '32', + name: { + first: 'David', + last: 'Heinemeier Hansson' + }, + addresses: [{city: 'Chicago', state: 'Illinois'}] + } + ) + end + + test "delete retains permitted status" do + @params.permit! + assert @params.delete(:person).permitted? + end + + test "delete retains unpermitted status" do + assert_not @params.delete(:person).permitted? + end + + test "delete_if retains permitted status" do + @params.permit! + assert @params.delete_if { |k| k == "person" }.permitted? + end + + test "delete_if retains unpermitted status" do + assert_not @params.delete_if { |k| k == "person" }.permitted? + end + + test "extract! retains permitted status" do + @params.permit! + assert @params.extract!(:person).permitted? + end + + test "extract! retains unpermitted status" do + assert_not @params.extract!(:person).permitted? + end + + test "keep_if retains permitted status" do + @params.permit! + assert @params.keep_if { |k,v| k == "person" }.permitted? + end + + test "keep_if retains unpermitted status" do + assert_not @params.keep_if { |k,v| k == "person" }.permitted? + end + + test "reject! retains permitted status" do + @params.permit! + assert @params.reject! { |k| k == "person" }.permitted? + end + + test "reject! retains unpermitted status" do + assert_not @params.reject! { |k| k == "person" }.permitted? + end + + test "select! retains permitted status" do + @params.permit! + assert @params.select! { |k| k != "person" }.permitted? + end + + test "select! retains unpermitted status" do + assert_not @params.select! { |k| k != "person" }.permitted? + end + + test "slice! retains permitted status" do + @params.permit! + assert @params.slice!(:person).permitted? + end + + test "slice! retains unpermitted status" do + assert_not @params.slice!(:person).permitted? + end + + test "transform_keys! retains permitted status" do + @params.permit! + assert @params.transform_keys! { |k| k }.permitted? + end + + test "transform_keys! retains unpermitted status" do + assert_not @params.transform_keys! { |k| k }.permitted? + end + + test "transform_values! retains permitted status" do + @params.permit! + assert @params.transform_values! { |v| v }.permitted? + end + + test "transform_values! retains unpermitted status" do + assert_not @params.transform_values! { |v| v }.permitted? + end +end diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index aa894ffa17..ba98ad7605 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -194,42 +194,6 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal "monkey", @params.fetch(:foo) { "monkey" } end - 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" } - - 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 - @params.permit! - assert @params.delete_if { |k| k == "person" }.permitted? - assert @params.keep_if { |k,v| k == "person" }.permitted? - end - test "not permitted is sticky beyond merges" do assert !@params.merge(a: "b").permitted? end @@ -277,4 +241,43 @@ class ParametersPermitTest < ActiveSupport::TestCase test "permitting parameters as an array" do assert_equal "32", @params[:person].permit([ :age ])[:age] end + + test "to_h returns empty hash on unpermitted params" do + assert @params.to_h.is_a? Hash + assert_not @params.to_h.is_a? ActionController::Parameters + assert @params.to_h.empty? + end + + test "to_h returns converted hash on permitted params" do + @params.permit! + + assert @params.to_h.is_a? Hash + assert_not @params.to_h.is_a? ActionController::Parameters + assert_equal @params.to_hash, @params.to_h + end + + test "to_h returns converted hash when .permit_all_parameters is set" do + begin + ActionController::Parameters.permit_all_parameters = true + params = ActionController::Parameters.new(crab: "Senjougahara Hitagi") + + assert params.to_h.is_a? Hash + assert_not @params.to_h.is_a? ActionController::Parameters + assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_h) + ensure + ActionController::Parameters.permit_all_parameters = false + end + end + + test "to_h returns always permitted parameter on unpermitted params" do + params = ActionController::Parameters.new( + controller: "users", + action: "create", + user: { + name: "Sengoku Nadeko" + } + ) + + assert_equal({ "controller" => "users", "action" => "create" }, params.to_h) + end end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index fe9ee6f73d..84bd392fd9 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -909,6 +909,31 @@ class RequestParameters < BaseRequestTest end end + test "parameters not accessible after rack parse error of invalid UTF8 character" do + request = stub_request("QUERY_STRING" => "foo%81E=1") + + 2.times do + assert_raises(ActionController::BadRequest) do + # rack will raise a Rack::Utils::InvalidParameterError when parsing this query string + request.parameters + end + end + end + + test "parameters not accessible after rack parse error 1" do + request = stub_request( + 'REQUEST_METHOD' => 'POST', + 'CONTENT_LENGTH' => "a%=".length, + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8', + 'rack.input' => StringIO.new("a%=") + ) + + assert_raises(ActionController::BadRequest) do + # rack will raise a TypeError when parsing this query string + request.parameters + end + end + test "we have access to the original exception" do request = stub_request("QUERY_STRING" => "x[y]=1&x[y][][w]=2") diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index 1ea00cff22..565c22e1e8 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -23,7 +23,8 @@ Gem::Specification.new do |s| s.add_dependency 'builder', '~> 3.1' s.add_dependency 'erubis', '~> 2.7.0' - s.add_dependency 'rails-deprecated_sanitizer' + s.add_dependency 'rails-deprecated_sanitizer', '~> 1.0', '>= 1.0.2' + s.add_dependency 'rails-dom-testing', '~> 1.0', '>= 1.0.2' s.add_development_dependency 'actionpack', version s.add_development_dependency 'activemodel', version diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index 669050e7a7..b7fdc16a9d 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -218,7 +218,7 @@ module ActionView tag("img", options) end - # Returns a string suitable for an html image tag alt attribute. + # Returns a string suitable for an HTML image tag alt attribute. # The +src+ argument is meant to be an image file path. # The method removes the basename of the file path and the digest, # if any. It also removes hyphens and underscores from file names and @@ -239,7 +239,7 @@ module ActionView File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize end - # Returns an html video tag for the +sources+. If +sources+ is a string, + # Returns an HTML video tag for the +sources+. If +sources+ is a string, # a single video tag will be returned. If +sources+ is an array, a video # tag with nested source tags for each source will be returned. The # +sources+ can be full paths or files that exists in your public videos diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 27c7a26098..9272bb5c10 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -330,7 +330,7 @@ module ActionView Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render end - # Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the + # Returns a set of HTML select-tags (one for year, month, day, hour, minute, and second) pre-selected with the # +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with # an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not # supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add @@ -379,7 +379,7 @@ module ActionView DateTimeSelector.new(datetime, options, html_options).select_datetime end - # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+. + # Returns a set of HTML select-tags (one for year, month, and day) pre-selected with the +date+. # It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. # If the array passed to the <tt>:order</tt> option does not contain all the three symbols, all tags will be hidden. @@ -418,7 +418,7 @@ module ActionView DateTimeSelector.new(date, options, html_options).select_date end - # Returns a set of html select-tags (one for hour and minute). + # Returns a set of HTML select-tags (one for hour and minute). # You can set <tt>:time_separator</tt> key to format the output, and # the <tt>:include_seconds</tt> option to include an input for seconds. # @@ -635,7 +635,7 @@ module ActionView DateTimeSelector.new(date, options, html_options).select_year end - # Returns an html time tag for the given date or time. + # Returns an HTML time tag for the given date or time. # # time_tag Date.today # => # <time datetime="2010-11-04">November 04, 2010</time> @@ -914,7 +914,7 @@ module ActionView build_select(type, build_options(selected, options)) end - # Build select option html from date value and options. + # Build select option HTML from date value and options. # build_options(15, start: 1, end: 31) # => "<option value="1">1</option> # <option value="2">2</option> @@ -954,7 +954,7 @@ module ActionView (select_options.join("\n") + "\n").html_safe end - # Builds select tag from date type and html select options. + # Builds select tag from date type and HTML select options. # build_select(:month, "<option value="1">January</option>...") # => "<select id="post_written_on_2i" name="post[written_on(2i)]"> # <option value="1">January</option>... diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 0582cb3e12..09843ca70d 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -142,7 +142,7 @@ module ActionView # will get expanded to # # <%= text_field :person, :first_name %> - # which results in an html <tt><input></tt> tag whose +name+ attribute is + # which results in an HTML <tt><input></tt> tag whose +name+ attribute is # <tt>person[first_name]</tt>. This means that when the form is submitted, # the value entered by the user will be available in the controller as # <tt>params[:person][:first_name]</tt>. diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index 8ade7c6a74..83b07a00d4 100644 --- a/actionview/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -314,7 +314,7 @@ module ActionView # # => <option>MasterCard</option> # # => <option selected="selected">Discover</option> # - # You can optionally provide html attributes as the last element of the array. + # You can optionally provide HTML attributes as the last element of the array. # # options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"]) # # => <option value="Denmark">Denmark</option> @@ -633,7 +633,7 @@ module ActionView # even use the label as wrapper, as in the example above. # # The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept - # extra html options: + # extra HTML options: # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b| # b.label(class: "radio_button") { b.radio_button(class: "radio_button") } # end @@ -696,7 +696,7 @@ module ActionView # use the label as wrapper, as in the example above. # # The builder methods <tt>label</tt> and <tt>check_box</tt> also accept - # extra html options: + # extra HTML options: # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b| # b.label(class: "check_box") { b.check_box(class: "check_box") } # end diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb index f03362d0f5..1c2a400245 100644 --- a/actionview/lib/action_view/helpers/output_safety_helper.rb +++ b/actionview/lib/action_view/helpers/output_safety_helper.rb @@ -17,10 +17,10 @@ module ActionView #:nodoc: stringish.to_s.html_safe end - # This method returns an html safe string similar to what <tt>Array#join</tt> + # This method returns an HTML safe string similar to what <tt>Array#join</tt> # would return. The array is flattened, and all items, including - # the supplied separator, are html escaped unless they are html - # safe, and the returned string is marked as html safe. + # the supplied separator, are HTML escaped unless they are HTML + # safe, and the returned string is marked as HTML safe. # # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />") # # => "<p>foo</p><br /><p>bar</p>" diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index 6cd6e858dd..e11670e00d 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -14,8 +14,8 @@ module ActionView # * <tt>:text</tt> - Renders the text passed in out. # * <tt>:plain</tt> - Renders the text passed in out. Setting the content # type as <tt>text/plain</tt>. - # * <tt>:html</tt> - Renders the html safe string passed in out, otherwise - # performs html escape on the string first. Setting the content type as + # * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise + # performs HTML escape on the string first. Setting the content type as # <tt>text/html</tt>. # * <tt>:body</tt> - Renders the text passed in, and inherits the content # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt> diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 153c64d691..dfbc52e3ac 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -9,7 +9,7 @@ module ActionView # These helper methods extend Action View making them callable within your template files. module SanitizeHelper extend ActiveSupport::Concern - # This +sanitize+ helper will html encode all tags and strip all attributes that + # This +sanitize+ helper will HTML encode all tags and strip all attributes that # aren't specifically allowed. # # It also strips href/src tags with invalid protocols, like javascript: especially. diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec index d609bb8fce..c74daa5045 100644 --- a/activejob/activejob.gemspec +++ b/activejob/activejob.gemspec @@ -18,5 +18,5 @@ Gem::Specification.new do |s| s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.md', 'lib/**/*'] s.require_path = 'lib' - s.add_dependency 'globalid' + s.add_dependency 'globalid', '>= 0.2.3' end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index d3b9b8251a..945f22d3c8 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1366,6 +1366,17 @@ module ActiveRecord # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>) # The declaration can also include an +options+ hash to specialize the behavior of the association. # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific record or customize the generated query + # when you access the associated object. + # + # Scope examples: + # belongs_to :user, -> { where(id: 2) } + # belongs_to :user, -> { joins(:friends) } + # belongs_to :level, ->(level) { where("game_level > ?", level.current) } + # # === Options # # [:class_name] 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 9e07e9a5c4..92ac607a3c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -2,6 +2,7 @@ require 'date' require 'set' require 'bigdecimal' require 'bigdecimal/util' +require 'active_support/core_ext/string/strip' module ActiveRecord module ConnectionAdapters #:nodoc: diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index a6847e28c2..659c5e3bbb 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -836,21 +836,20 @@ module ActiveRecord SchemaMigration.table_name end - def get_all_versions - SchemaMigration.all.map { |x| x.version.to_i }.sort + def get_all_versions(connection = Base.connection) + if connection.table_exists?(schema_migrations_table_name) + SchemaMigration.all.map { |x| x.version.to_i }.sort + else + [] + end end def current_version(connection = Base.connection) - sm_table = schema_migrations_table_name - if connection.table_exists?(sm_table) - get_all_versions.max || 0 - else - 0 - end + get_all_versions(connection).max || 0 end def needs_migration?(connection = Base.connection) - current_version(connection) < last_version + (migrations(migrations_paths).collect(&:version) - get_all_versions(connection)).size > 0 end def last_version diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 11338e1fb6..f9d1edc340 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -81,6 +81,21 @@ class MigrationTest < ActiveRecord::TestCase assert_equal 0, ActiveRecord::Migrator.current_version assert_equal 3, ActiveRecord::Migrator.last_version assert_equal true, ActiveRecord::Migrator.needs_migration? + + ActiveRecord::SchemaMigration.create!(:version => ActiveRecord::Migrator.last_version) + assert_equal true, ActiveRecord::Migrator.needs_migration? + ensure + ActiveRecord::Migrator.migrations_paths = old_path + end + + def test_migration_detection_without_schema_migration_table + ActiveRecord::Base.connection.drop_table :schema_migrations + + migrations_path = MIGRATIONS_ROOT + "/valid" + old_path = ActiveRecord::Migrator.migrations_paths + ActiveRecord::Migrator.migrations_paths = migrations_path + + assert_equal true, ActiveRecord::Migrator.needs_migration? ensure ActiveRecord::Migrator.migrations_paths = old_path end diff --git a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb index 901c2e05a8..c55600a02d 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb @@ -28,6 +28,9 @@ module ActiveSupport LocalCacheRegistry.set_cache_for(local_cache_key, nil) end response + rescue Rack::Utils::InvalidParameterError + LocalCacheRegistry.set_cache_for(local_cache_key, nil) + [400, {}, []] rescue Exception LocalCacheRegistry.set_cache_for(local_cache_key, nil) raise diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index b74bc9bf20..c93192f207 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -1,6 +1,4 @@ require 'abstract_unit' -require 'active_support/core_ext/date' -require 'active_support/core_ext/numeric/time' class AssertDifferenceTest < ActiveSupport::TestCase def setup @@ -174,72 +172,3 @@ class TestCaseTaggedLoggingTest < ActiveSupport::TestCase assert_match "#{self.class}: #{name}\n", @out.string end end - -class TimeHelperTest < ActiveSupport::TestCase - setup do - Time.stubs now: Time.now - end - - teardown do - travel_back - end - - def test_time_helper_travel - expected_time = Time.now + 1.day - travel 1.day - - assert_equal expected_time.to_s(:db), Time.now.to_s(:db) - assert_equal expected_time.to_date, Date.today - end - - def test_time_helper_travel_with_block - expected_time = Time.now + 1.day - - travel 1.day do - assert_equal expected_time.to_s(:db), Time.now.to_s(:db) - assert_equal expected_time.to_date, Date.today - end - - assert_not_equal expected_time.to_s(:db), Time.now.to_s(:db) - assert_not_equal expected_time.to_date, Date.today - end - - def test_time_helper_travel_to - expected_time = Time.new(2004, 11, 24, 01, 04, 44) - travel_to expected_time - - assert_equal expected_time, Time.now - assert_equal Date.new(2004, 11, 24), Date.today - end - - def test_time_helper_travel_to_with_block - expected_time = Time.new(2004, 11, 24, 01, 04, 44) - - travel_to expected_time do - assert_equal expected_time, Time.now - assert_equal Date.new(2004, 11, 24), Date.today - end - - assert_not_equal expected_time, Time.now - assert_not_equal Date.new(2004, 11, 24), Date.today - end - - def test_time_helper_travel_back - expected_time = Time.new(2004, 11, 24, 01, 04, 44) - - travel_to expected_time - assert_equal expected_time, Time.now - assert_equal Date.new(2004, 11, 24), Date.today - travel_back - - assert_not_equal expected_time, Time.now - assert_not_equal Date.new(2004, 11, 24), Date.today - end - - def test_travel_to_will_reset_the_usec_to_avoid_mysql_rouding - travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do - assert_equal 50, Time.now.sec - assert_equal 0, Time.now.usec - end - end -end diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb new file mode 100644 index 0000000000..065539671d --- /dev/null +++ b/activesupport/test/time_travel_test.rb @@ -0,0 +1,72 @@ +require 'abstract_unit' +require 'active_support/core_ext/date' +require 'active_support/core_ext/numeric/time' + +class TimeTravelTest < ActiveSupport::TestCase + setup do + Time.stubs now: Time.now + end + + teardown do + travel_back + end + + def test_time_helper_travel + expected_time = Time.now + 1.day + travel 1.day + + assert_equal expected_time.to_s(:db), Time.now.to_s(:db) + assert_equal expected_time.to_date, Date.today + end + + def test_time_helper_travel_with_block + expected_time = Time.now + 1.day + + travel 1.day do + assert_equal expected_time.to_s(:db), Time.now.to_s(:db) + assert_equal expected_time.to_date, Date.today + end + + assert_not_equal expected_time.to_s(:db), Time.now.to_s(:db) + assert_not_equal expected_time.to_date, Date.today + end + + def test_time_helper_travel_to + expected_time = Time.new(2004, 11, 24, 01, 04, 44) + travel_to expected_time + + assert_equal expected_time, Time.now + assert_equal Date.new(2004, 11, 24), Date.today + end + + def test_time_helper_travel_to_with_block + expected_time = Time.new(2004, 11, 24, 01, 04, 44) + + travel_to expected_time do + assert_equal expected_time, Time.now + assert_equal Date.new(2004, 11, 24), Date.today + end + + assert_not_equal expected_time, Time.now + assert_not_equal Date.new(2004, 11, 24), Date.today + end + + def test_time_helper_travel_back + expected_time = Time.new(2004, 11, 24, 01, 04, 44) + + travel_to expected_time + assert_equal expected_time, Time.now + assert_equal Date.new(2004, 11, 24), Date.today + travel_back + + assert_not_equal expected_time, Time.now + assert_not_equal Date.new(2004, 11, 24), Date.today + end + + def test_travel_to_will_reset_the_usec_to_avoid_mysql_rouding + travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do + assert_equal 50, Time.now.sec + assert_equal 0, Time.now.usec + end + end +end diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb index 2eb7ca17a3..688f177578 100644 --- a/guides/rails_guides/markdown/renderer.rb +++ b/guides/rails_guides/markdown/renderer.rb @@ -50,7 +50,7 @@ HTML when 'erb' 'ruby; html-script: true' when 'html' - 'xml' # html is understood, but there are .xml rules in the CSS + 'xml' # HTML is understood, but there are .xml rules in the CSS else 'plain' end diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index f710b8bac9..b90e5025e2 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -78,22 +78,24 @@ Please refer to the [Changelog][railties] for detailed changes. * Introduced an `after_bundle` callback for use in Rails templates. ([Pull Request](https://github.com/rails/rails/pull/16359)) -* Introduced the `x` namespace for defining custom configuration options: +* Custom configuration options can be chained: ```ruby # config/environments/production.rb - config.x.payment_processing.schedule = :daily - config.x.payment_processing.retries = 3 - config.x.super_debugger = true + config.payment_processing.schedule = :daily + config.payment_processing.retries = 3 + config.resque = { timeout: 60, inline_jobs: :always } + config.super_debugger = true ``` These options are then available through the configuration object: ```ruby - Rails.configuration.x.payment_processing.schedule # => :daily - Rails.configuration.x.payment_processing.retries # => 3 - Rails.configuration.x.super_debugger # => true - Rails.configuration.x.super_debugger.not_set # => nil + Rails.configuration.payment_processing.schedule # => :daily + Rails.configuration.payment_processing.retries # => 3 + Rails.configuration.resque.timeout # => 60 + Rails.configuration.resque.inline_jobs # => :always + Rails.configuration.super_debugger # => true ``` ([Commit](https://github.com/rails/rails/commit/611849772dd66c2e4d005dcfe153f7ce79a8a7db)) diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index ef7ef5a50e..f37bb20750 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -495,7 +495,7 @@ image_url("edit.png") # => http://www.example.com/assets/edit.png #### image_tag -Returns an html image tag for the source. The source can be a full path or a file that exists in your `app/assets/images` directory. +Returns an HTML image tag for the source. The source can be a full path or a file that exists in your `app/assets/images` directory. ```ruby image_tag("icon.png") # => <img src="/assets/icon.png" alt="Icon" /> @@ -503,7 +503,7 @@ image_tag("icon.png") # => <img src="/assets/icon.png" alt="Icon" /> #### javascript_include_tag -Returns an html script tag for each of the sources provided. You can pass in the filename (`.js` extension is optional) of JavaScript files that exist in your `app/assets/javascripts` directory for inclusion into the current page or you can pass the full path relative to your document root. +Returns an HTML script tag for each of the sources provided. You can pass in the filename (`.js` extension is optional) of JavaScript files that exist in your `app/assets/javascripts` directory for inclusion into the current page or you can pass the full path relative to your document root. ```ruby javascript_include_tag "common" # => <script src="/assets/common.js"></script> @@ -736,7 +736,7 @@ distance_of_time_in_words(Time.now, Time.now + 15.seconds, include_seconds: true #### select_date -Returns a set of html select-tags (one for year, month, and day) pre-selected with the `date` provided. +Returns a set of HTML select-tags (one for year, month, and day) pre-selected with the `date` provided. ```ruby # Generates a date select that defaults to the date provided (six days after today) @@ -748,7 +748,7 @@ select_date() #### select_datetime -Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the `datetime` provided. +Returns a set of HTML select-tags (one for year, month, day, hour, and minute) pre-selected with the `datetime` provided. ```ruby # Generates a datetime select that defaults to the datetime provided (four days after today) @@ -808,7 +808,7 @@ select_second(Time.now + 16.minutes) #### select_time -Returns a set of html select-tags (one for hour and minute). +Returns a set of HTML select-tags (one for hour and minute). ```ruby # Generates a time select that defaults to the time provided @@ -1526,7 +1526,7 @@ The SanitizeHelper module provides a set of methods for scrubbing text of undesi #### sanitize -This sanitize helper will html encode all tags and strip all attributes that aren't specifically allowed. +This sanitize helper will HTML encode all tags and strip all attributes that aren't specifically allowed. ```ruby sanitize @article.body diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 801cef5ca6..6922dd681a 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -1006,16 +1006,18 @@ Custom configuration You can configure your own code through the Rails configuration object with custom configuration. It works like this: ```ruby - config.x.payment_processing.schedule = :daily - config.x.payment_processing.retries = 3 - config.x.super_debugger = true + config.payment_processing.schedule = :daily + config.payment_processing.retries = 3 + config.resque = { timeout: 60, inline_jobs: :always } + config.super_debugger = true ``` These configuration points are then available through the configuration object: ```ruby - Rails.configuration.x.payment_processing.schedule # => :daily - Rails.configuration.x.payment_processing.retries # => 3 - Rails.configuration.x.super_debugger # => true - Rails.configuration.x.super_debugger.not_set # => nil + Rails.configuration.payment_processing.schedule # => :daily + Rails.configuration.payment_processing.retries # => 3 + Rails.configuration.resque.timeout # => 60 + Rails.configuration.resque.inline_jobs # => :always + Rails.configuration.super_debugger # => true ``` diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 887a8edf59..964bb30856 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -21,10 +21,10 @@ application from scratch. It does not assume that you have any prior experience with Rails. However, to get the most out of it, you need to have some prerequisites installed: -* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer. -* The [RubyGems](http://rubygems.org) packaging system, which is installed with Ruby +* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer. +* The [RubyGems](https://rubygems.org) packaging system, which is installed with Ruby versions 1.9 and later. To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org). -* A working installation of the [SQLite3 Database](http://www.sqlite.org). +* A working installation of the [SQLite3 Database](https://www.sqlite.org). Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning @@ -101,7 +101,7 @@ If you don't have Ruby installed have a look at install Ruby on your platform. Many popular UNIX-like OSes ship with an acceptable version of SQLite3. Windows -users and others can find installation instructions at [the SQLite3 website](http://www.sqlite.org). +users and others can find installation instructions at [the SQLite3 website](https://www.sqlite.org). Verify that it is correctly installed and in your PATH: ```bash @@ -748,7 +748,7 @@ to create an article. Try it! You should get an error that looks like this: (images/getting_started/forbidden_attributes_for_new_article.png) Rails has several security features that help you write secure applications, -and you're running into one of them now. This one is called [strong parameters](http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters), +and you're running into one of them now. This one is called [strong parameters](action_controller_overview.html#strong-parameters), which requires us to tell Rails exactly which parameters are allowed into our controller actions. diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index 8b37b92139..60571750a1 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -263,7 +263,7 @@ TIP: This is useful when you're rendering a small snippet of HTML code. However, you might want to consider moving it to a template file if the markup is complex. -NOTE: This option will escape HTML entities if the string is not html safe. +NOTE: This option will escape HTML entities if the string is not HTML safe. #### Rendering JSON diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 9f3c3ccdf5..fdc6d1806e 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -20,16 +20,18 @@ configure your own code through the Rails configuration object with custom configuration: # config/environments/production.rb - config.x.payment_processing.schedule = :daily - config.x.payment_processing.retries = 3 - config.x.super_debugger = true + config.payment_processing.schedule = :daily + config.payment_processing.retries = 3 + config.resque = { timeout: 60, inline_jobs: :always } + config.super_debugger = true These configuration points are then available through the configuration object: - Rails.configuration.x.payment_processing.schedule # => :daily - Rails.configuration.x.payment_processing.retries # => 3 - Rails.configuration.x.super_debugger # => true - Rails.configuration.x.super_debugger.not_set # => nil + Rails.configuration.payment_processing.schedule # => :daily + Rails.configuration.payment_processing.retries # => 3 + Rails.configuration.resque.timeout # => 60 + Rails.configuration.resque.inline_jobs # => :always + Rails.configuration.super_debugger # => true *DHH* diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 782bc4b0f1..5e8f4de847 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -13,7 +13,7 @@ module Rails :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, - :beginning_of_week, :filter_redirect, :x + :beginning_of_week, :filter_redirect attr_writer :log_level attr_reader :encoding @@ -48,7 +48,6 @@ module Rails @eager_load = nil @secret_token = nil @secret_key_base = nil - @x = Custom.new @assets = ActiveSupport::OrderedOptions.new @assets.enabled = true @@ -155,17 +154,6 @@ module Rails def annotations SourceAnnotationExtractor::Annotation end - - private - class Custom - def initialize - @configurations = Hash.new - end - - def method_missing(method, *args) - @configurations[method] ||= ActiveSupport::OrderedOptions.new - end - end end end end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index d68f0dd851..22b953aedc 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -16,9 +16,7 @@ source 'https://rubygems.org' # gem 'bcrypt', '~> 3.1.7' # Use Rails Html Sanitizer for HTML sanitization -gem 'rails-html-sanitizer', github: 'rails/rails-html-sanitizer', branch: 'master' -#temporary gem until a new version of loofah is released -gem 'loofah', github: 'kaspth/loofah', branch: 'single-scrub' +gem 'rails-html-sanitizer', '~> 1.0' # Use Unicorn as the app server # gem 'unicorn' diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 16fe50bab8..761e757d7f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -30,5 +30,10 @@ module <%= app_const_base %> # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de + + <%- unless options.skip_active_record? -%> + # For not swallow errors in after_commit/after_rollback callbacks. + config.active_record.raise_in_transactional_callbacks = true + <%- end -%> end end diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index eb3b2d8ef4..c15cc1a144 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -88,11 +88,45 @@ module Rails def method_missing(name, *args, &blk) if name.to_s =~ /=$/ - @@options[$`.to_sym] = args.first + key = $`.to_sym + value = args.first + + if value.is_a?(Hash) + @@options[key] = ChainedConfigurationOptions.new value + else + @@options[key] = value + end elsif @@options.key?(name) @@options[name] else - super + @@options[name] = ActiveSupport::OrderedOptions.new + end + end + + class ChainedConfigurationOptions < ActiveSupport::OrderedOptions # :nodoc: + def initialize(value) + value.each_pair { |k, v| set_value k, v } + end + + def method_missing(meth, *args) + if meth =~ /=$/ + key = $`.to_sym + value = args.first + + set_value key, value + else + self.fetch(meth) { super } + end + end + + private + + def set_value(key, value) + if value.is_a?(Hash) + value = self.class.new(value) + end + + self[key] = value end end end diff --git a/railties/test/application/configuration/base_test.rb b/railties/test/application/configuration/base_test.rb index d6a82b139d..6e2b618160 100644 --- a/railties/test/application/configuration/base_test.rb +++ b/railties/test/application/configuration/base_test.rb @@ -5,6 +5,8 @@ require 'env_helpers' module ApplicationTests module ConfigurationTests class BaseTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + def setup build_app boot_rails @@ -30,8 +32,8 @@ module ApplicationTests end def require_environment - require "#{app_path}/config/environment" + require "#{app_path}/config/environment" end end end -end
\ No newline at end of file +end diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb index 045537fc28..e8c7a37913 100644 --- a/railties/test/application/configuration/custom_test.rb +++ b/railties/test/application/configuration/custom_test.rb @@ -1,15 +1,84 @@ require 'application/configuration/base_test' class ApplicationTests::ConfigurationTests::CustomTest < ApplicationTests::ConfigurationTests::BaseTest - test 'access custom configuration point' do + test 'configuration top level can be chained' do add_to_config <<-RUBY - config.x.resque.inline_jobs = :always - config.x.resque.timeout = 60 + config.resque.inline_jobs = :always + config.resque.timeout = 60 RUBY require_environment - assert_equal :always, Rails.configuration.x.resque.inline_jobs - assert_equal 60, Rails.configuration.x.resque.timeout - assert_nil Rails.configuration.x.resque.nothing + assert_equal :always, Rails.configuration.resque.inline_jobs + assert_equal 60, Rails.configuration.resque.timeout + assert_nil Rails.configuration.resque.nothing + end + + test 'configuration top level accept normal values' do + add_to_config <<-RUBY + config.timeout = 60 + config.something_nil = nil + config.something_false = false + config.something_true = true + RUBY + require_environment + + assert_equal 60, Rails.configuration.timeout + assert_equal nil, Rails.configuration.something_nil + assert_equal false, Rails.configuration.something_false + assert_equal true, Rails.configuration.something_true + end + + test 'configuration top level builds options from hashes' do + add_to_config <<-RUBY + config.resque = { timeout: 60, inline_jobs: :always } + RUBY + require_environment + + assert_equal :always, Rails.configuration.resque.inline_jobs + assert_equal 60, Rails.configuration.resque.timeout + assert_nil Rails.configuration.resque.nothing + end + + test 'configuration top level builds options from hashes with string keys' do + add_to_config <<-RUBY + config.resque = { 'timeout' => 60, 'inline_jobs' => :always } + RUBY + require_environment + + assert_equal :always, Rails.configuration.resque.inline_jobs + assert_equal 60, Rails.configuration.resque.timeout + assert_nil Rails.configuration.resque.nothing + end + + test 'configuration top level builds nested options from hashes with symbol keys' do + add_to_config <<-RUBY + config.resque = { timeout: 60, inline_jobs: :always, url: { host: 'localhost', port: 8080 } } + config.resque.url.protocol = 'https' + config.resque.queues = { production: ['low_priority'] } + RUBY + require_environment + + assert_equal(:always, Rails.configuration.resque.inline_jobs) + assert_equal(60, Rails.configuration.resque.timeout) + assert_equal({ host: 'localhost', port: 8080, protocol: 'https' }, Rails.configuration.resque.url) + assert_equal('localhost', Rails.configuration.resque.url.host) + assert_equal(8080, Rails.configuration.resque.url.port) + assert_equal('https', Rails.configuration.resque.url.protocol) + assert_equal(['low_priority'], Rails.configuration.resque.queues.production) + assert_nil(Rails.configuration.resque.nothing) + end + + test 'configuration top level builds nested options from hashes with string keys' do + add_to_config <<-RUBY + config.resque = { 'timeout' => 60, 'inline_jobs' => :always, 'url' => { 'host' => 'localhost', 'port' => 8080 } } + RUBY + require_environment + + assert_equal(:always, Rails.configuration.resque.inline_jobs) + assert_equal(60, Rails.configuration.resque.timeout) + assert_equal({ host: 'localhost', port: 8080 }, Rails.configuration.resque.url) + assert_equal('localhost', Rails.configuration.resque.url.host) + assert_equal(8080, Rails.configuration.resque.url.port) + assert_nil(Rails.configuration.resque.nothing) end end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index ec64ce5941..da4eccd2b7 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -840,7 +840,7 @@ YAML Rails.application.load_seed assert Rails.application.config.app_seeds_loaded - assert_raise(NoMethodError) { Bukkits::Engine.config.bukkits_seeds_loaded } + assert_empty Bukkits::Engine.config.bukkits_seeds_loaded Bukkits::Engine.load_seed assert Bukkits::Engine.config.bukkits_seeds_loaded |