diff options
Diffstat (limited to 'activesupport')
45 files changed, 660 insertions, 279 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 6b7044aeae..034d03533f 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,6 +1,14 @@ *Rails 3.2.0 (unreleased)* -* Removed ActiveSupport::SecureRandom in favour of SecureRandom from the standard library [Jon Leighton] +* The inflector understands acronyms. [dlee] + +* Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern [José Valim] + +* Added Time#all_day/week/quarter/year as a way of generating ranges (example: Event.where(created_at: Time.now.all_week)) [DHH] + +* Added instance_accessor: false as an option to Class#cattr_accessor and friends [DHH] + +* Removed ActiveSupport::SecureRandom in favor of SecureRandom from the standard library [Jon Leighton] * ActiveSupport::OrderedHash now has different behavior for #each and #each_pair when given a block accepting its parameters with a splat. [Andrew Radev] diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 0e6bc30fa2..8f8deb9692 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -1,12 +1,12 @@ module ActiveSupport - # Many backtraces include too much information that's not relevant for the context. This makes it hard to find the signal - # in the backtrace and adds debugging time. With a BacktraceCleaner, you can setup filters and silencers for your particular - # context, so only the relevant lines are included. + # Backtraces often include many lines that are not relevant for the context under review. This makes it hard to find the + # signal amongst the backtrace noise, and adds debugging time. With a BacktraceCleaner, filters and silencers are used to + # remove the noisy lines, so that only the most relevant lines remain. # - # If you need to reconfigure an existing BacktraceCleaner, like the one in Rails, to show as much as possible, you can always - # call BacktraceCleaner#remove_silencers! Also, 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 BacktraceCleaner#remove_filters! These two methods - # will give you a completely untouched backtrace. + # Filters are used to modify lines of data, while silencers are used to remove lines entirely. The typical filter use case + # is to remove lengthy path information from the start of each line, and view file paths relevant to the app directory + # instead of the file system root. The typical silencer use case is to exclude the output of a noisy library from the + # backtrace, so that you can focus on the rest. # # ==== Example: # @@ -15,13 +15,18 @@ module ActiveSupport # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems # + # To reconfigure an existing BacktraceCleaner (like the default one in Rails) and show as much data as possible, you can + # always call <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 BacktraceCleaner#remove_filters! These two methods will give you a completely untouched backtrace. + # # Inspired by the Quiet Backtrace gem by Thoughtbot. class BacktraceCleaner def initialize @filters, @silencers = [], [] end - # Returns the backtrace after all filters and silencers has been run against it. Filters run first, then silencers. + # Returns the backtrace after all filters and silencers have been run against it. Filters run first, then silencers. def clean(backtrace, kind = :silent) filtered = filter(backtrace) @@ -45,8 +50,8 @@ module ActiveSupport @filters << block end - # Adds a silencer from the block provided. If the silencer returns true for a given line, it'll be excluded from the - # clean backtrace. + # Adds a silencer from the block provided. If the silencer returns true for a given line, it will be excluded from + # the clean backtrace. # # Example: # @@ -57,7 +62,7 @@ module ActiveSupport end # Will remove all silencers, but leave in the filters. This is useful if your context of debugging suddenly expands as - # you suspect a bug in the libraries you use. + # you suspect a bug in one of the libraries you use. def remove_silencers! @silencers = [] end diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index e303eaa5ce..cc94041a1d 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -3,28 +3,28 @@ require 'active_support/core_ext/hash/keys' module ActiveSupport module Benchmarkable - # Allows you to measure the execution time of a block - # in a template and records the result to the log. Wrap this block around - # expensive operations or possible bottlenecks to get a time reading - # for the operation. For example, let's say you thought your file - # processing method was taking too long; you could wrap it in a benchmark block. + # Allows you to measure the execution time of a block in a template and records the result to + # the log. Wrap this block around expensive operations or possible bottlenecks to get a time + # reading for the operation. For example, let's say you thought your file processing method + # was taking too long; you could wrap it in a benchmark block. # # <% benchmark "Process data files" do %> # <%= expensive_files_operation %> # <% end %> # - # That would add something like "Process data files (345.2ms)" to the log, - # which you can then use to compare timings when optimizing your code. + # That would add something like "Process data files (345.2ms)" to the log, which you can then + # use to compare timings when optimizing your code. # - # You may give an optional logger level as the :level option. - # (:debug, :info, :warn, :error); the default value is :info. + # You may give an optional logger level (:debug, :info, :warn, :error) as the :level option. + # The default logger level value is :info. # # <% benchmark "Low-level files", :level => :debug do %> # <%= lowlevel_files_operation %> # <% end %> # - # Finally, you can pass true as the third argument to silence all log activity - # inside the block. This is great for boiling down a noisy block to just a single statement: + # Finally, you can pass true as the third argument to silence all log activity (other than the + # timing information) from inside the block. This is great for boiling down a noisy block to + # just a single statement that produces one log line: # # <% benchmark "Process data files", :level => :info, :silence => true do %> # <%= expensive_and_chatty_files_operation %> diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 10c457bb1d..85692428e3 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -116,31 +116,32 @@ module ActiveSupport # cache.read("city") # => "Duckburgh" # # Keys are always translated into Strings and are case sensitive. When an - # object is specified as a key, its +cache_key+ method will be called if it - # is defined. Otherwise, the +to_param+ method will be called. Hashes and - # Arrays can be used as keys. The elements will be delimited by slashes - # and Hashes elements will be sorted by key so they are consistent. + # object is specified as a key and has a +cache_key+ method defined, this + # method will be called to define the key. Otherwise, the +to_param+ + # method will be called. Hashes and Arrays can also be used as keys. The + # elements will be delimited by slashes, and the elements within a Hash + # will be sorted by key so they are consistent. # # cache.read("city") == cache.read(:city) # => true # # Nil values can be cached. # - # If your cache is on a shared infrastructure, you can define a namespace for - # your cache entries. If a namespace is defined, it will be prefixed on to every - # key. The namespace can be either a static value or a Proc. If it is a Proc, it - # will be invoked when each key is evaluated so that you can use application logic - # to invalidate keys. + # If your cache is on a shared infrastructure, you can define a namespace + # for your cache entries. If a namespace is defined, it will be prefixed on + # to every key. The namespace can be either a static value or a Proc. If it + # is a Proc, it will be invoked when each key is evaluated so that you can + # use application logic to invalidate keys. # # cache.namespace = lambda { @last_mod_time } # Set the namespace to a variable # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace # # - # Caches can also store values in a compressed format to save space and reduce - # time spent sending data. Since there is some overhead, values must be large - # enough to warrant compression. To turn on compression either pass - # <tt>:compress => true</tt> in the initializer or to +fetch+ or +write+. - # To specify the threshold at which to compress values, set - # <tt>:compress_threshold</tt>. The default threshold is 32K. + # Caches can also store values in a compressed format to save space and + # reduce time spent sending data. Since there is overhead, values must be + # large enough to warrant compression. To turn on compression either pass + # <tt>:compress => true</tt> in the initializer or as an option to +fetch+ + # or +write+. To specify the threshold at which to compress values, set the + # <tt>:compress_threshold</tt> option. The default threshold is 32K. class Store cattr_accessor :logger, :instance_writer => true @@ -180,11 +181,11 @@ module ActiveSupport # Fetches data from the cache, using the given key. If there is data in # the cache with the given key, then that data is returned. # - # If there is no such data in the cache (a cache miss occurred), - # then nil will be returned. However, if a block has been passed, then - # that block will be run in the event of a cache miss. The return value - # of the block will be written to the cache under the given cache key, - # and that return value will be returned. + # If there is no such data in the cache (a cache miss), then nil will be + # returned. However, if a block has been passed, that block will be run + # in the event of a cache miss. The return value of the block will be + # written to the cache under the given cache key, and that return value + # will be returned. # # cache.write("today", "Monday") # cache.fetch("today") # => "Monday" @@ -205,10 +206,11 @@ module ActiveSupport # in a compressed format. # # - # Setting <tt>:expires_in</tt> will set an expiration time on the cache. All caches - # support auto expiring content after a specified number of seconds. This value can - # be specified as an option to the construction in which call all entries will be - # affected. Or it can be supplied to the +fetch+ or +write+ method for just one entry. + # Setting <tt>:expires_in</tt> will set an expiration time on the cache. + # All caches support auto-expiring content after a specified number of + # seconds. This value can be specified as an option to the constructor + # (in which case all entries will be affected), or it can be supplied to + # the +fetch+ or +write+ method to effect just one entry. # # cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 5.minutes) # cache.write(key, value, :expires_in => 1.minute) # Set a lower value for one entry diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index a94446acde..a2d2719de7 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -12,12 +12,12 @@ module ActiveSupport class Configuration < ActiveSupport::InheritableOptions def compile_methods! - self.class.compile_methods!(keys.reject {|key| respond_to?(key)}) + self.class.compile_methods!(keys) end # compiles reader methods so we don't have to go through method_missing def self.compile_methods!(keys) - keys.each do |key| + keys.reject { |m| method_defined?(m) }.each do |key| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{key}; _get(#{key.inspect}); end RUBY diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index 7fabae3138..4834eca8b1 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -14,7 +14,7 @@ class Array # This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences: # # * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt> - # moves on to try +to_a+ if the returned value is +nil+, but <tt>Arraw.wrap</tt> returns + # moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns # such a +nil+ right away. # * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt> # raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value. @@ -40,7 +40,7 @@ class Array if object.nil? [] elsif object.respond_to?(:to_ary) - object.to_ary + object.to_ary || [object] else [object] end diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 7baba75ad3..ca9b2c1b60 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/array/extract_options' class Class # Declare a class-level attribute whose value is inheritable by subclasses. @@ -56,11 +57,18 @@ class Class # object.setting # => false # Base.setting # => true # + # To opt out of the instance reader method, pass :instance_reader => false. + # + # object.setting # => NoMethodError + # object.setting? # => NoMethodError + # # To opt out of the instance writer method, pass :instance_writer => false. # # object.setting = false # => NoMethodError def class_attribute(*attrs) - instance_writer = !attrs.last.is_a?(Hash) || attrs.pop[:instance_writer] + options = attrs.extract_options! + instance_reader = options.fetch(:instance_reader, true) + instance_writer = options.fetch(:instance_writer, true) attrs.each do |name| class_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -84,13 +92,15 @@ class Class val end - remove_possible_method :#{name} - def #{name} - defined?(@#{name}) ? @#{name} : self.class.#{name} - end + if instance_reader + remove_possible_method :#{name} + def #{name} + defined?(@#{name}) ? @#{name} : self.class.#{name} + end - def #{name}? - !!#{name} + def #{name}? + !!#{name} + end end RUBY diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index a903735acf..268303aaf2 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -17,6 +17,7 @@ require 'active_support/core_ext/array/extract_options' # # To opt out of the instance writer method, pass :instance_writer => false. # To opt out of the instance reader method, pass :instance_reader => false. +# To opt out of both instance methods, pass :instance_accessor => false. # # class Person # cattr_accessor :hair_colors, :instance_writer => false, :instance_reader => false @@ -38,7 +39,7 @@ class Class end EOS - unless options[:instance_reader] == false + unless options[:instance_reader] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} @@#{sym} @@ -61,7 +62,7 @@ class Class end EOS - unless options[:instance_writer] == false + unless options[:instance_writer] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym}=(obj) @@#{sym} = obj diff --git a/activesupport/lib/active_support/core_ext/date/freeze.rb b/activesupport/lib/active_support/core_ext/date/freeze.rb index 4edd715ba2..a731f8345e 100644 --- a/activesupport/lib/active_support/core_ext/date/freeze.rb +++ b/activesupport/lib/active_support/core_ext/date/freeze.rb @@ -5,7 +5,7 @@ # first call will result in a frozen object error since the memo # instance variable is uninitialized. # -# Work around by eagerly memoizing before freezing. +# Work around by eagerly memoizing before the first freeze. # # Ruby 1.9 uses a preinitialized instance variable so it's unaffected. # This hack is as close as we can get to feature detection: @@ -17,9 +17,11 @@ if RUBY_VERSION < '1.9' if frozen_object_error.message =~ /frozen/ class Date #:nodoc: def freeze - self.class.private_instance_methods(false).each do |m| - if m.to_s =~ /\A__\d+__\Z/ - instance_variable_set(:"@#{m}", [send(m)]) + unless frozen? + self.class.private_instance_methods(false).each do |m| + if m.to_s =~ /\A__\d+__\Z/ + instance_variable_set(:"@#{m}", [send(m)]) + end end end diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index c2a6476604..0b368fe7b7 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -11,7 +11,7 @@ class Hash end # Called when object is nested under an object that receives - # #with_indifferent_access. This method with be called on the current object + # #with_indifferent_access. This method will be called on the current object # by the enclosing object and is aliased to #with_indifferent_access by # default. Subclasses of Hash may overwrite this method to return +self+ if # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index c6920098a8..526b8378a5 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -59,7 +59,7 @@ module Kernel raise unless exception_classes.any? { |cls| e.kind_of?(cls) } end end - + # Captures the given stream and returns it: # # stream = capture(:stdout) { puts "Cool" } 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 871f5cef3b..be94ae1565 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -12,7 +12,7 @@ class Module end EOS - unless options[:instance_reader] == false + unless options[:instance_reader] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} @@#{sym} @@ -31,7 +31,7 @@ class Module end EOS - unless options[:instance_writer] == false + unless options[:instance_writer] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym}=(obj) @@#{sym} = obj @@ -53,6 +53,10 @@ class Module # end # # AppConfiguration.google_api_key = "overriding the api key!" + # + # To opt out of the instance writer method, pass :instance_writer => false. + # To opt out of the instance reader method, pass :instance_reader => false. + # To opt out of both instance methods, pass :instance_accessor => false. def mattr_accessor(*syms) mattr_reader(*syms) mattr_writer(*syms) diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 3a7652f5bf..1777a4b32d 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -113,7 +113,7 @@ class Module raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." end - prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_" || '' + prefix = options[:prefix] ? "#{options[:prefix] == true ? to : options[:prefix]}_" : '' file, line = caller.first.split(':', 2) line = line.to_i diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 2f0676f567..002688d6c0 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -1,5 +1,4 @@ require 'active_support/inflector/methods' -require 'active_support/inflector/inflections' require 'active_support/inflector/transliterate' # String inflections define new methods on the String class to transform names for different purposes. 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 c27cbc37c5..3bf4edbdef 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -51,7 +51,8 @@ class ERB # <%=j @person.to_json %> # def json_escape(s) - s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + result = s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + s.html_safe? ? result.html_safe : result end alias j json_escape @@ -66,7 +67,7 @@ class Object end end -class Fixnum +class Numeric def html_safe? true end @@ -74,10 +75,34 @@ end module ActiveSupport #:nodoc: class SafeBuffer < String - alias safe_concat concat + UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase"].freeze + + alias_method :original_concat, :concat + private :original_concat + + class SafeConcatError < StandardError + def initialize + super "Could not concatenate to the buffer because it is not html safe." + end + end + + def safe_concat(value) + raise SafeConcatError if dirty? + original_concat(value) + end + + def initialize(*) + @dirty = false + super + end + + def initialize_copy(other) + super + @dirty = other.dirty? + end def concat(value) - if value.html_safe? + if dirty? || value.html_safe? super(value) else super(ERB::Util.h(value)) @@ -90,15 +115,15 @@ module ActiveSupport #:nodoc: end def html_safe? - true + !dirty? end - def html_safe + def to_s self end - def to_s - self + def to_param + to_str end def encode_with(coder) @@ -107,17 +132,31 @@ module ActiveSupport #:nodoc: def to_yaml(*args) return super() if defined?(YAML::ENGINE) && !YAML::ENGINE.syck? - to_str.to_yaml(*args) end + + UNSAFE_STRING_METHODS.each do |unsafe_method| + class_eval <<-EOT, __FILE__, __LINE__ + def #{unsafe_method}(*args, &block) # def gsub(*args, &block) + to_str.#{unsafe_method}(*args, &block) # to_str.gsub(*args, &block) + end # end + + def #{unsafe_method}!(*args) # def gsub!(*args) + @dirty = true # @dirty = true + super # super + end # end + EOT + end + + protected + + def dirty? + @dirty + end end end class String - def html_safe! - raise "You can't call html_safe! on a String" - end - def html_safe ActiveSupport::SafeBuffer.new(self) end diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 00fda2b370..c5fbbcf890 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -226,7 +226,7 @@ class Time end alias :at_end_of_quarter :end_of_quarter - # Returns a new Time representing the start of the year (1st of january, 0:00) + # Returns a new Time representing the start of the year (1st of january, 0:00) def beginning_of_year change(:month => 1, :day => 1, :hour => 0) end @@ -248,6 +248,31 @@ class Time advance(:days => 1) end + # Returns a Range representing the whole day of the current time. + def all_day + beginning_of_day..end_of_day + end + + # Returns a Range representing the whole week of the current time. + def all_week + beginning_of_week..end_of_week + end + + # Returns a Range representing the whole month of the current time. + def all_month + beginning_of_month..end_of_month + end + + # Returns a Range representing the whole quarter of the current time. + def all_quarter + beginning_of_quarter..end_of_quarter + end + + # Returns a Range representing the whole year of the current time. + def all_year + beginning_of_year..end_of_year + end + def plus_with_duration(other) #:nodoc: if ActiveSupport::Duration === other other.since(self) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 26c5c157cb..d1543c4c58 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -628,17 +628,6 @@ module ActiveSupport #:nodoc: return [] end - class LoadingModule #:nodoc: - # Old style environment.rb referenced this method directly. Please note, it doesn't - # actually *do* anything any more. - def self.root(*args) - if defined?(Rails) && Rails.logger - Rails.logger.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases." - Rails.logger.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19" - end - end - end - # Convert the provided const desc to a qualified constant name (as a string). # A module, class, symbol, or string may be provided. def to_constant_name(desc) #:nodoc: diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index d5d55b7207..90bb62f57b 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -20,10 +20,61 @@ module ActiveSupport @__instance__ ||= new end - attr_reader :plurals, :singulars, :uncountables, :humans + attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex def initialize - @plurals, @singulars, @uncountables, @humans = [], [], [], [] + @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/ + end + + # Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore + # string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`. + # A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will + # convert the acronym into a non-delimited single lowercase word when passed to +underscore+. + # + # Examples: + # acronym 'HTML' + # titleize 'html' #=> 'HTML' + # camelize 'html' #=> 'HTML' + # underscore 'MyHTML' #=> 'my_html' + # + # The acronym, however, must occur as a delimited unit and not be part of another word for conversions to recognize it: + # + # acronym 'HTTP' + # camelize 'my_http_delimited' #=> 'MyHTTPDelimited' + # camelize 'https' #=> 'Https', not 'HTTPs' + # underscore 'HTTPS' #=> 'http_s', not 'https' + # + # acronym 'HTTPS' + # camelize 'https' #=> 'HTTPS' + # underscore 'HTTPS' #=> 'https' + # + # Note: Acronyms that are passed to `pluralize` will no longer be recognized, since the acronym will not occur as + # a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an + # acronym as well: + # + # acronym 'API' + # camelize(pluralize('api')) #=> 'Apis' + # + # acronym 'APIs' + # camelize(pluralize('api')) #=> 'APIs' + # + # `acronym` may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard + # capitalization. The only restriction is that the word must begin with a capital letter. + # + # Examples: + # acronym 'RESTful' + # underscore 'RESTful' #=> 'restful' + # underscore 'RESTfulController' #=> 'restful_controller' + # titleize 'RESTfulController' #=> 'RESTful Controller' + # camelize 'restful' #=> 'RESTful' + # camelize 'restful_controller' #=> 'RESTfulController' + # + # acronym 'McDonald' + # underscore 'McDonald' #=> 'mcdonald' + # camelize 'mcdonald' #=> 'McDonald' + def acronym(word) + @acronyms[word.downcase] = word + @acronym_regex = /#{@acronyms.values.join("|")}/ end # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. @@ -117,95 +168,5 @@ module ActiveSupport Inflections.instance end end - - # Returns the plural form of the word in the string. - # - # Examples: - # "post".pluralize # => "posts" - # "octopus".pluralize # => "octopi" - # "sheep".pluralize # => "sheep" - # "words".pluralize # => "words" - # "CamelOctopus".pluralize # => "CamelOctopi" - def pluralize(word) - result = word.to_s.dup - - if word.empty? || inflections.uncountables.include?(result.downcase) - result - else - inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # The reverse of +pluralize+, returns the singular form of a word in a string. - # - # Examples: - # "posts".singularize # => "post" - # "octopi".singularize # => "octopus" - # "sheep".singularize # => "sheep" - # "word".singularize # => "word" - # "CamelOctopi".singularize # => "CamelOctopus" - def singularize(word) - result = word.to_s.dup - - if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } - result - else - inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # Capitalizes the first word and turns underscores into spaces and strips a - # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. - # - # Examples: - # "employee_salary" # => "Employee salary" - # "author_id" # => "Author" - def humanize(lower_case_and_underscored_word) - result = lower_case_and_underscored_word.to_s.dup - - inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result.gsub(/_id$/, "").gsub(/_/, " ").capitalize - end - - # Capitalizes all the words and replaces some characters in the string to create - # a nicer looking title. +titleize+ is meant for creating pretty output. It is not - # used in the Rails internals. - # - # +titleize+ is also aliased as as +titlecase+. - # - # Examples: - # "man from the boondocks".titleize # => "Man From The Boondocks" - # "x-men: the last stand".titleize # => "X Men: The Last Stand" - def titleize(word) - humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } - end - - # Create the name of a table like Rails does for models to table names. This method - # uses the +pluralize+ method on the last word in the string. - # - # Examples - # "RawScaledScorer".tableize # => "raw_scaled_scorers" - # "egg_and_ham".tableize # => "egg_and_hams" - # "fancyCategory".tableize # => "fancy_categories" - def tableize(class_name) - pluralize(underscore(class_name)) - end - - # Create a class name from a plural table name like Rails does for table names to models. - # Note that this returns a string and not a Class. (To convert to an actual class - # follow +classify+ with +constantize+.) - # - # Examples: - # "egg_and_hams".classify # => "EggAndHam" - # "posts".classify # => "Post" - # - # Singular names are not handled correctly: - # "business".classify # => "Busines" - def classify(table_name) - # strip out any leading schema name - camelize(singularize(table_name.to_s.sub(/.*\./, ''))) - end end end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index a2c4f7bfda..3d28d33f40 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,3 +1,5 @@ +require 'active_support/inflector/inflections' + module ActiveSupport # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept @@ -10,6 +12,44 @@ module ActiveSupport module Inflector extend self + # Returns the plural form of the word in the string. + # + # Examples: + # "post".pluralize # => "posts" + # "octopus".pluralize # => "octopi" + # "sheep".pluralize # => "sheep" + # "words".pluralize # => "words" + # "CamelOctopus".pluralize # => "CamelOctopi" + def pluralize(word) + result = word.to_s.dup + + if word.empty? || inflections.uncountables.include?(result.downcase) + result + else + inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end + + # The reverse of +pluralize+, returns the singular form of a word in a string. + # + # Examples: + # "posts".singularize # => "post" + # "octopi".singularize # => "octopus" + # "sheep".singularize # => "sheep" + # "word".singularize # => "word" + # "CamelOctopi".singularize # => "CamelOctopus" + def singularize(word) + result = word.to_s.dup + + if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } + result + else + inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end + # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase. # @@ -25,12 +65,14 @@ module ActiveSupport # though there are cases where that does not hold: # # "SSLError".underscore.camelize # => "SslError" - def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) - if first_letter_in_uppercase - lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } + def camelize(term, uppercase_first_letter = true) + string = term.to_s + if uppercase_first_letter + string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize } else - lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1] + string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase } end + string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::') end # Makes an underscored, lowercase form from the expression in the string. @@ -48,13 +90,66 @@ module ActiveSupport def underscore(camel_cased_word) word = camel_cased_word.to_s.dup word.gsub!(/::/, '/') - word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } + word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') word.tr!("-", "_") word.downcase! word end + # Capitalizes the first word and turns underscores into spaces and strips a + # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. + # + # Examples: + # "employee_salary" # => "Employee salary" + # "author_id" # => "Author" + def humanize(lower_case_and_underscored_word) + result = lower_case_and_underscored_word.to_s.dup + inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result.gsub!(/_id$/, "") + result.gsub(/(_)?([a-z\d]*)/i) { "#{$1 && ' '}#{inflections.acronyms[$2] || $2.downcase}" }.gsub(/^\w/) { $&.upcase } + end + + # Capitalizes all the words and replaces some characters in the string to create + # a nicer looking title. +titleize+ is meant for creating pretty output. It is not + # used in the Rails internals. + # + # +titleize+ is also aliased as as +titlecase+. + # + # Examples: + # "man from the boondocks".titleize # => "Man From The Boondocks" + # "x-men: the last stand".titleize # => "X Men: The Last Stand" + def titleize(word) + humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } + end + + # Create the name of a table like Rails does for models to table names. This method + # uses the +pluralize+ method on the last word in the string. + # + # Examples + # "RawScaledScorer".tableize # => "raw_scaled_scorers" + # "egg_and_ham".tableize # => "egg_and_hams" + # "fancyCategory".tableize # => "fancy_categories" + def tableize(class_name) + pluralize(underscore(class_name)) + end + + # Create a class name from a plural table name like Rails does for table names to models. + # Note that this returns a string and not a Class. (To convert to an actual class + # follow +classify+ with +constantize+.) + # + # Examples: + # "egg_and_hams".classify # => "EggAndHam" + # "posts".classify # => "Post" + # + # Singular names are not handled correctly: + # "business".classify # => "Busines" + def classify(table_name) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, ''))) + end + # Replaces underscores with dashes in the string. # # Example: diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 1c4dd24227..6296c1d4b8 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -3,8 +3,8 @@ require 'active_support/core_ext/class/attribute' module ActiveSupport # ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications - # with solely purpose of logging. The log subscriber dispatches notifications to a - # registered object based on its given namespace. + # with the sole purpose of logging them. The log subscriber dispatches notifications to + # a registered object based on its given namespace. # # An example would be Active Record log subscriber responsible for logging queries: # @@ -109,8 +109,8 @@ module ActiveSupport # Set color by using a string or one of the defined constants. If a third # option is set to true, it also adds bold to the string. This is based - # on Highline implementation and it automatically appends CLEAR to the end - # of the returned String. + # on the Highline implementation and will automatically append CLEAR to the + # end of the returned String. # def color(text, color, bold=false) return text unless colorize_logging diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb index 0a7bcd5bb8..4c67676ad5 100644 --- a/activesupport/lib/active_support/memoizable.rb +++ b/activesupport/lib/active_support/memoizable.rb @@ -1,8 +1,15 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/aliasing' +require 'active_support/deprecation' module ActiveSupport module Memoizable + def self.extended(base) + ActiveSupport::Deprecation.warn "ActiveSupport::Memoizable is deprecated and will be removed in future releases," \ + "simply use Ruby memoization pattern instead.", caller + super + end + def self.memoized_ivar_for(symbol) "@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym end @@ -79,7 +86,11 @@ module ActiveSupport else # else def #{symbol}(*args) # def mime_type(*args) #{memoized_ivar} ||= {} unless frozen? # @_memoized_mime_type ||= {} unless frozen? - reload = args.pop if args.last == true || args.last == :reload # reload = args.pop if args.last == true || args.last == :reload + args_length = method(:#{original_method}).arity # args_length = method(:_unmemoized_mime_type).arity + if args.length == args_length + 1 && # if args.length == args_length + 1 && + (args.last == true || args.last == :reload) # (args.last == true || args.last == :reload) + reload = args.pop # reload = args.pop + end # end # if defined?(#{memoized_ivar}) && #{memoized_ivar} # if defined?(@_memoized_mime_type) && @_memoized_mime_type if !reload && #{memoized_ivar}.has_key?(args) # if !reload && @_memoized_mime_type.has_key?(args) diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 8d8e6ebc58..bf81567d22 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -36,6 +36,10 @@ module ActiveSupport #:nodoc: self[name] end end + + def respond_to?(name) + true + end end class InheritableOptions < OrderedOptions diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 05da50e150..3864b1f5a6 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -68,7 +68,7 @@ module ActiveSupport # # A error message can be specified. # - # assert_no_difference 'Article.count', "An Article should not be destroyed" do + # assert_no_difference 'Article.count', "An Article should not be created" do # post :create, :article => invalid_attributes # end def assert_no_difference(expression, message = nil, &block) diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index 02c19448fd..dd23f8d82d 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -9,7 +9,7 @@ module ActiveSupport module Testing module Performance extend ActiveSupport::Concern - + included do superclass_delegating_accessor :profile_options self.profile_options = {} @@ -20,7 +20,7 @@ module ActiveSupport include ForClassicTestUnit end end - + # each implementation should define metrics and freeze the defaults DEFAULTS = if ARGV.include?('--benchmark') # HAX for rake test @@ -32,7 +32,7 @@ module ActiveSupport :output => 'tmp/performance', :benchmark => false } end - + def full_profile_options DEFAULTS.merge(profile_options) end @@ -40,7 +40,7 @@ module ActiveSupport def full_test_name "#{self.class.name}##{method_name}" end - + module ForMiniTest def run(runner) @runner = runner @@ -53,7 +53,7 @@ module ActiveSupport end end end - + return end @@ -122,7 +122,7 @@ module ActiveSupport protected # overridden by each implementation def run_gc; end - + def run_warmup run_gc @@ -132,7 +132,7 @@ module ActiveSupport run_gc end - + def run_profile(metric) klass = full_profile_options[:benchmark] ? Benchmarker : Profiler performer = klass.new(self, metric) @@ -163,7 +163,7 @@ module ActiveSupport "#{full_profile_options[:output]}/#{full_test_name}_#{@metric.name}" end end - + # overridden by each implementation class Profiler < Performer def time_with_block @@ -171,7 +171,7 @@ module ActiveSupport yield Time.now - before end - + def run; end def record; end end @@ -181,10 +181,10 @@ module ActiveSupport super @supported = @metric.respond_to?('measure') end - + def run return unless @supported - + full_profile_options[:runs].to_i.times { run_test(@metric, :benchmark) } @total = @metric.total end @@ -237,7 +237,7 @@ module ActiveSupport "#{super}.csv" end end - + module Metrics def self.[](name) const_get(name.to_s.camelize) @@ -247,7 +247,7 @@ module ActiveSupport class Base include ActionView::Helpers::NumberHelper - + attr_reader :total def initialize @@ -265,15 +265,15 @@ module ActiveSupport @total += (measure - before) end end - + # overridden by each implementation def profile; end - + protected # overridden by each implementation def with_gc_stats; end end - + class Time < Base def measure ::Time.now.to_f @@ -287,19 +287,19 @@ module ActiveSupport end end end - + class Amount < Base def format(measurement) number_with_delimiter(measurement.floor) end end - + class DigitalInformationUnit < Base def format(measurement) number_to_human_size(measurement, :precision => 2) end end - + # each implementation provides its own metrics like ProcessTime, Memory or GcRuns end end diff --git a/activesupport/lib/active_support/testing/performance/jruby.rb b/activesupport/lib/active_support/testing/performance/jruby.rb index 326904bbac..b347539f13 100644 --- a/activesupport/lib/active_support/testing/performance/jruby.rb +++ b/activesupport/lib/active_support/testing/performance/jruby.rb @@ -1,5 +1,5 @@ require 'jruby/profiler' -require 'java' +require 'java' java_import java.lang.management.ManagementFactory module ActiveSupport @@ -12,21 +12,21 @@ module ActiveSupport { :metrics => [:wall_time], :formats => [:flat, :graph] } end).freeze - + protected def run_gc ManagementFactory.memory_mx_bean.gc - end + end class Profiler < Performer def initialize(*args) super @supported = @metric.is_a?(Metrics::WallTime) end - + def run return unless @supported - + @total = time_with_block do @data = JRuby::Profiler.profile do full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } @@ -36,7 +36,7 @@ module ActiveSupport def record return unless @supported - + klasses = full_profile_options[:formats].map { |f| JRuby::Profiler.const_get("#{f.to_s.camelize}ProfilePrinter") }.compact klasses.each do |klass| @@ -61,7 +61,7 @@ module ActiveSupport end end - module Metrics + module Metrics class Base def profile yield @@ -85,7 +85,7 @@ module ActiveSupport ManagementFactory.thread_mx_bean.get_current_thread_cpu_time / 1000 / 1000 / 1000.0 # seconds end end - + class UserTime < Time def measure ManagementFactory.thread_mx_bean.get_current_thread_user_time / 1000 / 1000 / 1000.0 # seconds @@ -97,7 +97,7 @@ module ActiveSupport ManagementFactory.memory_mx_bean.non_heap_memory_usage.used + ManagementFactory.memory_mx_bean.heap_memory_usage.used end end - + class GcRuns < Amount def measure ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_runs, current_gc| total_runs += current_gc.collection_count } diff --git a/activesupport/lib/active_support/testing/performance/rubinius.rb b/activesupport/lib/active_support/testing/performance/rubinius.rb index 198d235548..d9ebfbe352 100644 --- a/activesupport/lib/active_support/testing/performance/rubinius.rb +++ b/activesupport/lib/active_support/testing/performance/rubinius.rb @@ -10,12 +10,12 @@ module ActiveSupport { :metrics => [:wall_time], :formats => [:flat, :graph] } end).freeze - + protected def run_gc GC.run(true) end - + class Performer; end class Profiler < Performer @@ -23,35 +23,35 @@ module ActiveSupport super @supported = @metric.is_a?(Metrics::WallTime) end - + def run return unless @supported - + @profiler = Rubinius::Profiler::Instrumenter.new - + @total = time_with_block do @profiler.profile(false) do full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } end end end - + def record return unless @supported - + if(full_profile_options[:formats].include?(:flat)) create_path_and_open_file(:flat) do |file| @profiler.show(file) end end - + if(full_profile_options[:formats].include?(:graph)) create_path_and_open_file(:graph) do |file| @profiler.show(file) end end end - + protected def create_path_and_open_file(printer_name) fname = "#{output_filename}_#{printer_name}.txt" @@ -62,10 +62,10 @@ module ActiveSupport end end - module Metrics + module Metrics class Base attr_reader :loopback - + def profile yield end diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb index b29ec6719c..7d6d047ef6 100644 --- a/activesupport/lib/active_support/testing/performance/ruby.rb +++ b/activesupport/lib/active_support/testing/performance/ruby.rb @@ -16,7 +16,7 @@ module ActiveSupport :metrics => [:process_time, :memory, :objects], :formats => [:flat, :graph_html, :call_tree, :call_stack] } end).freeze - + protected def run_gc GC.start @@ -77,7 +77,7 @@ module ActiveSupport def measure_mode self.class::Mode end - + def profile RubyProf.resume yield @@ -91,7 +91,7 @@ module ActiveSupport yield end end - + class ProcessTime < Time Mode = RubyProf::PROCESS_TIME if RubyProf.const_defined?(:PROCESS_TIME) diff --git a/activesupport/lib/active_support/testing/performance/ruby/mri.rb b/activesupport/lib/active_support/testing/performance/ruby/mri.rb index 86e650050b..142279dd6e 100644 --- a/activesupport/lib/active_support/testing/performance/ruby/mri.rb +++ b/activesupport/lib/active_support/testing/performance/ruby/mri.rb @@ -15,7 +15,7 @@ module ActiveSupport end end end - + class Memory < DigitalInformationUnit # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_memory) @@ -24,7 +24,7 @@ module ActiveSupport end end end - + class Objects < Amount # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_allocations) @@ -33,7 +33,7 @@ module ActiveSupport end end end - + class GcRuns < Amount # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_gc_runs) @@ -42,7 +42,7 @@ module ActiveSupport end end end - + class GcTime < Time # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_gc_time) @@ -55,5 +55,3 @@ module ActiveSupport end end end - - diff --git a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb index 62095a8fe4..7873262331 100644 --- a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb +++ b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb @@ -15,7 +15,7 @@ module ActiveSupport end end end - + class Memory < DigitalInformationUnit # Ruby 1.9 + GCdata patch if GC.respond_to?(:malloc_allocated_size) @@ -24,7 +24,7 @@ module ActiveSupport end end end - + class Objects < Amount # Ruby 1.9 + GCdata patch if GC.respond_to?(:malloc_allocations) @@ -33,7 +33,7 @@ module ActiveSupport end end end - + class GcRuns < Amount # Ruby 1.9 if GC.respond_to?(:count) @@ -42,7 +42,7 @@ module ActiveSupport end end end - + class GcTime < Time # Ruby 1.9 with GC::Profiler if defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time) diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index e135872bf6..bd8e7f907a 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -1,9 +1,9 @@ module ActiveSupport module VERSION #:nodoc: MAJOR = 3 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "rc1" + PRE = "beta" STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb index c6d8191298..2e5ea2c360 100644 --- a/activesupport/test/configurable_test.rb +++ b/activesupport/test/configurable_test.rb @@ -58,16 +58,26 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase child = Class.new(parent) parent.config.bar = :foo - assert !parent.config.respond_to?(:bar) - assert !child.config.respond_to?(:bar) - assert !child.new.config.respond_to?(:bar) + assert_method_not_defined parent.config, :bar + assert_method_not_defined child.config, :bar + assert_method_not_defined child.new.config, :bar parent.config.compile_methods! assert_equal :foo, parent.config.bar assert_equal :foo, child.new.config.bar - assert_respond_to parent.config, :bar - assert_respond_to child.config, :bar - assert_respond_to child.new.config, :bar + assert_method_defined parent.config, :bar + assert_method_defined child.config, :bar + assert_method_defined child.new.config, :bar + end + + def assert_method_defined(object, method) + methods = object.public_methods.map(&:to_s) + assert methods.include?(method.to_s), "Expected #{methods.inspect} to include #{method.to_s.inspect}" + end + + def assert_method_not_defined(object, method) + methods = object.public_methods.map(&:to_s) + assert !methods.include?(method.to_s), "Expected #{methods.inspect} to not include #{method.to_s.inspect}" end end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 0e5407bc35..e532010b18 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -456,8 +456,9 @@ class ArrayWrapperTests < Test::Unit::TestCase assert_equal [o], Array.wrap(o) end - def test_wrap_returns_nil_if_to_ary_returns_nil - assert_nil Array.wrap(NilToAry.new) + def test_wrap_returns_wrapped_if_to_ary_returns_nil + o = NilToAry.new + assert_equal [o], Array.wrap(o) end def test_wrap_does_not_complain_if_to_ary_does_not_return_an_array diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb index 456f4b7948..6b50f8db37 100644 --- a/activesupport/test/core_ext/class/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/class/attribute_accessor_test.rb @@ -5,8 +5,9 @@ class ClassAttributeAccessorTest < Test::Unit::TestCase def setup @class = Class.new do cattr_accessor :foo - cattr_accessor :bar, :instance_writer => false - cattr_reader :shaq, :instance_reader => false + cattr_accessor :bar, :instance_writer => false + cattr_reader :shaq, :instance_reader => false + cattr_accessor :camp, :instance_accessor => false end @object = @class.new end @@ -35,4 +36,10 @@ class ClassAttributeAccessorTest < Test::Unit::TestCase assert_respond_to @class, :shaq assert !@object.respond_to?(:shaq) end + + def test_should_not_create_instance_accessors + assert_respond_to @class, :camp + assert !@object.respond_to?(:camp) + assert !@object.respond_to?(:camp=) + end end diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb index d58b60482b..e290a6e012 100644 --- a/activesupport/test/core_ext/class/attribute_test.rb +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -60,6 +60,12 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_raise(NoMethodError) { object.setting = 'boom' } end + test 'disabling instance reader' do + object = Class.new { class_attribute :setting, :instance_reader => false }.new + assert_raise(NoMethodError) { object.setting } + assert_raise(NoMethodError) { object.setting? } + end + test 'works well with singleton classes' do object = @klass.new object.singleton_class.setting = 'foo' diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index d81693209f..b4f848cd44 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -454,4 +454,10 @@ class DateExtBehaviorTest < Test::Unit::TestCase Date.today.freeze.inspect end end + + def test_can_freeze_twice + assert_nothing_raised do + Date.today.freeze.freeze + end + end end diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb index 118fb070a0..29889b51e0 100644 --- a/activesupport/test/core_ext/module/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb @@ -7,6 +7,7 @@ class ModuleAttributeAccessorTest < Test::Unit::TestCase mattr_accessor :foo mattr_accessor :bar, :instance_writer => false mattr_reader :shaq, :instance_reader => false + mattr_accessor :camp, :instance_accessor => false end @class = Class.new @class.instance_eval { include m } @@ -37,4 +38,10 @@ class ModuleAttributeAccessorTest < Test::Unit::TestCase assert_respond_to @module, :shaq assert !@object.respond_to?(:shaq) end + + def test_should_not_create_instance_accessors + assert_respond_to @module, :camp + assert !@object.respond_to?(:camp) + assert !@object.respond_to?(:camp=) + end end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 0fd0cb8ac7..4000cc913a 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -252,7 +252,7 @@ class StringInflectionsTest < Test::Unit::TestCase # And changes the original string: assert_equal original, expected end - + def test_string_inquiry assert "production".inquiry.production? assert !"production".inquiry.development? @@ -354,6 +354,10 @@ class OutputSafetyTest < ActiveSupport::TestCase test "A fixnum is safe by default" do assert 5.html_safe? end + + test "a float is safe by default" do + assert 5.7.html_safe? + end test "An object is unsafe by default" do assert !@object.html_safe? @@ -452,6 +456,12 @@ class OutputSafetyTest < ActiveSupport::TestCase assert !'ruby'.encoding_aware? end end + + test "call to_param returns a normal string" do + string = @string.html_safe + assert string.html_safe? + assert !string.to_param.html_safe? + end end class StringExcludeTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 44e02109b1..c4c4381957 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -767,6 +767,26 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal false, Time === DateTime.civil(2000) end + def test_all_day + assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_day + end + + def test_all_week + assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week + end + + def test_all_month + assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_month + end + + def test_all_quarter + assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_quarter + end + + def test_all_year + assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_year + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/activesupport/test/flush_cache_on_private_memoization_test.rb b/activesupport/test/flush_cache_on_private_memoization_test.rb index a7db96eb71..20768b777a 100644 --- a/activesupport/test/flush_cache_on_private_memoization_test.rb +++ b/activesupport/test/flush_cache_on_private_memoization_test.rb @@ -2,7 +2,9 @@ require 'abstract_unit' require 'test/unit' class FlashCacheOnPrivateMemoizationTest < Test::Unit::TestCase - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end def test_public assert_method_unmemoizable :pub diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 95f18126d4..b9e299af75 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -94,6 +94,88 @@ class InflectorTest < Test::Unit::TestCase assert_equal('capital', ActiveSupport::Inflector.camelize('Capital', false)) end + def test_camelize_with_underscores + assert_equal("CamelCase", ActiveSupport::Inflector.camelize('Camel_Case')) + end + + def test_acronyms + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML") + inflect.acronym("HTTP") + inflect.acronym("RESTful") + inflect.acronym("W3C") + inflect.acronym("PhD") + inflect.acronym("RoR") + inflect.acronym("SSL") + end + + # camelize underscore humanize titleize + [ + ["API", "api", "API", "API"], + ["APIController", "api_controller", "API controller", "API Controller"], + ["Nokogiri::HTML", "nokogiri/html", "Nokogiri/HTML", "Nokogiri/HTML"], + ["HTTPAPI", "http_api", "HTTP API", "HTTP API"], + ["HTTP::Get", "http/get", "HTTP/get", "HTTP/Get"], + ["SSLError", "ssl_error", "SSL error", "SSL Error"], + ["RESTful", "restful", "RESTful", "RESTful"], + ["RESTfulController", "restful_controller", "RESTful controller", "RESTful Controller"], + ["IHeartW3C", "i_heart_w3c", "I heart W3C", "I Heart W3C"], + ["PhDRequired", "phd_required", "PhD required", "PhD Required"], + ["IRoRU", "i_ror_u", "I RoR u", "I RoR U"], + ["RESTfulHTTPAPI", "restful_http_api", "RESTful HTTP API", "RESTful HTTP API"], + + # misdirection + ["Capistrano", "capistrano", "Capistrano", "Capistrano"], + ["CapiController", "capi_controller", "Capi controller", "Capi Controller"], + ["HttpsApis", "https_apis", "Https apis", "Https Apis"], + ["Html5", "html5", "Html5", "Html5"], + ["Restfully", "restfully", "Restfully", "Restfully"], + ["RoRails", "ro_rails", "Ro rails", "Ro Rails"] + ].each do |camel, under, human, title| + assert_equal(camel, ActiveSupport::Inflector.camelize(under)) + assert_equal(camel, ActiveSupport::Inflector.camelize(camel)) + assert_equal(under, ActiveSupport::Inflector.underscore(under)) + assert_equal(under, ActiveSupport::Inflector.underscore(camel)) + assert_equal(title, ActiveSupport::Inflector.titleize(under)) + assert_equal(title, ActiveSupport::Inflector.titleize(camel)) + assert_equal(human, ActiveSupport::Inflector.humanize(under)) + end + end + + def test_acronym_override + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("LegacyApi") + end + + assert_equal("LegacyApi", ActiveSupport::Inflector.camelize("legacyapi")) + assert_equal("LegacyAPI", ActiveSupport::Inflector.camelize("legacy_api")) + assert_equal("SomeLegacyApi", ActiveSupport::Inflector.camelize("some_legacyapi")) + assert_equal("Nonlegacyapi", ActiveSupport::Inflector.camelize("nonlegacyapi")) + end + + def test_acronyms_camelize_lower + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML") + end + + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("html_api", false)) + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("htmlAPI", false)) + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("HTMLAPI", false)) + end + + def test_underscore_acronym_sequence + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML5") + inflect.acronym("HTML") + end + + assert_equal("html5_html_api", ActiveSupport::Inflector.underscore("HTML5HTMLAPI")) + end + def test_underscore CamelToUnderscore.each do |camel, underscore| assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) @@ -148,8 +230,8 @@ class InflectorTest < Test::Unit::TestCase end def test_parameterize_with_custom_separator - StringToParameterized.each do |some_string, parameterized_string| - assert_equal(parameterized_string.gsub('-', '_'), ActiveSupport::Inflector.parameterize(some_string, '_')) + StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| + assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_')) end end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index ec9d92794c..0cb1f70657 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -168,6 +168,7 @@ module InflectorTestCases StringToParameterizeWithNoSeparator = { "Donald E. Knuth" => "donaldeknuth", + "With-some-dashes" => "with-some-dashes", "Random text with *(bad)* characters" => "randomtextwithbadcharacters", "Trailing bad characters!@#" => "trailingbadcharacters", "!@#Leading bad characters" => "leadingbadcharacters", @@ -179,6 +180,8 @@ module InflectorTestCases StringToParameterizeWithUnderscore = { "Donald E. Knuth" => "donald_e_knuth", "Random text with *(bad)* characters" => "random_text_with_bad_characters", + "With-some-dashes" => "with-some-dashes", + "Retain_underscore" => "retain_underscore", "Trailing bad characters!@#" => "trailing_bad_characters", "!@#Leading bad characters" => "leading_bad_characters", "Squeeze separators" => "squeeze_separators", diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb index bceac1315b..e333b9a78c 100644 --- a/activesupport/test/memoizable_test.rb +++ b/activesupport/test/memoizable_test.rb @@ -2,7 +2,9 @@ require 'abstract_unit' class MemoizableTest < ActiveSupport::TestCase class Person - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end attr_reader :name_calls, :age_calls, :is_developer_calls, :name_query_calls @@ -65,7 +67,9 @@ class MemoizableTest < ActiveSupport::TestCase end module Rates - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end attr_reader :sales_tax_calls def sales_tax(price) @@ -77,7 +81,9 @@ class MemoizableTest < ActiveSupport::TestCase end class Calculator - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end include Rates attr_reader :fib_calls @@ -96,6 +102,15 @@ class MemoizableTest < ActiveSupport::TestCase end memoize :fib + def add_or_subtract(i, j, add) + if add + i + j + else + i - j + end + end + memoize :add_or_subtract + def counter @count ||= 0 @count += 1 @@ -199,9 +214,16 @@ class MemoizableTest < ActiveSupport::TestCase assert_equal 13, @calculator.fib_calls end + def test_memoization_with_boolean_arg + assert_equal 4, @calculator.add_or_subtract(2, 2, true) + assert_equal 2, @calculator.add_or_subtract(4, 2, false) + end + def test_object_memoization [Company.new, Company.new, Company.new].each do |company| - company.extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + company.extend ActiveSupport::Memoizable + end company.memoize :name assert_equal "37signals", company.name @@ -235,11 +257,15 @@ class MemoizableTest < ActiveSupport::TestCase def test_double_memoization assert_raise(RuntimeError) { Person.memoize :name } person = Person.new - person.extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + person.extend ActiveSupport::Memoizable + end assert_raise(RuntimeError) { person.memoize :name } company = Company.new - company.extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + company.extend ActiveSupport::Memoizable + end company.memoize :name assert_raise(RuntimeError) { company.memoize :name } end diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index a4e2acbb32..46bc0d7a34 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -4,6 +4,7 @@ begin rescue LoadError end +require 'active_support/core_ext/string/inflections' require 'yaml' class SafeBufferTest < ActiveSupport::TestCase @@ -45,7 +46,7 @@ class SafeBufferTest < ActiveSupport::TestCase assert_equal ActiveSupport::SafeBuffer, new_buffer.class end - def test_to_yaml + test "Should be converted to_yaml" do str = 'hello!' buf = ActiveSupport::SafeBuffer.new str yaml = buf.to_yaml @@ -54,10 +55,57 @@ class SafeBufferTest < ActiveSupport::TestCase assert_equal 'hello!', YAML.load(yaml) end - def test_nested + test "Should work in nested to_yaml conversion" do str = 'hello!' data = { 'str' => ActiveSupport::SafeBuffer.new(str) } yaml = YAML.dump data assert_equal({'str' => str}, YAML.load(yaml)) end + + test "Should work with underscore" do + str = "MyTest".html_safe.underscore + assert_equal "my_test", str + end + + test "Should not return safe buffer from gsub" do + altered_buffer = @buffer.gsub('', 'asdf') + assert_equal 'asdf', altered_buffer + assert !altered_buffer.html_safe? + end + + test "Should not return safe buffer from gsub!" do + @buffer.gsub!('', 'asdf') + assert_equal 'asdf', @buffer + assert !@buffer.html_safe? + end + + test "Should escape dirty buffers on add" do + dirty = @buffer + clean = "hello".html_safe + @buffer.gsub!('', '<>') + assert_equal "hello<>", clean + @buffer + end + + test "Should concat as a normal string when dirty" do + dirty = @buffer + clean = "hello".html_safe + @buffer.gsub!('', '<>') + assert_equal "<>hello", @buffer + clean + end + + test "Should preserve dirty? status on copy" do + @buffer.gsub!('', '<>') + assert !@buffer.dup.html_safe? + end + + test "Should raise an error when safe_concat is called on dirty buffers" do + @buffer.gsub!('', '<>') + assert_raise ActiveSupport::SafeBuffer::SafeConcatError do + @buffer.safe_concat "BUSTED" + end + end + + test "should not fail if the returned object is not a string" do + assert_kind_of NilClass, @buffer.slice("chipchop") + end end diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index ee5a20c789..5bd995aa32 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -50,7 +50,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase def test_expression_is_evaluated_in_the_appropriate_scope silence_warnings do - local_scope = 'foo' + local_scope = local_scope = 'foo' assert_difference('local_scope; @object.num') { @object.increment } end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 49cefc6e82..3575175517 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -224,9 +224,9 @@ class TimeZoneTest < Test::Unit::TestCase end def test_formatted_offset_positive - zone = ActiveSupport::TimeZone['Moscow'] - assert_equal "+03:00", zone.formatted_offset - assert_equal "+0300", zone.formatted_offset(false) + zone = ActiveSupport::TimeZone['New Delhi'] + assert_equal "+05:30", zone.formatted_offset + assert_equal "+0530", zone.formatted_offset(false) end def test_formatted_offset_negative @@ -257,7 +257,7 @@ class TimeZoneTest < Test::Unit::TestCase end def test_to_s - assert_equal "(GMT+03:00) Moscow", ActiveSupport::TimeZone['Moscow'].to_s + assert_equal "(GMT+05:30) New Delhi", ActiveSupport::TimeZone['New Delhi'].to_s end def test_all_sorted |