diff options
Diffstat (limited to 'activesupport')
27 files changed, 877 insertions, 193 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 7c0f3eae80..d946fc1bbd 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,37 @@ +* `ActiveSupport::Cache::Store#namespaced_key`, + `ActiveSupport::Cache::MemCachedStore#escape_key`, and + `ActiveSupport::Cache::FileStore#key_file_path` + are deprecated and replaced with `normalize_key` that now calls `super`. + + `ActiveSupport::Cache::LocaleCache#set_cache_value` is deprecated and replaced with `write_cache_value`. + + *Michael Grosser* + +* Implements an evented file system monitor to asynchronously detect changes + in the application source code, routes, locales, etc. + + To opt-in load the [listen](https://github.com/guard/listen) gem in `Gemfile`: + + group :development do + gem 'listen', '~> 3.0.4' + end + + *Puneet Agarwal* and *Xavier Noria* + +* Added `Time.days_in_year` to return the number of days in the given year, or the + current year if no argument is provided. + + *Jon Pascoe* + +* Updated `parameterize` to preserve the case of a string, optionally. + + Example: + + parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth" + parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth" + + *Swaathi Kakarla* + * `HashWithIndifferentAccess.new` respects the default value or proc on objects that respond to `#to_hash`. `.new_from_hash_copying_default` simply invokes `.new`. All calls to `.new_from_hash_copying_default` are replaced with `.new`. diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 404c16b29f..32e28c0212 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -24,6 +24,6 @@ Gem::Specification.new do |s| s.add_dependency 'json', '~> 1.7', '>= 1.7.7' s.add_dependency 'tzinfo', '~> 1.1' s.add_dependency 'minitest', '~> 5.1' - s.add_dependency 'concurrent-ruby', '~> 1.0.0.pre5', '< 2.0.0' + s.add_dependency 'concurrent-ruby', '~> 1.0' s.add_dependency 'method_source' end diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 63277a65b4..3a2a7d28cb 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -34,6 +34,7 @@ module ActiveSupport autoload :Dependencies autoload :DescendantsTracker autoload :FileUpdateChecker + autoload :FileEventedUpdateChecker autoload :LogSubscriber autoload :Notifications diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index d0e53eaf05..5011014e96 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -8,6 +8,7 @@ require 'active_support/core_ext/numeric/bytes' require 'active_support/core_ext/numeric/time' require 'active_support/core_ext/object/to_param' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/string/strip' module ActiveSupport # See ActiveSupport::Cache::Store for documentation. @@ -275,7 +276,7 @@ module ActiveSupport def fetch(name, options = nil) if block_given? options = merged_options(options) - key = namespaced_key(name, options) + key = normalize_key(name, options) instrument(:read, name, options) do |payload| cached_entry = read_entry(key, options) unless options[:force] @@ -302,7 +303,7 @@ module ActiveSupport # Options are passed to the underlying cache implementation. def read(name, options = nil) options = merged_options(options) - key = namespaced_key(name, options) + key = normalize_key(name, options) instrument(:read, name, options) do |payload| entry = read_entry(key, options) if entry @@ -334,7 +335,7 @@ module ActiveSupport instrument_multi(:read, names, options) do |payload| results = {} names.each do |name| - key = namespaced_key(name, options) + key = normalize_key(name, options) entry = read_entry(key, options) if entry if entry.expired? @@ -386,7 +387,7 @@ module ActiveSupport instrument(:write, name, options) do entry = Entry.new(value, options) - write_entry(namespaced_key(name, options), entry, options) + write_entry(normalize_key(name, options), entry, options) end end @@ -397,7 +398,7 @@ module ActiveSupport options = merged_options(options) instrument(:delete, name) do - delete_entry(namespaced_key(name, options), options) + delete_entry(normalize_key(name, options), options) end end @@ -408,7 +409,7 @@ module ActiveSupport options = merged_options(options) instrument(:exist?, name) do - entry = read_entry(namespaced_key(name, options), options) + entry = read_entry(normalize_key(name, options), options) (entry && !entry.expired?) || false end end @@ -529,7 +530,7 @@ module ActiveSupport # Prefix a key with the namespace. Namespace and key will be delimited # with a colon. - def namespaced_key(key, options) + def normalize_key(key, options) key = expanded_key(key) namespace = options[:namespace] if options prefix = namespace.is_a?(Proc) ? namespace.call : namespace @@ -537,8 +538,16 @@ module ActiveSupport key end + def namespaced_key(*args) + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `namespaced_key` is deprecated and will be removed from Rails 5.1. + Please use `normalize_key` which will return a fully resolved key. + MESSAGE + normalize_key(*args) + end + def instrument(operation, key, options = nil) - log { "Cache #{operation}: #{namespaced_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" } + log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" } payload = { :key => key } payload.merge!(options) if options.is_a?(Hash) diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 9a88fc286a..dff2443bc8 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -60,7 +60,7 @@ module ActiveSupport matcher = key_matcher(matcher, options) search_dir(cache_path) do |path| key = file_path_key(path) - delete_entry(key, options) if key.match(matcher) + delete_entry(path, options) if key.match(matcher) end end end @@ -68,9 +68,8 @@ module ActiveSupport protected def read_entry(key, options) - file_name = key_file_path(key) - if File.exist?(file_name) - File.open(file_name) { |f| Marshal.load(f) } + if File.exist?(key) + File.open(key) { |f| Marshal.load(f) } end rescue => e logger.error("FileStoreError (#{e}): #{e.message}") if logger @@ -78,23 +77,21 @@ module ActiveSupport end def write_entry(key, entry, options) - file_name = key_file_path(key) - return false if options[:unless_exist] && File.exist?(file_name) - ensure_cache_path(File.dirname(file_name)) - File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)} + return false if options[:unless_exist] && File.exist?(key) + ensure_cache_path(File.dirname(key)) + File.atomic_write(key, cache_path) {|f| Marshal.dump(entry, f)} true end def delete_entry(key, options) - file_name = key_file_path(key) - if File.exist?(file_name) + if File.exist?(key) begin - File.delete(file_name) - delete_empty_directories(File.dirname(file_name)) + File.delete(key) + delete_empty_directories(File.dirname(key)) true rescue => e # Just in case the error was caused by another process deleting the file first. - raise e if File.exist?(file_name) + raise e if File.exist?(key) false end end @@ -118,7 +115,8 @@ module ActiveSupport end # Translate a key into a file path. - def key_file_path(key) + def normalize_key(key, options) + key = super fname = URI.encode_www_form_component(key) if fname.size > FILEPATH_MAX_SIZE @@ -139,6 +137,14 @@ module ActiveSupport File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths) end + def key_file_path(key) + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `key_file_path` is deprecated and will be removed from Rails 5.1. + Please use `normalize_key` which will return a fully resolved key or nothing. + MESSAGE + key + end + # Translate a file path into a key. def file_path_key(path) fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last @@ -175,7 +181,7 @@ module ActiveSupport # 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)) + file_name = normalize_key(name, options) lock_file(file_name) do options = merged_options(options) diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index e2f536ef1e..4b0ad37586 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -97,7 +97,7 @@ module ActiveSupport options = merged_options(options) instrument_multi(:read, names, options) do - keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}] + keys_to_names = Hash[names.map{|name| [normalize_key(name, options), name]}] raw_values = @data.get_multi(keys_to_names.keys, :raw => true) values = {} raw_values.each do |key, value| @@ -115,7 +115,7 @@ module ActiveSupport def increment(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) instrument(:increment, name, :amount => amount) do - @data.incr(escape_key(namespaced_key(name, options)), amount) + @data.incr(normalize_key(name, options), amount) end rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger @@ -129,7 +129,7 @@ module ActiveSupport def decrement(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) instrument(:decrement, name, :amount => amount) do - @data.decr(escape_key(namespaced_key(name, options)), amount) + @data.decr(normalize_key(name, options), amount) end rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger @@ -153,7 +153,7 @@ module ActiveSupport protected # Read an entry from the cache. def read_entry(key, options) # :nodoc: - deserialize_entry(@data.get(escape_key(key), options)) + deserialize_entry(@data.get(key, options)) rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger nil @@ -168,7 +168,7 @@ module ActiveSupport # Set the memcache expire a few minutes in the future to support race condition ttls on read expires_in += 5.minutes end - @data.send(method, escape_key(key), value, expires_in, options) + @data.send(method, key, value, expires_in, options) rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger false @@ -176,7 +176,7 @@ module ActiveSupport # Delete an entry from the cache. def delete_entry(key, options) # :nodoc: - @data.delete(escape_key(key)) + @data.delete(key) rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger false @@ -187,14 +187,22 @@ module ActiveSupport # Memcache keys are binaries. So we need to force their encoding to binary # before applying the regular expression to ensure we are escaping all # characters properly. - def escape_key(key) - key = key.to_s.dup + def normalize_key(key, options) + key = super.dup key = key.force_encoding(Encoding::ASCII_8BIT) key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" } key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 key end + def escape_key(key) + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `escape_key` is deprecated and will be removed from Rails 5.1. + Please use `normalize_key` which will return a fully resolved key or nothing. + MESSAGE + key + end + def deserialize_entry(raw_value) if raw_value entry = Marshal.load(raw_value) rescue raw_value diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 90bb2c38c3..896c28ad8b 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -75,30 +75,12 @@ module ActiveSupport # Increment an integer value in the cache. def increment(name, amount = 1, options = nil) - synchronize 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 # Decrement an integer value in the cache. def decrement(name, amount = 1, options = nil) - synchronize 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) @@ -167,6 +149,19 @@ module ActiveSupport !!entry end end + + private + + def modify_value(name, amount, options) + synchronize 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/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index d521061004..df38dbcf11 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -79,24 +79,28 @@ module ActiveSupport end def clear(options = nil) # :nodoc: - local_cache.clear(options) if local_cache + return super unless cache = local_cache + cache.clear(options) super end def cleanup(options = nil) # :nodoc: - local_cache.clear(options) if local_cache + return super unless cache = local_cache + cache.clear(options) super end def increment(name, amount = 1, options = nil) # :nodoc: + return super unless local_cache value = bypass_local_cache{super} - set_cache_value(value, name, amount, options) + write_cache_value(name, value, options) value end def decrement(name, amount = 1, options = nil) # :nodoc: + return super unless local_cache value = bypass_local_cache{super} - set_cache_value(value, name, amount, options) + write_cache_value(name, value, options) value end @@ -120,13 +124,21 @@ module ActiveSupport end def set_cache_value(value, name, amount, options) # :nodoc: - if local_cache - local_cache.mute do - if value - local_cache.write(name, value, options) - else - local_cache.delete(name, options) - end + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `set_cache_value` is deprecated and will be removed from Rails 5.1. + Please use `write_cache_value` + MESSAGE + write_cache_value name, value, options + end + + def write_cache_value(name, value, options) # :nodoc: + name = normalize_key(name, options) + cache = local_cache + cache.mute do + if value + cache.write(name, value, options) + else + cache.delete(name, options) end end end diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 3c4bfc5f1e..b0f9a8be34 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -3,7 +3,8 @@ require 'active_support/core_ext/module/reachable' class Class begin - ObjectSpace.each_object(Class.new) {} + # Test if this Ruby supports each_object against singleton_class + ObjectSpace.each_object(Numeric.singleton_class) {} def descendants # :nodoc: descendants = [] @@ -12,7 +13,7 @@ class Class end descendants end - rescue StandardError # JRuby + rescue StandardError # JRuby 9.0.4.0 and earlier def descendants # :nodoc: descendants = [] ObjectSpace.each_object(Class) do |k| diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index fc7531d088..8a74ad4d66 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -21,7 +21,7 @@ module Enumerable if block_given? map(&block).sum(identity) else - inject { |sum, element| sum + element } || identity + inject(:+) || identity end end diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index bf175a8a70..124f90dc0f 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -5,7 +5,7 @@ require 'active_support/core_ext/array/extract_options' # attributes. class Module # Defines a class attribute and creates a class and instance reader methods. - # The underlying the class variable is set to +nil+, if it is not previously + # The underlying class variable is set to +nil+, if it is not previously # defined. # # module HairColors diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index b2e713077c..cc71b8155d 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -164,8 +164,26 @@ class String # # <%= link_to(@person.name, person_path) %> # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> - def parameterize(sep = '-'.freeze) - ActiveSupport::Inflector.parameterize(self, sep) + # + # To preserve the case of the characters in a string, use the `preserve_case` argument. + # + # class Person + # def to_param + # "#{id}-#{name.parameterize(preserve_case: true)}" + # end + # end + # + # @person = Person.find(1) + # # => #<Person id: 1, name: "Donald E. Knuth"> + # + # <%= link_to(@person.name, person_path) %> + # # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a> + def parameterize(sep = :unused, separator: '-', preserve_case: false) + unless sep == :unused + ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.") + separator = sep + end + ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case) end # Creates the name of a table like Rails does for models to table names. This method diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 82e003fc3b..675db8a36b 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -26,6 +26,12 @@ class Time end end + # Returns the number of days in the given year. + # If no year is specified, it will use the current year. + def days_in_year(year = current.year) + days_in_month(2, year) + 337 + end + # Returns <tt>Time.zone.now</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns <tt>Time.now</tt>. def current ::Time.zone ? ::Time.zone.now : ::Time.now diff --git a/activesupport/lib/active_support/file_evented_update_checker.rb b/activesupport/lib/active_support/file_evented_update_checker.rb new file mode 100644 index 0000000000..bb0f26f874 --- /dev/null +++ b/activesupport/lib/active_support/file_evented_update_checker.rb @@ -0,0 +1,147 @@ +require 'listen' +require 'set' +require 'pathname' +require 'concurrent/atomic/atomic_boolean' + +module ActiveSupport + class FileEventedUpdateChecker #:nodoc: all + def initialize(files, dirs = {}, &block) + @ph = PathHelper.new + @files = files.map { |f| @ph.xpath(f) }.to_set + + @dirs = {} + dirs.each do |dir, exts| + @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) } + end + + @block = block + @updated = Concurrent::AtomicBoolean.new(false) + @lcsp = @ph.longest_common_subpath(@dirs.keys) + + if (dtw = directories_to_watch).any? + Listen.to(*dtw, &method(:changed)).start + end + end + + def updated? + @updated.true? + end + + def execute + @updated.make_false + @block.call + end + + def execute_if_updated + if updated? + execute + true + end + end + + private + + def changed(modified, added, removed) + unless updated? + @updated.make_true if (modified + added + removed).any? { |f| watching?(f) } + end + end + + def watching?(file) + file = @ph.xpath(file) + + if @files.member?(file) + true + elsif file.directory? + false + else + ext = @ph.normalize_extension(file.extname) + + file.dirname.ascend do |dir| + if @dirs.fetch(dir, []).include?(ext) + break true + elsif dir == @lcsp || dir.root? + break false + end + end + end + end + + def directories_to_watch + dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) } + dtw.compact! + dtw.uniq! + + @ph.filter_out_descendants(dtw) + end + + class PathHelper + using Module.new { + refine Pathname do + def ascendant_of?(other) + self != other && other.ascend do |ascendant| + break true if self == ascendant + end + end + end + } + + def xpath(path) + Pathname.new(path).expand_path + end + + def normalize_extension(ext) + ext.to_s.sub(/\A\./, '') + end + + # Given a collection of Pathname objects returns the longest subpath + # common to all of them, or +nil+ if there is none. + def longest_common_subpath(paths) + return if paths.empty? + + lcsp = Pathname.new(paths[0]) + + paths[1..-1].each do |path| + until lcsp.ascendant_of?(path) + if lcsp.root? + # If we get here a root directory is not an ascendant of path. + # This may happen if there are paths in different drives on + # Windows. + return + else + lcsp = lcsp.parent + end + end + end + + lcsp + end + + # Returns the deepest existing ascendant, which could be the argument itself. + def existing_parent(dir) + dir.ascend do |ascendant| + break ascendant if ascendant.directory? + end + end + + # Filters out directories which are descendants of others in the collection (stable). + def filter_out_descendants(dirs) + return dirs if dirs.length < 2 + + dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length } + descendants = [] + + until dirs_sorted_by_nparts.empty? + dir = dirs_sorted_by_nparts.shift + + dirs_sorted_by_nparts.reject! do |possible_descendant| + dir.ascendant_of?(possible_descendant) && descendants << possible_descendant + end + end + + # Array#- preserves order. + dirs - descendants + end + end + end +end diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 78b627c286..1fa9335080 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -35,7 +35,7 @@ module ActiveSupport # This method must also receive a block that will be called once a path # changes. The array of files and list of directories cannot be changed # after FileUpdateChecker has been initialized. - def initialize(files, dirs={}, &block) + def initialize(files, dirs = {}, &block) @files = files.freeze @glob = compile_glob(dirs) @block = block diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index 6775eec34b..82aacf3b24 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -56,7 +56,7 @@ module I18n I18n.enforce_available_locales = enforce_available_locales directories = watched_dirs_with_extensions(reloadable_paths) - reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup, directories) do + reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do I18n.load_path.keep_if { |p| File.exist?(p) } I18n.load_path |= reloadable_paths.map(&:existent).flatten diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 103207fb63..871cfb8a72 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -68,29 +68,44 @@ module ActiveSupport # # parameterize("Donald E. Knuth") # => "donald-e-knuth" # parameterize("^trés|Jolie-- ") # => "tres-jolie" - def parameterize(string, sep = '-') + # + # To use a custom separator, override the `separator` argument. + # + # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth" + # parameterize("^trés|Jolie-- ", separator: '_') # => "tres_jolie" + # + # To preserve the case of the characters in a string, use the `preserve_case` argument. + # + # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth" + # parameterize("^trés|Jolie-- ", preserve_case: true) # => "tres-Jolie" + # + def parameterize(string, sep = :unused, separator: '-', preserve_case: false) + unless sep == :unused + ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.") + separator = sep + end # Replace accented chars with their ASCII equivalents. parameterized_string = transliterate(string) # Turn unwanted chars into the separator. - parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep) + parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator) - unless sep.nil? || sep.empty? - if sep == "-".freeze + unless separator.nil? || separator.empty? + if separator == "-".freeze re_duplicate_separator = /-{2,}/ re_leading_trailing_separator = /^-|-$/i else - re_sep = Regexp.escape(sep) + re_sep = Regexp.escape(separator) re_duplicate_separator = /#{re_sep}{2,}/ re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i end # No more than one of the separator in a row. - parameterized_string.gsub!(re_duplicate_separator, sep) + parameterized_string.gsub!(re_duplicate_separator, separator) # Remove leading/trailing separator. parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze) end - - parameterized_string.downcase! + + parameterized_string.downcase! unless preserve_case parameterized_string end end diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index c82a13511e..2dde01c844 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -34,8 +34,8 @@ module ActiveSupport # Initialize a new MessageEncryptor. +secret+ must be at least as long as # the cipher key size. For the default 'aes-256-cbc' cipher, this is 256 # bits. If you are using a user-entered secret, you can generate a suitable - # key with <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or - # similar. + # key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key + # derivation function. # # Options: # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index 506dd950cb..a909a65bb6 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/module/delegation' + module ActiveSupport # This module is used to encapsulate access to thread local variables. # diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 3629f5e64b..7bef73136c 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -416,7 +416,7 @@ module CacheStoreBehavior def test_race_condition_protection_skipped_if_not_defined @cache.write('foo', 'bar') - time = @cache.send(:read_entry, 'foo', {}).expires_at + time = @cache.send(:read_entry, @cache.send(:normalize_key, 'foo', {}), {}).expires_at Time.stub(:now, Time.at(time)) do result = @cache.fetch('foo') do @@ -518,6 +518,12 @@ module CacheStoreBehavior ensure ActiveSupport::Notifications.unsubscribe "cache_read.active_support" end + + def test_can_call_deprecated_namesaced_key + assert_deprecated "`namespaced_key` is deprecated" do + @cache.send(:namespaced_key, 111, {}) + end + end end # https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters @@ -693,6 +699,15 @@ module LocalCacheBehavior app = @cache.middleware.new(app) app.call({}) end + + def test_can_call_deprecated_set_cache_value + @cache.with_local_cache do + assert_deprecated "`set_cache_value` is deprecated" do + @cache.send(:set_cache_value, 1, 'foo', :ignored, {}) + end + assert_equal 1, @cache.read('foo') + end + end end module AutoloadingCacheBehavior @@ -783,13 +798,13 @@ class FileStoreTest < ActiveSupport::TestCase end def test_key_transformation - key = @cache.send(:key_file_path, "views/index?id=1") + key = @cache.send(:normalize_key, "views/index?id=1", {}) assert_equal "views/index?id=1", @cache.send(:file_path_key, key) end def test_key_transformation_with_pathname FileUtils.touch(File.join(cache_dir, "foo")) - key = @cache_with_pathname.send(:key_file_path, "views/index?id=1") + key = @cache_with_pathname.send(:normalize_key, "views/index?id=1", {}) assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key) end @@ -797,7 +812,7 @@ class FileStoreTest < ActiveSupport::TestCase # remain valid def test_filename_max_size key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}" - path = @cache.send(:key_file_path, key) + path = @cache.send(:normalize_key, key, {}) Dir::Tmpname.create(path) do |tmpname, n, opts| assert File.basename(tmpname+'.lock').length <= 255, "Temp filename too long: #{File.basename(tmpname+'.lock').length}" end @@ -807,7 +822,7 @@ class FileStoreTest < ActiveSupport::TestCase # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B def test_key_transformation_max_filename_size key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B" - path = @cache.send(:key_file_path, key) + path = @cache.send(:normalize_key, key, {}) assert path.split('/').all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE} assert_equal 'B', File.basename(path) end @@ -858,6 +873,12 @@ class FileStoreTest < ActiveSupport::TestCase @cache.write(1, nil) assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) end + + def test_can_call_deprecated_key_file_path + assert_deprecated "`key_file_path` is deprecated" do + assert_equal 111, @cache.send(:key_file_path, 111) + end + end end class MemoryStoreTest < ActiveSupport::TestCase @@ -1032,6 +1053,12 @@ class MemCacheStoreTest < ActiveSupport::TestCase value << 'bingo' assert_not_equal value, @cache.read('foo') end + + def test_can_call_deprecated_escape_key + assert_deprecated "`escape_key` is deprecated" do + assert_equal 111, @cache.send(:escape_key, 111) + end + end end class NullStoreTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 9cc7bb1a77..2e69816364 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -143,15 +143,49 @@ class StringInflectionsTest < ActiveSupport::TestCase end end + def test_string_parameterized_normal_preserve_case + StringToParameterizedPreserveCase.each do |normal, slugged| + assert_equal(normal.parameterize(preserve_case: true), slugged) + end + end + def test_string_parameterized_no_separator StringToParameterizeWithNoSeparator.each do |normal, slugged| - assert_equal(normal.parameterize(''), slugged) + assert_equal(normal.parameterize(separator: ''), slugged) + end + end + + def test_string_parameterized_no_separator_deprecated + StringToParameterizeWithNoSeparator.each do |normal, slugged| + assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: ''` instead./i) do + assert_equal(normal.parameterize(''), slugged) + end + end + end + + def test_string_parameterized_no_separator_preserve_case + StringToParameterizePreserveCaseWithNoSeparator.each do |normal, slugged| + assert_equal(normal.parameterize(separator: '', preserve_case: true), slugged) end end def test_string_parameterized_underscore StringToParameterizeWithUnderscore.each do |normal, slugged| - assert_equal(normal.parameterize('_'), slugged) + assert_equal(normal.parameterize(separator: '_'), slugged) + end + end + + def test_string_parameterized_underscore_deprecated + StringToParameterizeWithUnderscore.each do |normal, slugged| + assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do + assert_equal(normal.parameterize('_'), slugged) + end + end + end + + def test_string_parameterized_underscore_preserve_case + StringToParameterizePreserceCaseWithUnderscore.each do |normal, slugged| + assert_equal(normal.parameterize(separator: '_', preserve_case: true), slugged) end end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 2d0fb70a6b..e45df63fd5 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -617,6 +617,25 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end end + def test_days_in_year_with_year + assert_equal 365, Time.days_in_year(2005) + assert_equal 366, Time.days_in_year(2004) + assert_equal 366, Time.days_in_year(2000) + assert_equal 365, Time.days_in_year(1900) + end + + def test_days_in_year_in_common_year_without_year_arg + Time.stub(:now, Time.utc(2007)) do + assert_equal 365, Time.days_in_year + end + end + + def test_days_in_year_in_leap_year_without_year_arg + Time.stub(:now, Time.utc(2008)) do + assert_equal 366, Time.days_in_year + end + end + def test_last_month_on_31st assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month end diff --git a/activesupport/test/file_evented_update_checker_test.rb b/activesupport/test/file_evented_update_checker_test.rb new file mode 100644 index 0000000000..071449d399 --- /dev/null +++ b/activesupport/test/file_evented_update_checker_test.rb @@ -0,0 +1,150 @@ +require 'abstract_unit' +require 'pathname' +require 'file_update_checker_shared_tests' + +class FileEventedUpdateCheckerTest < ActiveSupport::TestCase + include FileUpdateCheckerSharedTests + + def new_checker(files = [], dirs = {}, &block) + ActiveSupport::FileEventedUpdateChecker.new(files, dirs, &block).tap do + wait + end + end + + def teardown + super + Listen.stop + end + + def wait + sleep 1 + end + + def touch(files) + super + wait # wait for the events to fire + end + + def rm_f(files) + super + wait + end +end + +class FileEventedUpdateCheckerPathHelperTest < ActiveSupport::TestCase + def pn(path) + Pathname.new(path) + end + + setup do + @ph = ActiveSupport::FileEventedUpdateChecker::PathHelper.new + end + + test '#xpath returns the expanded path as a Pathname object' do + assert_equal pn(__FILE__).expand_path, @ph.xpath(__FILE__) + end + + test '#normalize_extension returns a bare extension as is' do + assert_equal 'rb', @ph.normalize_extension('rb') + end + + test '#normalize_extension removes a leading dot' do + assert_equal 'rb', @ph.normalize_extension('.rb') + end + + test '#normalize_extension supports symbols' do + assert_equal 'rb', @ph.normalize_extension(:rb) + end + + test '#longest_common_subpath finds the longest common subpath, if there is one' do + paths = %w( + /foo/bar + /foo/baz + /foo/bar/baz/woo/zoo + ).map { |path| pn(path) } + + assert_equal pn('/foo'), @ph.longest_common_subpath(paths) + end + + test '#longest_common_subpath returns the root directory as an edge case' do + paths = %w( + /foo/bar + /foo/baz + /foo/bar/baz/woo/zoo + /wadus + ).map { |path| pn(path) } + + assert_equal pn('/'), @ph.longest_common_subpath(paths) + end + + test '#longest_common_subpath returns nil for an empty collection' do + assert_nil @ph.longest_common_subpath([]) + end + + test '#existing_parent returns the most specific existing ascendant' do + wd = Pathname.getwd + + assert_equal wd, @ph.existing_parent(wd) + assert_equal wd, @ph.existing_parent(wd.join('non-existing/directory')) + assert_equal pn('/'), @ph.existing_parent(pn('/non-existing/directory')) + end + + test '#filter_out_descendants returns the same collection if there are no descendants (empty)' do + assert_equal [], @ph.filter_out_descendants([]) + end + + test '#filter_out_descendants returns the same collection if there are no descendants (one)' do + assert_equal ['/foo'], @ph.filter_out_descendants(['/foo']) + end + + test '#filter_out_descendants returns the same collection if there are no descendants (several)' do + paths = %w( + /Rails.root/app/controllers + /Rails.root/app/models + /Rails.root/app/helpers + ).map { |path| pn(path) } + + assert_equal paths, @ph.filter_out_descendants(paths) + end + + test '#filter_out_descendants filters out descendants preserving order' do + paths = %w( + /Rails.root/app/controllers + /Rails.root/app/controllers/concerns + /Rails.root/app/models + /Rails.root/app/models/concerns + /Rails.root/app/helpers + ).map { |path| pn(path) } + + assert_equal paths.values_at(0, 2, 4), @ph.filter_out_descendants(paths) + end + + test '#filter_out_descendants works on path units' do + paths = %w( + /foo/bar + /foo/barrrr + ).map { |path| pn(path) } + + assert_equal paths, @ph.filter_out_descendants(paths) + end + + test '#filter_out_descendants deals correctly with the root directory' do + paths = %w( + / + /foo + /foo/bar + ).map { |path| pn(path) } + + assert_equal paths.values_at(0), @ph.filter_out_descendants(paths) + end + + test '#filter_out_descendants preserves duplicates' do + paths = %w( + /foo + /foo/bar + /foo + ).map { |path| pn(path) } + + assert_equal paths.values_at(0, 2), @ph.filter_out_descendants(paths) + end +end diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb new file mode 100644 index 0000000000..100cbc9756 --- /dev/null +++ b/activesupport/test/file_update_checker_shared_tests.rb @@ -0,0 +1,241 @@ +require 'fileutils' + +module FileUpdateCheckerSharedTests + extend ActiveSupport::Testing::Declarative + include FileUtils + + def tmpdir + @tmpdir ||= Dir.mktmpdir(nil, __dir__) + end + + def tmpfile(name) + "#{tmpdir}/#{name}" + end + + def tmpfiles + @tmpfiles ||= %w(foo.rb bar.rb baz.rb).map { |f| tmpfile(f) } + end + + def teardown + FileUtils.rm_rf(@tmpdir) if defined? @tmpdir + end + + test 'should not execute the block if no paths are given' do + i = 0 + + checker = new_checker { i += 1 } + + assert !checker.execute_if_updated + assert_equal 0, i + end + + test 'should not execute the block if no files change' do + i = 0 + + FileUtils.touch(tmpfiles) + + checker = new_checker(tmpfiles) { i += 1 } + + assert !checker.execute_if_updated + assert_equal 0, i + end + + test 'should execute the block once when files are created' do + i = 0 + + checker = new_checker(tmpfiles) { i += 1 } + + touch(tmpfiles) + + assert checker.execute_if_updated + assert_equal 1, i + end + + test 'should execute the block once when files are modified' do + i = 0 + + FileUtils.touch(tmpfiles) + + checker = new_checker(tmpfiles) { i += 1 } + + touch(tmpfiles) + + assert checker.execute_if_updated + assert_equal 1, i + end + + test 'should execute the block once when files are deleted' do + i = 0 + + FileUtils.touch(tmpfiles) + + checker = new_checker(tmpfiles) { i += 1 } + + rm_f(tmpfiles) + + assert checker.execute_if_updated + assert_equal 1, i + end + + + test 'updated should become true when watched files are created' do + i = 0 + + checker = new_checker(tmpfiles) { i += 1 } + assert !checker.updated? + + touch(tmpfiles) + + assert checker.updated? + end + + + test 'updated should become true when watched files are modified' do + i = 0 + + FileUtils.touch(tmpfiles) + + checker = new_checker(tmpfiles) { i += 1 } + assert !checker.updated? + + touch(tmpfiles) + + assert checker.updated? + end + + test 'updated should become true when watched files are deleted' do + i = 0 + + FileUtils.touch(tmpfiles) + + checker = new_checker(tmpfiles) { i += 1 } + assert !checker.updated? + + rm_f(tmpfiles) + + assert checker.updated? + end + + test 'should be robust to handle files with wrong modified time' do + i = 0 + + FileUtils.touch(tmpfiles) + + now = Time.now + time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future + File.utime(time, time, tmpfiles[0]) + + checker = new_checker(tmpfiles) { i += 1 } + + touch(tmpfiles[1..-1]) + + assert checker.execute_if_updated + assert_equal 1, i + end + + test 'should cache updated result until execute' do + i = 0 + + checker = new_checker(tmpfiles) { i += 1 } + assert !checker.updated? + + touch(tmpfiles) + + assert checker.updated? + checker.execute + assert !checker.updated? + end + + test 'should execute the block if files change in a watched directory one extension' do + i = 0 + + checker = new_checker([], tmpdir => :rb) { i += 1 } + + touch(tmpfile('foo.rb')) + + assert checker.execute_if_updated + assert_equal 1, i + end + + test 'should execute the block if files change in a watched directory several extensions' do + i = 0 + + checker = new_checker([], tmpdir => [:rb, :txt]) { i += 1 } + + touch(tmpfile('foo.rb')) + + assert checker.execute_if_updated + assert_equal 1, i + + touch(tmpfile('foo.txt')) + + assert checker.execute_if_updated + assert_equal 2, i + end + + test 'should not execute the block if the file extension is not watched' do + i = 0 + + checker = new_checker([], tmpdir => :txt) { i += 1 } + + touch(tmpfile('foo.rb')) + + assert !checker.execute_if_updated + assert_equal 0, i + end + + test 'does not assume files exist on instantiation' do + i = 0 + + non_existing = tmpfile('non_existing.rb') + checker = new_checker([non_existing]) { i += 1 } + + touch(non_existing) + + assert checker.execute_if_updated + assert_equal 1, i + end + + test 'detects files in new subdirectories' do + i = 0 + + checker = new_checker([], tmpdir => :rb) { i += 1 } + + subdir = tmpfile('subdir') + mkdir(subdir) + wait + + assert !checker.execute_if_updated + assert_equal 0, i + + touch("#{subdir}/nested.rb") + + assert checker.execute_if_updated + assert_equal 1, i + end + + test 'looked up extensions are inherited in subdirectories not listening to them' do + i = 0 + + subdir = tmpfile('subdir') + mkdir(subdir) + + checker = new_checker([], tmpdir => :rb, subdir => :txt) { i += 1 } + + touch(tmpfile('new.txt')) + + assert !checker.execute_if_updated + assert_equal 0, i + + # subdir does not look for Ruby files, but its parent tmpdir does. + touch("#{subdir}/nested.rb") + + assert checker.execute_if_updated + assert_equal 1, i + + touch("#{subdir}/nested.txt") + + assert checker.execute_if_updated + assert_equal 2, i + end +end diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb index bd1df0f858..752f7836cd 100644 --- a/activesupport/test/file_update_checker_test.rb +++ b/activesupport/test/file_update_checker_test.rb @@ -1,112 +1,19 @@ require 'abstract_unit' -require 'fileutils' -require 'thread' +require 'file_update_checker_shared_tests' -MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__) +class FileUpdateCheckerTest < ActiveSupport::TestCase + include FileUpdateCheckerSharedTests -class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase - FILES = %w(1.txt 2.txt 3.txt) - - def setup - FileUtils.mkdir_p("tmp_watcher") - FileUtils.touch(FILES) - end - - def teardown - FileUtils.rm_rf("tmp_watcher") - FileUtils.rm_rf(FILES) - end - - def test_should_not_execute_the_block_if_no_paths_are_given - i = 0 - checker = ActiveSupport::FileUpdateChecker.new([]){ i += 1 } - checker.execute_if_updated - assert_equal 0, i - end - - def test_should_not_invoke_the_block_if_no_file_has_changed - i = 0 - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - 5.times { assert !checker.execute_if_updated } - assert_equal 0, i - end - - def test_should_invoke_the_block_if_a_file_has_changed - i = 0 - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - sleep(1) - FileUtils.touch(FILES) - assert checker.execute_if_updated - assert_equal 1, i - end - - def test_should_be_robust_enough_to_handle_deleted_files - i = 0 - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - FileUtils.rm(FILES) - assert checker.execute_if_updated - assert_equal 1, i - end - - def test_should_be_robust_to_handle_files_with_wrong_modified_time - i = 0 - now = Time.now - time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future - File.utime time, time, FILES[2] - - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - - sleep(1) - FileUtils.touch(FILES[0..1]) - - assert checker.execute_if_updated - assert_equal 1, i - end - - def test_should_cache_updated_result_until_execute - i = 0 - checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } - assert !checker.updated? - - sleep(1) - FileUtils.touch(FILES) - - assert checker.updated? - checker.execute - assert !checker.updated? - end - - def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob - i = 0 - checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => [:txt]){ i += 1 } - FileUtils.cd "tmp_watcher" do - FileUtils.touch(FILES) - end - assert checker.execute_if_updated - assert_equal 1, i + def new_checker(files = [], dirs = {}, &block) + ActiveSupport::FileUpdateChecker.new(files, dirs, &block) end - def test_should_not_invoke_the_block_if_a_watched_dir_changed_its_glob - i = 0 - checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => :rb){ i += 1 } - FileUtils.cd "tmp_watcher" do - FileUtils.touch(FILES) - end - assert !checker.execute_if_updated - assert_equal 0, i + def wait + # noop end - def test_should_not_block_if_a_strange_filename_used - FileUtils.mkdir_p("tmp_watcher/valid,yetstrange,path,") - FileUtils.touch(FILES.map { |file_name| "tmp_watcher/valid,yetstrange,path,/#{file_name}" }) - - test = Thread.new do - ActiveSupport::FileUpdateChecker.new([],"tmp_watcher/valid,yetstrange,path," => :txt) { i += 1 } - Thread.exit - end - test.priority = -1 - test.join(5) - - assert !test.alive? + def touch(files) + sleep 1 # let's wait a bit to ensure there's a new mtime + super end end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index a0764f6d6b..06cd41c86d 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -269,14 +269,32 @@ class InflectorTest < ActiveSupport::TestCase def test_parameterize_with_custom_separator jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| - assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_')) + assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, separator: '_')) + end + end + + def test_parameterize_with_custom_separator_deprecated + jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" + StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| + assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do + assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_')) + end end end def test_parameterize_with_multi_character_separator jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterized.each do |some_string, parameterized_string| - assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__')) + assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, separator: '__sep__')) + end + end + + def test_parameterize_with_multi_character_separator_deprecated + jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" + StringToParameterized.each do |some_string, parameterized_string| + assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '__sep__'` instead./i) do + assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__')) + end end end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index e6898658b5..14fe97a986 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -174,6 +174,17 @@ module InflectorTestCases "Test with malformed utf8 \251" => "test-with-malformed-utf8" } + StringToParameterizedPreserveCase = { + "Donald E. Knuth" => "Donald-E-Knuth", + "Random text with *(bad)* characters" => "Random-text-with-bad-characters", + "Allow_Under_Scores" => "Allow_Under_Scores", + "Trailing bad characters!@#" => "Trailing-bad-characters", + "!@#Leading bad characters" => "Leading-bad-characters", + "Squeeze separators" => "Squeeze-separators", + "Test with + sign" => "Test-with-sign", + "Test with malformed utf8 \xA9" => "Test-with-malformed-utf8" + } + StringToParameterizeWithNoSeparator = { "Donald E. Knuth" => "donaldeknuth", "With-some-dashes" => "with-some-dashes", @@ -185,6 +196,17 @@ module InflectorTestCases "Test with malformed utf8 \251" => "testwithmalformedutf8" } + StringToParameterizePreserveCaseWithNoSeparator = { + "Donald E. Knuth" => "DonaldEKnuth", + "With-some-dashes" => "With-some-dashes", + "Random text with *(bad)* characters" => "Randomtextwithbadcharacters", + "Trailing bad characters!@#" => "Trailingbadcharacters", + "!@#Leading bad characters" => "Leadingbadcharacters", + "Squeeze separators" => "Squeezeseparators", + "Test with + sign" => "Testwithsign", + "Test with malformed utf8 \xA9" => "Testwithmalformedutf8" + } + StringToParameterizeWithUnderscore = { "Donald E. Knuth" => "donald_e_knuth", "Random text with *(bad)* characters" => "random_text_with_bad_characters", @@ -197,6 +219,18 @@ module InflectorTestCases "Test with malformed utf8 \251" => "test_with_malformed_utf8" } + StringToParameterizePreserceCaseWithUnderscore = { + "Donald E. Knuth" => "Donald_E_Knuth", + "Random text with *(bad)* characters" => "Random_text_with_bad_characters", + "With-some-dashes" => "With-some-dashes", + "Allow_Under_Scores" => "Allow_Under_Scores", + "Trailing bad characters!@#" => "Trailing_bad_characters", + "!@#Leading bad characters" => "Leading_bad_characters", + "Squeeze separators" => "Squeeze_separators", + "Test with + sign" => "Test_with_sign", + "Test with malformed utf8 \xA9" => "Test_with_malformed_utf8" + } + StringToParameterizedAndNormalized = { "Malmö" => "malmo", "Garçons" => "garcons", |