diff options
42 files changed, 233 insertions, 94 deletions
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 798241aaf8..a70bf1544a 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -84,7 +84,8 @@ module ActionMailer # name as the method in your mailer model. For example, in the mailer defined above, the template at # <tt>app/views/notifier/welcome.text.erb</tt> would be used to generate the email. # - # Variables defined in the model are accessible as instance variables in the view. + # Variables defined in the methods of your mailer model are accessible as instance variables in their + # corresponding view. # # Emails by default are sent in plain text, so a sample view for our model example might look like this: # @@ -702,8 +703,8 @@ module ActionMailer # The main method that creates the message and renders the email templates. There are # two ways to call this method, with a block, or without a block. # - # Both methods accept a headers hash. This hash allows you to specify the most used headers - # in an email message, these are: + # It accepts a headers hash. This hash allows you to specify + # the most used headers in an email message, these are: # # * +:subject+ - The subject of the message, if this is omitted, Action Mailer will # ask the Rails I18n class for a translated +:subject+ in the scope of diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb index 622f202695..95231411fb 100644 --- a/actionmailer/lib/action_mailer/delivery_job.rb +++ b/actionmailer/lib/action_mailer/delivery_job.rb @@ -1,6 +1,8 @@ require 'active_job' module ActionMailer + # The <tt>ActionMailer::DeliveryJob</tt> class is used when you + # want to send emails outside of the request-response cycle. class DeliveryJob < ActiveJob::Base #:nodoc: queue_as :mailers diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 95c67d482b..df7382f02d 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -150,7 +150,17 @@ module AbstractController rescue LoadError => e raise AbstractController::Helpers::MissingHelperError.new(e, file_name) end - file_name.camelize.constantize + + mod_name = file_name.camelize + begin + mod_name.constantize + rescue LoadError + # dependencies.rb gives a similar error message but its wording is + # not as clear because it mentions autoloading. To the user all it + # matters is that a helper module couldn't be loaded, autoloading + # is an internal mechanism that should not leak. + raise NameError, "Couldn't find #{mod_name}, expected it to be defined in helpers/#{file_name}.rb" + end when Module arg else diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index a4f376816f..fd20682f8f 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -1,5 +1,6 @@ require 'rack/session/abstract/id' require 'action_controller/metal/exceptions' +require 'active_support/security_utils' module ActionController #:nodoc: class InvalidAuthenticityToken < ActionControllerError #:nodoc: @@ -305,8 +306,7 @@ module ActionController #:nodoc: end def compare_with_real_token(token, session) - # Borrow a constant-time comparison from Rack - Rack::Utils.secure_compare(token, real_csrf_token(session)) + ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session)) end def real_csrf_token(session) diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index 20f99f19ee..936b8c2450 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -60,6 +60,12 @@ class HelpersPathsController < ActionController::Base end end +class HelpersTypoController < ActionController::Base + path = File.expand_path('../../fixtures/helpers_typo', __FILE__) + $:.unshift(path) + self.helpers_path = path +end + module LocalAbcHelper def a() end def b() end @@ -82,6 +88,22 @@ class HelperPathsTest < ActiveSupport::TestCase end end +class HelpersTypoControllerTest < ActiveSupport::TestCase + def setup + @autoload_paths = ActiveSupport::Dependencies.autoload_paths + ActiveSupport::Dependencies.autoload_paths = Array(HelpersTypoController.helpers_path) + end + + def test_helper_typo_error_message + e = assert_raise(NameError) { HelpersTypoController.helper 'admin/users' } + assert_equal "Couldn't find Admin::UsersHelper, expected it to be defined in helpers/admin/users_helper.rb", e.message + end + + def teardown + ActiveSupport::Dependencies.autoload_paths = @autoload_paths + end +end + class HelperTest < ActiveSupport::TestCase class TestController < ActionController::Base attr_accessor :delegate_attr diff --git a/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb b/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb new file mode 100644 index 0000000000..7d2326e04d --- /dev/null +++ b/actionpack/test/fixtures/helpers_typo/admin/users_helper.rb @@ -0,0 +1,5 @@ +module Admin + module UsersHelpeR + end +end + diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 8ac74dc938..e388e6ecd3 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,9 @@ +* Update `select_tag` to work correctly with `:include_blank` option passing a string. + + Fixes #16483. + + *Frank Groeneveld* + * Changed the meaning of `render "foo/bar"`. Previously, calling `render "foo/bar"` in a controller action is equivalent diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 69893b8abd..c0218fd55d 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -133,12 +133,18 @@ module ActionView option_tags ||= "" html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name - if options.delete(:include_blank) - option_tags = content_tag(:option, '', :value => '').safe_concat(option_tags) + if options.include?(:include_blank) + include_blank = options.delete(:include_blank) + + if include_blank == true + include_blank = '' + end + + option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags) end if prompt = options.delete(:prompt) - option_tags = content_tag(:option, prompt, :value => '').safe_concat(option_tags) + option_tags = content_tag(:option, prompt, value: '').safe_concat(option_tags) end content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 4f2db0a0c4..7cb55cc214 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -57,7 +57,7 @@ module ActionView # Add table tags to the default allowed tags # # class Application < Rails::Application - # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' + # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td'] # end # # Remove tags to the default allowed tags @@ -176,7 +176,7 @@ module ActionView # Replaces the allowed tags for the +sanitize+ helper. # # class Application < Rails::Application - # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' + # config.action_view.sanitized_allowed_tags = ['table', 'tr', 'td'] # end # diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index f3a48ecfa0..cd21d7ab47 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -18,7 +18,7 @@ module ActionView # Determine the template to be rendered using the given options. def determine_template(options) - keys = options.fetch(:locals, {}).keys + keys = options.has_key?(:locals) ? options[:locals].keys : [] if options.key?(:body) Template::Text.new(options[:body]) diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index 379f31bdaf..6b61378a1f 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -313,11 +313,15 @@ module ActionView def locals_code #:nodoc: # Double assign to suppress the dreaded 'assigned but unused variable' warning - @locals.map { |key| "#{key} = #{key} = local_assigns[:#{key}];" }.join + @locals.each_with_object('') { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" } end def method_name #:nodoc: - @method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".tr('-', "_") + @method_name ||= begin + m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}" + m.tr!('-', '_') + m + end end def identifier_method_name #:nodoc: diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb index 2d89332841..f8fd642809 100644 --- a/actionview/test/template/form_tag_helper_test.rb +++ b/actionview/test/template/form_tag_helper_test.rb @@ -232,6 +232,12 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_select_tag_with_include_blank_string + actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, include_blank: 'Choose' + expected = %(<select id="places" name="places"><option value="">Choose</option><option>Home</option><option>Work</option><option>Pub</option></select>) + assert_dom_equal expected, actual + end + def test_select_tag_with_prompt actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "string" expected = %(<select id="places" name="places"><option value="">string</option><option>Home</option><option>Work</option><option>Pub</option></select>) diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 1116f6b8ee..c1e344b215 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -392,7 +392,7 @@ module ActiveModel protected def run_validations! #:nodoc: - run_validate_callbacks + _run_validate_callbacks errors.empty? end end diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index 1a5192b0ff..25ccabd66b 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -110,7 +110,7 @@ module ActiveModel # Overwrite run validations to include callbacks. def run_validations! #:nodoc: - run_validation_callbacks { super } + _run_validation_callbacks { super } end end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index e0e107e89d..fa9e023d52 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* Fix `Relation.rewhere` to work with Range values. + + *Dan Olson* + * `AR::UnknownAttributeError` now includes the class name of a record. User.new(name: "Yuki Nishijima", project_attributes: {name: "kaminari"}) diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 455a540bdb..57affce4f7 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -159,7 +159,7 @@ module ActiveRecord count = scope.destroy_all.length else scope.to_a.each do |record| - record.run_destroy_callbacks + record._run_destroy_callbacks end arel = scope.arel diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 1aa760157a..523d492a48 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -289,25 +289,25 @@ module ActiveRecord end def destroy #:nodoc: - run_destroy_callbacks { super } + _run_destroy_callbacks { super } end def touch(*) #:nodoc: - run_touch_callbacks { super } + _run_touch_callbacks { super } end private def create_or_update #:nodoc: - run_save_callbacks { super } + _run_save_callbacks { super } end def _create_record #:nodoc: - run_create_callbacks { super } + _run_create_callbacks { super } end def _update_record(*) #:nodoc: - run_update_callbacks { super } + _run_update_callbacks { super } end 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 da43e5bb10..eba51baba1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -360,7 +360,7 @@ module ActiveRecord synchronize do owner = conn.owner - conn.run_checkin_callbacks do + conn._run_checkin_callbacks do conn.expire end @@ -449,7 +449,7 @@ module ActiveRecord end def checkout_and_verify(c) - c.run_checkout_callbacks do + c._run_checkout_callbacks do c.verify! end c diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb index e396ff4a1e..35e699eeda 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -4,7 +4,7 @@ module ActiveRecord module OID # :nodoc: # This class uses the data from PostgreSQL pg_type table to build # the OID -> Type mapping. - # - OID is and integer representing the type. + # - OID is an integer representing the type. # - Type is an OID::Type object. # This class has side effects on the +store+ passed during initialization. class TypeMapInitializer # :nodoc: diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 5571a2d297..f9b6375459 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -272,7 +272,7 @@ module ActiveRecord init_attributes(attributes, options) if attributes yield self if block_given? - run_initialize_callbacks + _run_initialize_callbacks end # Initialize an empty model object from +coder+. +coder+ must contain @@ -294,8 +294,8 @@ module ActiveRecord self.class.define_attribute_methods - run_find_callbacks - run_initialize_callbacks + _run_find_callbacks + _run_initialize_callbacks self end @@ -331,7 +331,7 @@ module ActiveRecord @attributes = @attributes.dup @attributes.reset(self.class.primary_key) - run_initialize_callbacks + _run_initialize_callbacks @aggregation_cache = {} @association_cache = {} diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index bbddd28ccc..a53e8c9d0a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -913,7 +913,7 @@ module ActiveRecord where_values.reject! do |rel| case rel - when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual + when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right) subrelation.name == target_value end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index bb06d0304b..f92e1de03b 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -309,7 +309,7 @@ module ActiveRecord # Ensure that it is not called if the object was never persisted (failed create), # but call it after the commit of a destroyed object. def committed!(should_run_callbacks = true) #:nodoc: - run_commit_callbacks if should_run_callbacks && destroyed? || persisted? + _run_commit_callbacks if should_run_callbacks && destroyed? || persisted? ensure force_clear_transaction_record_state end @@ -317,7 +317,7 @@ module ActiveRecord # Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record # state should be rolled back to the beginning or just to the last savepoint. def rolledback!(force_restore_state = false, should_run_callbacks = true) #:nodoc: - run_rollback_callbacks if should_run_callbacks + _run_rollback_callbacks if should_run_callbacks ensure restore_transaction_record_state(force_restore_state) clear_transaction_record_state diff --git a/activerecord/lib/active_record/type/boolean.rb b/activerecord/lib/active_record/type/boolean.rb index 1311be3944..612478e67d 100644 --- a/activerecord/lib/active_record/type/boolean.rb +++ b/activerecord/lib/active_record/type/boolean.rb @@ -15,7 +15,7 @@ module ActiveRecord else if !ConnectionAdapters::Column::FALSE_VALUES.include?(value) ActiveSupport::Deprecation.warn(<<-EOM) - You attempted to assign a value which is not explicitly true or false to a boolean column. Currently this value casts to false. This will change to match Ruby's sematics, and will cast to true in Rails 5.0. If you would like to maintain the current behavior, you should explicitly handle the values you would like cast to false. + You attempted to assign a value which is not explicitly true or false to a boolean column. Currently this value casts to false. This will change to match Ruby's semantics, and will cast to true in Rails 5.0. If you would like to maintain the current behavior, you should explicitly handle the values you would like cast to false. EOM end false diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb index b9e69bdb08..478ac1bc42 100644 --- a/activerecord/test/cases/relation/where_chain_test.rb +++ b/activerecord/test/cases/relation/where_chain_test.rb @@ -149,5 +149,12 @@ module ActiveRecord assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality assert_equal 'alone', bind.last end + + def test_rewhere_with_range + relation = Post.where(comments_count: 1..3).rewhere(comments_count: 3..5) + + assert_equal 1, relation.where_values.size + assert_equal Post.where(comments_count: 3..5), relation + end end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 45231bc101..24c702b602 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -78,7 +78,7 @@ module ActiveSupport # save # end def run_callbacks(kind, &block) - send "run_#{kind}_callbacks", &block + send "_run_#{kind}_callbacks", &block end private @@ -730,7 +730,7 @@ module ActiveSupport set_callbacks name, CallbackChain.new(name, options) module_eval <<-RUBY, __FILE__, __LINE__ + 1 - def run_#{name}_callbacks(&block) + def _run_#{name}_callbacks(&block) _run_callbacks(_#{name}_callbacks, &block) end RUBY diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index caa499dfa2..45b89d2705 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -20,7 +20,11 @@ class Array # %w( a b c d ).to(-2) # => ["a", "b", "c"] # %w( a b c ).to(-10) # => [] def to(position) - self[0..position] + if position >= 0 + first position + 1 + else + self[0..position] + end end # Equal to <tt>self[1]</tt>. diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 112ec05e15..56da398978 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -9,7 +9,23 @@ class Object # # instead of # - # @person ? @person.name : nil + # @person.name if @person + # + # +try+ calls can be chained: + # + # @person.try(:spouse).try(:name) + # + # instead of + # + # @person.spouse.name if @person && @person.spouse + # + # +try+ will also return +nil+ if the receiver does not respond to the method: + # + # @person.try(:non_existing_method) #=> nil + # + # instead of + # + # @person.non_existing_method if @person.respond_to?(:non_existing_method) #=> nil # # +try+ returns +nil+ when called on +nil+ regardless of whether it responds # to the method: @@ -24,7 +40,7 @@ class Object # # The number of arguments in the signature must match. If the object responds # to the method the call is attempted and +ArgumentError+ is still raised - # otherwise. + # in case of argument mismatch. # # If +try+ is called without arguments it yields the receiver to a given # block unless it is +nil+: @@ -38,12 +54,19 @@ class Object # # @person.try { upcase.truncate(50) } # - # Please also note that +try+ is defined on +Object+, therefore it won't work + # Please also note that +try+ is defined on +Object+. Therefore, it won't work # with instances of classes that do not have +Object+ among their ancestors, # like direct subclasses of +BasicObject+. For example, using +try+ with # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on - # delegator itself. + # the delegator itself. def try(*a, &b) + try!(*a, &b) if a.empty? || respond_to?(a.first) + end + + # Same as #try, but will raise a NoMethodError exception if the receiver is not +nil+ and + # does not implement the tried method. + + def try!(*a, &b) if a.empty? && block_given? if b.arity.zero? instance_eval(&b) @@ -51,16 +74,6 @@ class Object yield self end else - public_send(*a, &b) if respond_to?(a.first) - end - end - - # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and - # does not implement the tried method. - def try!(*a, &b) - if a.empty? && block_given? - try(*a, &b) - else public_send(*a, &b) end end @@ -68,12 +81,12 @@ end class NilClass # Calling +try+ on +nil+ always returns +nil+. - # It becomes specially helpful when navigating through associations that may return +nil+. + # It becomes especially helpful when navigating through associations that may return +nil+. # # nil.try(:name) # => nil # # Without +try+ - # @person && !@person.children.blank? && @person.children.first.name + # @person && @person.children.any? && @person.children.first.name # # With +try+ # @person.try(:children).try(:first).try(:name) diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index c761325108..042283e4fc 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -150,7 +150,7 @@ module ActiveSupport #:nodoc: else if html_safe? new_safe_buffer = super - new_safe_buffer.instance_eval { @html_safe = true } + new_safe_buffer.instance_variable_set :@html_safe, true new_safe_buffer else to_str[*args] diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 93a11d4586..a89c769e34 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -30,6 +30,10 @@ module ActiveSupport #:nodoc: mattr_accessor :loaded self.loaded = Set.new + # Stack of files being loaded. + mattr_accessor :loading + self.loading = [] + # Should we load files or require them? mattr_accessor :mechanism self.mechanism = ENV['NO_RELOAD'] ? :require : :load @@ -317,6 +321,7 @@ module ActiveSupport #:nodoc: def clear log_call loaded.clear + loading.clear remove_unloadable_constants! end @@ -329,6 +334,7 @@ module ActiveSupport #:nodoc: # Record that we've seen this file *before* loading it to avoid an # infinite loop with mutual dependencies. loaded << expanded + loading << expanded begin if load? @@ -351,6 +357,8 @@ module ActiveSupport #:nodoc: rescue Exception loaded.delete expanded raise + ensure + loading.pop end # Record history *after* loading so first load gets warnings. @@ -475,7 +483,7 @@ module ActiveSupport #:nodoc: expanded = File.expand_path(file_path) expanded.sub!(/\.rb\z/, '') - if loaded.include?(expanded) + if loading.include?(expanded) raise "Circular dependency detected while autoloading constant #{qualified_name}" else require_or_load(expanded, qualified_name) diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 97401ccec7..486838bd15 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -154,7 +154,7 @@ module ActiveSupport end end - # Add uncountable words that shouldn't be attempted inflected. + # Specifies words that are uncountable and should not be inflected. # # uncountable 'money' # uncountable 'money', 'information' diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 6cb2884fb7..4e0796f4f8 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -1,5 +1,6 @@ require 'base64' require 'active_support/core_ext/object/blank' +require 'active_support/security_utils' module ActiveSupport # +MessageVerifier+ makes it easy to generate and verify messages which are @@ -37,7 +38,7 @@ module ActiveSupport raise InvalidSignature if signed_message.blank? data, digest = signed_message.split("--") - if data.present? && digest.present? && secure_compare(digest, generate_digest(data)) + if data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) begin @serializer.load(::Base64.strict_decode64(data)) rescue ArgumentError => argument_error @@ -55,17 +56,6 @@ module ActiveSupport end private - # constant-time comparison algorithm to prevent timing attacks - def secure_compare(a, b) - return false unless a.bytesize == b.bytesize - - l = a.unpack "C#{a.bytesize}" - - res = 0 - b.each_byte { |byte| res |= byte ^ l.shift } - res == 0 - end - def generate_digest(data) require 'openssl' unless defined?(OpenSSL) OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb new file mode 100644 index 0000000000..64c4801179 --- /dev/null +++ b/activesupport/lib/active_support/security_utils.rb @@ -0,0 +1,20 @@ +module ActiveSupport + module SecurityUtils + # Constant time string comparison. + # + # The values compared should be of fixed length, such as strings + # that have already been processed by HMAC. This should not be used + # on variable length plaintext strings because it could leak length info + # via timing attacks. + def secure_compare(a, b) + return false unless a.bytesize == b.bytesize + + l = a.unpack "C#{a.bytesize}" + + res = 0 + b.each_byte { |byte| res |= byte ^ l.shift } + res == 0 + end + module_function :secure_compare + end +end diff --git a/activesupport/test/autoloading_fixtures/typo.rb b/activesupport/test/autoloading_fixtures/typo.rb new file mode 100644 index 0000000000..8e047f5fd4 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/typo.rb @@ -0,0 +1,2 @@ +TypO = 1 + diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 899bb75eae..f2f60167c4 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -157,6 +157,31 @@ class DependenciesTest < ActiveSupport::TestCase end end + def test_ensures_the_expected_constant_is_defined + with_autoloading_fixtures do + e = assert_raise(LoadError) { Typo } + assert_match %r{Unable to autoload constant Typo, expected .*activesupport/test/autoloading_fixtures/typo.rb to define it}, e.message + end + end + + def test_require_dependency_does_not_assume_any_particular_constant_is_defined + with_autoloading_fixtures do + require_dependency 'typo' + assert_equal 1, TypO + end + end + + # Regression, see https://github.com/rails/rails/issues/16468. + def test_require_dependency_interaction_with_autoloading + with_autoloading_fixtures do + require_dependency 'typo' + assert_equal 1, TypO + + e = assert_raise(LoadError) { Typo } + assert_match %r{Unable to autoload constant Typo, expected .*activesupport/test/autoloading_fixtures/typo.rb to define it}, e.message + end + end + def test_module_loading with_autoloading_fixtures do assert_kind_of Module, A diff --git a/activesupport/test/dependencies_test_helpers.rb b/activesupport/test/dependencies_test_helpers.rb index 9268512a97..e4d5197112 100644 --- a/activesupport/test/dependencies_test_helpers.rb +++ b/activesupport/test/dependencies_test_helpers.rb @@ -13,6 +13,7 @@ module DependenciesTestHelpers ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths ActiveSupport::Dependencies.mechanism = old_mechanism ActiveSupport::Dependencies.explicitly_unloadable_constants = [] + ActiveSupport::Dependencies.clear end def with_autoloading_fixtures(&block) diff --git a/activesupport/test/security_utils_test.rb b/activesupport/test/security_utils_test.rb new file mode 100644 index 0000000000..08d2e3baa6 --- /dev/null +++ b/activesupport/test/security_utils_test.rb @@ -0,0 +1,9 @@ +require 'abstract_unit' +require 'active_support/security_utils' + +class SecurityUtilsTest < ActiveSupport::TestCase + def test_secure_compare_should_perform_string_comparison + assert ActiveSupport::SecurityUtils.secure_compare('a', 'a') + assert !ActiveSupport::SecurityUtils.secure_compare('a', 'b') + end +end diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index ecf3483d7e..bd074d0055 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -31,7 +31,7 @@ Object Relational Mapping system. in his book _Patterns of Enterprise Application Architecture_. In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring -data access logic is part of the object will educate users of that +data access logic as part of the object will educate users of that object on how to write to and read from the database. ### Object Relational Mapping diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index 229c6ee458..c8a31fe7b8 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -466,7 +466,7 @@ add_foreign_key :articles, :authors ``` This adds a new foreign key to the `author_id` column of the `articles` -table. The key references the `id` column of the `articles` table. If the +table. The key references the `id` column of the `authors` table. If the column names can not be derived from the table names, you can use the `:column` and `:primary_key` options. diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index de42f13145..f6f96b79c6 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1833,16 +1833,14 @@ attribute names: ```ruby def full_messages - full_messages = [] - - each do |attribute, messages| - ... - attr_name = attribute.to_s.gsub('.', '_').humanize - attr_name = @base.class.human_attribute_name(attribute, default: attr_name) - ... - end + map { |attribute, message| full_message(attribute, message) } +end - full_messages +def full_message + ... + attr_name = attribute.to_s.tr('.', '_').humanize + attr_name = @base.class.human_attribute_name(attribute, default: attr_name) + ... end ``` diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index e1a11482e6..2960eccb3f 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -89,13 +89,7 @@ *Rafael Mendonça França* -* Add a generic --skip-gems options to generator - - This option is useful if users want to remove some gems like jbuilder, - turbolinks, coffee-rails, etc that don't have specific options on the - generator. - - rails new my_app --skip-gems turbolinks coffee-rails +* Add a generic --skip-turbolinks options to generator. *Rafael Mendonça França* diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 1eca86bd30..92ed9136a0 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -41,9 +41,6 @@ module Rails class_option :skip_active_record, type: :boolean, aliases: '-O', default: false, desc: 'Skip Active Record files' - class_option :skip_gems, type: :array, default: [], - desc: 'Skip the provided gems files' - class_option :skip_sprockets, type: :boolean, aliases: '-S', default: false, desc: 'Skip Sprockets files' @@ -65,6 +62,9 @@ module Rails class_option :edge, type: :boolean, default: false, desc: "Setup the #{name} with Gemfile pointing to Rails repository" + class_option :skip_turbolinks, type: :boolean, default: false, + desc: 'Skip turbolinks gem' + class_option :skip_test_unit, type: :boolean, aliases: '-T', default: false, desc: 'Skip Test::Unit files' @@ -79,7 +79,7 @@ module Rails end def initialize(*args) - @gem_filter = lambda { |gem| !options[:skip_gems].include?(gem.name) } + @gem_filter = lambda { |gem| true } @extra_entries = [] super convert_database_option_for_jruby @@ -287,8 +287,11 @@ module Rails "Use #{options[:javascript]} as the JavaScript library") end - gems << GemfileEntry.version("turbolinks", nil, - "Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks") + unless options[:skip_turbolinks] + gems << GemfileEntry.version("turbolinks", nil, + "Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks") + end + gems end end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index b492258efb..501b2e0811 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -450,12 +450,11 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_if_skip_gems_is_given - run_generator [destination_root, "--skip-gems", "turbolinks", "coffee-rails"] + def test_generator_if_skip_turbolinks_is_given + run_generator [destination_root, "--skip-turbolinks"] assert_file "Gemfile" do |content| assert_no_match(/turbolinks/, content) - assert_no_match(/coffee-rails/, content) end assert_file "app/views/layouts/application.html.erb" do |content| assert_no_match(/data-turbolinks-track/, content) |