diff options
Diffstat (limited to 'activesupport')
-rw-r--r-- | activesupport/CHANGELOG.md | 65 | ||||
-rw-r--r-- | activesupport/activesupport.gemspec | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/backtrace_cleaner.rb | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/cache/file_store.rb | 44 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/module.rb | 1 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/module/concerning.rb | 135 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/string/output_safety.rb | 19 | ||||
-rw-r--r-- | activesupport/lib/active_support/i18n_railtie.rb | 14 | ||||
-rw-r--r-- | activesupport/lib/active_support/message_verifier.rb | 5 | ||||
-rw-r--r-- | activesupport/lib/active_support/per_thread_registry.rb | 7 | ||||
-rw-r--r-- | activesupport/lib/active_support/version.rb | 2 | ||||
-rw-r--r-- | activesupport/test/abstract_unit.rb | 3 | ||||
-rw-r--r-- | activesupport/test/core_ext/module/concerning_test.rb | 35 | ||||
-rw-r--r-- | activesupport/test/message_verifier_test.rb | 14 | ||||
-rw-r--r-- | activesupport/test/safe_buffer_test.rb | 25 |
15 files changed, 326 insertions, 47 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 994e44db00..c830ee61e6 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,11 +1,66 @@ -* Fix file descriptor being leaked on each call to `Kernel.silence_stream`. +* Default the new `I18n.enforce_available_locales` config to `true`, meaning + `I18n` will make sure that all locales passed to it must be declared in the + `available_locales` list. - *Mario Visic* + To disable it add the following configuration to your application: -* Ensure `config.i18n.enforce_available_locales` is set before any other - configuration option. + config.i18n.enforce_available_locales = false - *Yves Senn* + This also ensures I18n configuration is properly initialized taking the new + option into account, to avoid their deprecations while booting up the app. + + *Carlos Antonio da Silva*, *Yves Senn* + +* Introduce Module#concerning: a natural, low-ceremony way to separate + responsibilities within a class. + + Imported from https://github.com/37signals/concerning#readme + + class Todo < ActiveRecord::Base + concerning :EventTracking do + included do + has_many :events + end + + def latest_event + ... + end + + private + def some_internal_method + ... + end + end + + concerning :Trashable do + def trashed? + ... + end + + def latest_event + super some_option: true + end + end + end + + is equivalent to defining these modules inline, extending them into + concerns, then mixing them in to the class. + + Inline concerns tame "junk drawer" classes that intersperse many unrelated + class-level declarations, public instance methods, and private + implementation. Coalesce related bits and give them definition. + These are a stepping stone toward future growth & refactoring. + + When to move on from an inline concern: + * Encapsulating state? Extract collaborator object. + * Encompassing more public behavior or implementation? Move to separate file. + * Sharing behavior among classes? Move to separate file. + + *Jeremy Kemper* + +* Fix file descriptor being leaked on each call to `Kernel.silence_stream`. + + *Mario Visic* * Added `Date#all_week/month/quarter/year` for generating date ranges. diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 0427022dc6..f3625e8b79 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.rdoc_options.concat ['--encoding', 'UTF-8'] - s.add_dependency 'i18n', '~> 0.6', '>= 0.6.4' + s.add_dependency 'i18n', '~> 0.6', '>= 0.6.9' s.add_dependency 'json', '~> 1.7', '>= 1.7.7' s.add_dependency 'tzinfo', '~> 1.1' s.add_dependency 'minitest', '~> 5.1' diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index c88ae3e661..d58578b7bc 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -22,7 +22,7 @@ module ActiveSupport # <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the # backtrace to a pristine state. If you need to reconfigure an existing # BacktraceCleaner so that it does not filter or modify the paths of any lines - # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!<tt> + # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt> # These two methods will give you a completely untouched backtrace. # # Inspired by the Quiet Backtrace gem by Thoughtbot. diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 5795d86a42..8ed60aebac 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -22,8 +22,8 @@ module ActiveSupport extend Strategy::LocalCache end - # Deletes all items from the cache. In this case it deletes all the entries in the specified - # file store directory except for .gitkeep. Be careful which directory is specified in your + # Deletes all items from the cache. In this case it deletes all the entries in the specified + # file store directory except for .gitkeep. Be careful which directory is specified in your # config file when using +FileStore+ because everything in that directory will be deleted. def clear(options = nil) root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)} @@ -43,33 +43,13 @@ module ActiveSupport # Increments an already existing integer value that is stored in the cache. # If the key is not found nothing is done. def increment(name, amount = 1, options = nil) - file_name = key_file_path(namespaced_key(name, options)) - lock_file(file_name) do - options = merged_options(options) - if num = read(name, options) - num = num.to_i + amount - write(name, num, options) - num - else - nil - end - end + modify_value(name, amount, options) end # Decrements an already existing integer value that is stored in the cache. # If the key is not found nothing is done. def decrement(name, amount = 1, options = nil) - file_name = key_file_path(namespaced_key(name, options)) - lock_file(file_name) do - options = merged_options(options) - if num = read(name, options) - num = num.to_i - amount - write(name, num, options) - num - else - nil - end - end + modify_value(name, -amount, options) end def delete_matched(matcher, options = nil) @@ -184,6 +164,22 @@ module ActiveSupport end end end + + # Modifies the amount of an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. + def modify_value(name, amount, options) + file_name = key_file_path(namespaced_key(name, options)) + + lock_file(file_name) do + options = merged_options(options) + + if num = read(name, options) + num = num.to_i + amount + write(name, num, options) + num + end + end + end end end end diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index f2d4887df6..b4efff8b24 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/module/reachable' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/module/attr_internal' +require 'active_support/core_ext/module/concerning' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/deprecation' require 'active_support/core_ext/module/remove_method' diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb new file mode 100644 index 0000000000..b22dc5ff1e --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/concerning.rb @@ -0,0 +1,135 @@ +require 'active_support/concern' + +class Module + # = Bite-sized separation of concerns + # + # We often find ourselves with a medium-sized chunk of behavior that we'd + # like to extract, but only mix in to a single class. + # + # Extracting a plain old Ruby object to encapsulate it and collaborate or + # delegate to the original object is often a good choice, but when there's + # no additional state to encapsulate or we're making DSL-style declarations + # about the parent class, introducing new collaborators can obfuscate rather + # than simplify. + # + # The typical route is to just dump everything in a monolithic class, perhaps + # with a comment, as a least-bad alternative. Using modules in separate files + # means tedious sifting to get a big-picture view. + # + # = Dissatisfying ways to separate small concerns + # + # == Using comments: + # + # class Todo + # # Other todo implementation + # # ... + # + # ## Event tracking + # has_many :events + # + # before_create :track_creation + # after_destroy :track_deletion + # + # private + # def track_creation + # # ... + # end + # end + # + # == With an inline module: + # + # Noisy syntax. + # + # class Todo + # # Other todo implementation + # # ... + # + # module EventTracking + # extend ActiveSupport::Concern + # + # included do + # has_many :events + # before_create :track_creation + # after_destroy :track_deletion + # end + # + # private + # def track_creation + # # ... + # end + # end + # include EventTracking + # end + # + # == Mix-in noise exiled to its own file: + # + # Once our chunk of behavior starts pushing the scroll-to-understand it + # boundary, we give in and move it to a separate file. At this size, the + # overhead feels in good proportion to the size of our extraction, despite + # diluting our at-a-glance sense of how things really work. + # + # class Todo + # # Other todo implementation + # # ... + # + # include TodoEventTracking + # end + # + # = Introducing Module#concerning + # + # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to + # separate bite-sized concerns. + # + # class Todo + # # Other todo implementation + # # ... + # + # concerning :EventTracking do + # included do + # has_many :events + # before_create :track_creation + # after_destroy :track_deletion + # end + # + # private + # def track_creation + # # ... + # end + # end + # end + # + # Todo.ancestors + # # => Todo, Todo::EventTracking, Object + # + # This small step has some wonderful ripple effects. We can + # * grok the behavior of our class in one glance, + # * clean up monolithic junk-drawer classes by separating their concerns, and + # * stop leaning on protected/private for crude "this is internal stuff" modularity. + module Concerning + # Define a new concern and mix it in. + def concerning(topic, &block) + include concern(topic, &block) + end + + # A low-cruft shortcut to define a concern. + # + # concern :EventTracking do + # ... + # end + # + # is equivalent to + # + # module EventTracking + # extend ActiveSupport::Concern + # + # ... + # end + def concern(topic, &module_definition) + const_set topic, Module.new { + extend ::ActiveSupport::Concern + module_eval(&module_definition) + } + end + end + include Concerning +end 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 1b2098fc84..1b20507c0b 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -183,15 +183,14 @@ module ActiveSupport #:nodoc: end def %(args) - args = Array(args).map do |arg| - if !html_safe? || arg.html_safe? - arg - else - ERB::Util.h(arg) - end + case args + when Hash + escaped_args = Hash[args.map { |k,arg| [k, html_escape_interpolated_argument(arg)] }] + else + escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) } end - self.class.new(super(args)) + self.class.new(super(escaped_args)) end def html_safe? @@ -224,6 +223,12 @@ module ActiveSupport #:nodoc: EOT end end + + private + + def html_escape_interpolated_argument(arg) + (!html_safe? || arg.html_safe?) ? arg : ERB::Util.h(arg) + end end end diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index dcdea70443..ac9bca44b6 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -8,6 +8,8 @@ module I18n config.i18n.railties_load_path = [] config.i18n.load_path = [] config.i18n.fallbacks = ActiveSupport::OrderedOptions.new + # Enforce I18n to check the available locales when setting a locale. + config.i18n.enforce_available_locales = true # Set the i18n configuration after initialization since a lot of # configuration is still usually done in application initializers. @@ -31,10 +33,11 @@ module I18n fallbacks = app.config.i18n.delete(:fallbacks) - if app.config.i18n.has_key?(:enforce_available_locales) - # this option needs to be set before `default_locale=` to work properly. - I18n.enforce_available_locales = app.config.i18n.delete(:enforce_available_locales) - end + # Avoid issues with setting the default_locale by disabling available locales + # check while configuring. + enforce_available_locales = app.config.i18n.delete(:enforce_available_locales) + enforce_available_locales = I18n.enforce_available_locales unless I18n.enforce_available_locales.nil? + I18n.enforce_available_locales = false app.config.i18n.each do |setting, value| case setting @@ -49,6 +52,9 @@ module I18n init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks) + # Restore avalable locales check so it will take place from now on. + I18n.enforce_available_locales = enforce_available_locales + reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! } app.reloaders << reloader ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated } diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index a35d5980fe..8e6e1dcfeb 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -39,8 +39,9 @@ module ActiveSupport if data.present? && digest.present? && secure_compare(digest, generate_digest(data)) begin @serializer.load(::Base64.strict_decode64(data)) - rescue ArgumentError - raise InvalidSignature + rescue ArgumentError => argument_error + raise InvalidSignature if argument_error.message =~ %r{invalid base64} + raise end else raise InvalidSignature diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index a5e7389d16..ca2e4d5625 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -32,12 +32,15 @@ module ActiveSupport # # If the class has an initializer, it must accept no arguments. module PerThreadRegistry + def self.extended(object) + object.instance_variable_set '@per_thread_registry_key', object.name.freeze + end + def instance - Thread.current[name] ||= new + Thread.current[@per_thread_registry_key] ||= new end protected - def method_missing(name, *args, &block) # :nodoc: # Caches the method definition as a singleton method of the receiver. define_singleton_method(name) do |*a, &b| diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index 8762330a6e..b3f0e7198d 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -1,7 +1,7 @@ module ActiveSupport # Returns the version of the currently loaded ActiveSupport as a Gem::Version def self.version - Gem::Version.new "4.1.0.beta" + Gem::Version.new "4.1.0.beta1" end module VERSION #:nodoc: diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 4600855998..1dfa3833f0 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -24,6 +24,9 @@ Thread.abort_on_exception = true # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true +# Disable available locale checks to avoid warnings running the test suite. +I18n.enforce_available_locales = false + # Skips the current run on Rubinius using Minitest::Assertions#skip def rubinius_skip(message = '') skip message if RUBY_ENGINE == 'rbx' diff --git a/activesupport/test/core_ext/module/concerning_test.rb b/activesupport/test/core_ext/module/concerning_test.rb new file mode 100644 index 0000000000..c6863b24a4 --- /dev/null +++ b/activesupport/test/core_ext/module/concerning_test.rb @@ -0,0 +1,35 @@ +require 'abstract_unit' +require 'active_support/core_ext/module/concerning' + +class ConcerningTest < ActiveSupport::TestCase + def test_concern_shortcut_creates_a_module_but_doesnt_include_it + mod = Module.new { concern(:Foo) { } } + assert_kind_of Module, mod::Foo + assert mod::Foo.respond_to?(:included) + assert !mod.ancestors.include?(mod::Foo), mod.ancestors.inspect + end + + def test_concern_creates_a_module_extended_with_active_support_concern + klass = Class.new do + concern :Foo do + included { @foo = 1 } + def should_be_public; end + end + end + + # Declares a concern but doesn't include it + assert_kind_of Module, klass::Foo + assert !klass.ancestors.include?(klass::Foo), klass.ancestors.inspect + + # Public method visibility by default + assert klass::Foo.public_instance_methods.map(&:to_s).include?('should_be_public') + + # Calls included hook + assert_equal 1, Class.new { include klass::Foo }.instance_variable_get('@foo') + end + + def test_concerning_declares_a_concern_and_includes_it_immediately + klass = Class.new { concerning(:Foo) { } } + assert klass.ancestors.include?(klass::Foo), klass.ancestors.inspect + end +end diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index f208814468..a5748d28ba 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -55,6 +55,20 @@ class MessageVerifierTest < ActiveSupport::TestCase ActiveSupport.use_standard_json_time_format = prev end + def test_raise_error_when_argument_class_is_not_loaded + # To generate the valid message below: + # + # AutoloadClass = Struct.new(:foo) + # valid_message = @verifier.generate(foo: AutoloadClass.new('foo')) + # + valid_message = "BAh7BjoIZm9vbzonTWVzc2FnZVZlcmlmaWVyVGVzdDo6QXV0b2xvYWRDbGFzcwY6CUBmb29JIghmb28GOgZFVA==--f3ef39a5241c365083770566dc7a9eb5d6ace914" + exception = assert_raise(ArgumentError, NameError) do + @verifier.verify(valid_message) + end + assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass", + "undefined class/module MessageVerifierTest::AutoloadClass"], exception.message + end + def assert_not_verified(message) assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do @verifier.verify(message) diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index 047b89be2a..efa9d5e61f 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -140,4 +140,29 @@ class SafeBufferTest < ActiveSupport::TestCase # should still be unsafe assert !y.html_safe?, "should not be safe" end + + test 'Should work with interpolation (array argument)' do + x = 'foo %s bar'.html_safe % ['qux'] + assert_equal 'foo qux bar', x + end + + test 'Should work with interpolation (hash argument)' do + x = 'foo %{x} bar'.html_safe % { x: 'qux' } + assert_equal 'foo qux bar', x + end + + test 'Should escape unsafe interpolated args' do + x = 'foo %{x} bar'.html_safe % { x: '<br/>' } + assert_equal 'foo <br/> bar', x + end + + test 'Should not escape safe interpolated args' do + x = 'foo %{x} bar'.html_safe % { x: '<br/>'.html_safe } + assert_equal 'foo <br/> bar', x + end + + test 'Should interpolate to a safe string' do + x = 'foo %{x} bar'.html_safe % { x: 'qux' } + assert x.html_safe?, 'should be safe' + end end |