diff options
Diffstat (limited to 'activesupport/lib/active_support/core_ext')
73 files changed, 1528 insertions, 861 deletions
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 4f1e432b61..ca66d806ef 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -5,6 +5,8 @@ class Array # %w( a b c d ).from(2) # => ["c", "d"] # %w( a b c d ).from(10) # => [] # %w().from(0) # => [] + # %w( a b c d ).from(-2) # => ["c", "d"] + # %w( a b c ).from(-10) # => [] def from(position) self[position, length] || [] end @@ -15,8 +17,14 @@ class Array # %w( a b c d ).to(2) # => ["a", "b", "c"] # %w( a b c d ).to(10) # => ["a", "b", "c", "d"] # %w().to(0) # => [] + # %w( a b c d ).to(-2) # => ["a", "b", "c"] + # %w( a b c ).to(-10) # => [] def to(position) - first position + 1 + if position >= 0 + take position + 1 + else + self[0..position] + end end # Equal to <tt>self[1]</tt>. @@ -48,6 +56,8 @@ class Array end # Equal to <tt>self[41]</tt>. Also known as accessing "the reddit". + # + # (1..42).to_a.forty_two # => 42 def forty_two self[41] end diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 76ffd23ed1..080e3b5ef7 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -92,7 +92,7 @@ class Array if empty? 'null' else - collect { |element| element.id }.join(',') + collect(&:id).join(',') end else to_default_s diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index 37f007c751..87ae052eb0 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -18,6 +18,11 @@ class Array # ["3", "4"] # ["5"] def in_groups_of(number, fill_with = nil) + if number.to_i <= 0 + raise ArgumentError, + "Group size must be a positive integer, was #{number.inspect}" + end + if fill_with == false collection = self else @@ -53,7 +58,7 @@ class Array # ["4", "5"] # ["6", "7"] def in_groups(number, fill_with = nil) - # size / number gives minor group size; + # size.div number gives minor group size; # size % number gives how many objects need extra accommodation; # each group hold either division or division + 1 items. division = size.div number @@ -83,10 +88,10 @@ class Array # # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]] # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]] - def split(value = nil, &block) - if block + def split(value = nil) + if block_given? inject([[]]) do |results, element| - if block.call(element) + if yield(element) results << [] else results.last << element @@ -95,9 +100,9 @@ class Array results end else - results, arr = [[]], self + results, arr = [[]], self.dup until arr.empty? - if (idx = index(value)) + if (idx = arr.index(value)) results.last.concat(arr.shift(idx)) arr.shift results << [] diff --git a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb index 27718f19d4..f8d48b69df 100644 --- a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb +++ b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb @@ -1,7 +1,7 @@ class Array - # The human way of thinking about adding stuff to the end of a list is with append + # The human way of thinking about adding stuff to the end of a list is with append. alias_method :append, :<< - # The human way of thinking about adding stuff to the beginning of a list is with prepend + # The human way of thinking about adding stuff to the beginning of a list is with prepend. alias_method :prepend, :unshift end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index 39b8cea807..234283e792 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -1,31 +1,15 @@ require 'bigdecimal' require 'bigdecimal/util' -require 'yaml' class BigDecimal - YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' } - - def encode_with(coder) - string = to_s - coder.represent_scalar(nil, YAML_MAPPING[string] || string) - end - - # Backport this method if it doesn't exist - unless method_defined?(:to_d) - def to_d - self - end - end - DEFAULT_STRING_FORMAT = 'F' - def to_formatted_s(*args) - if args[0].is_a?(Symbol) - super + alias_method :to_default_s, :to_s + + def to_s(format = nil, options = nil) + if format.is_a?(Symbol) + to_formatted_s(format, options || {}) else - format = args[0] || DEFAULT_STRING_FORMAT - _original_to_s(format) + to_default_s(format || DEFAULT_STRING_FORMAT) end end - alias_method :_original_to_s, :to_s - alias_method :to_s, :to_formatted_s end diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb index 86b752c2f3..c750a10bb2 100644 --- a/activesupport/lib/active_support/core_ext/class.rb +++ b/activesupport/lib/active_support/core_ext/class.rb @@ -1,4 +1,3 @@ require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/class/delegating_attributes' require 'active_support/core_ext/class/subclasses' diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 83038f9da5..f2b7bb3ef1 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -116,9 +116,4 @@ class Class attr_writer name if instance_writer end end - - private - def singleton_class? - ancestors.first != self - end end 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 34859617c9..84d5e95e7a 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -1,170 +1,4 @@ -require 'active_support/core_ext/array/extract_options' - -# Extends the class object with class and instance accessors for class attributes, -# just like the native attr* accessors for instance attributes. -class Class - # Defines a class attribute if it's not defined and creates a reader method that - # returns the attribute value. - # - # class Person - # cattr_reader :hair_colors - # end - # - # Person.class_variable_set("@@hair_colors", [:brown, :black]) - # Person.hair_colors # => [:brown, :black] - # Person.new.hair_colors # => [:brown, :black] - # - # The attribute name must be a valid method name in Ruby. - # - # class Person - # cattr_reader :"1_Badname " - # end - # # => NameError: invalid attribute name - # - # If you want to opt out the instance reader method, you can pass <tt>instance_reader: false</tt> - # or <tt>instance_accessor: false</tt>. - # - # class Person - # cattr_reader :hair_colors, instance_reader: false - # end - # - # Person.new.hair_colors # => NoMethodError - def cattr_reader(*syms) - options = syms.extract_options! - syms.each do |sym| - raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - unless defined? @@#{sym} - @@#{sym} = nil - end - - def self.#{sym} - @@#{sym} - end - EOS - - unless options[:instance_reader] == false || options[:instance_accessor] == false - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{sym} - @@#{sym} - end - EOS - end - end - end - - # Defines a class attribute if it's not defined and creates a writer method to allow - # assignment to the attribute. - # - # class Person - # cattr_writer :hair_colors - # end - # - # Person.hair_colors = [:brown, :black] - # Person.class_variable_get("@@hair_colors") # => [:brown, :black] - # Person.new.hair_colors = [:blonde, :red] - # Person.class_variable_get("@@hair_colors") # => [:blonde, :red] - # - # The attribute name must be a valid method name in Ruby. - # - # class Person - # cattr_writer :"1_Badname " - # end - # # => NameError: invalid attribute name - # - # If you want to opt out the instance writer method, pass <tt>instance_writer: false</tt> - # or <tt>instance_accessor: false</tt>. - # - # class Person - # cattr_writer :hair_colors, instance_writer: false - # end - # - # Person.new.hair_colors = [:blonde, :red] # => NoMethodError - # - # Also, you can pass a block to set up the attribute with a default value. - # - # class Person - # cattr_writer :hair_colors do - # [:brown, :black, :blonde, :red] - # end - # end - # - # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] - def cattr_writer(*syms) - options = syms.extract_options! - syms.each do |sym| - raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - unless defined? @@#{sym} - @@#{sym} = nil - end - - def self.#{sym}=(obj) - @@#{sym} = obj - end - EOS - - unless options[:instance_writer] == false || options[:instance_accessor] == false - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{sym}=(obj) - @@#{sym} = obj - end - EOS - end - send("#{sym}=", yield) if block_given? - end - end - - # Defines both class and instance accessors for class attributes. - # - # class Person - # cattr_accessor :hair_colors - # end - # - # Person.hair_colors = [:brown, :black, :blonde, :red] - # Person.hair_colors # => [:brown, :black, :blonde, :red] - # Person.new.hair_colors # => [:brown, :black, :blonde, :red] - # - # If a subclass changes the value then that would also change the value for - # parent class. Similarly if parent class changes the value then that would - # change the value of subclasses too. - # - # class Male < Person - # end - # - # Male.hair_colors << :blue - # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue] - # - # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>. - # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>. - # - # class Person - # cattr_accessor :hair_colors, instance_writer: false, instance_reader: false - # end - # - # Person.new.hair_colors = [:brown] # => NoMethodError - # Person.new.hair_colors # => NoMethodError - # - # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods. - # - # class Person - # cattr_accessor :hair_colors, instance_accessor: false - # end - # - # Person.new.hair_colors = [:brown] # => NoMethodError - # Person.new.hair_colors # => NoMethodError - # - # Also you can pass a block to set up the attribute with a default value. - # - # class Person - # cattr_accessor :hair_colors do - # [:brown, :black, :blonde, :red] - # end - # end - # - # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red] - def cattr_accessor(*syms, &blk) - cattr_reader(*syms) - cattr_writer(*syms, &blk) - end -end +# cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d, +# but we keep this around for libraries that directly require it knowing they +# want cattr_*. No need to deprecate. +require 'active_support/core_ext/module/attribute_accessors' diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb index ff870f5fd1..1c305c5970 100644 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb @@ -1,25 +1,30 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/module/deprecation' + class Class def superclass_delegating_accessor(name, options = {}) # Create private _name and _name= methods that can still be used if the public - # methods are overridden. This allows - _superclass_delegating_accessor("_#{name}") + # methods are overridden. + _superclass_delegating_accessor("_#{name}", options) - # Generate the public methods name, name=, and name? + # Generate the public methods name, name=, and name?. # These methods dispatch to the private _name, and _name= methods, making them - # overridable + # overridable. singleton_class.send(:define_method, name) { send("_#{name}") } singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") } singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) } - # If an instance_reader is needed, generate methods for name and name= on the - # class itself, so instances will be able to see them - define_method(name) { send("_#{name}") } if options[:instance_reader] != false - define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false + # If an instance_reader is needed, generate public instance methods name and name?. + if options[:instance_reader] != false + define_method(name) { send("_#{name}") } + define_method("#{name}?") { !!send("#{name}") } + end end + deprecate superclass_delegating_accessor: :class_attribute + private # Take the object being set and store it in a method. This gives us automatic # inheritance behavior, without having to store the object in an instance diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 6bc8f12176..df419a6e63 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,6 +1,7 @@ require 'date' require 'active_support/inflector/methods' require 'active_support/core_ext/date/zones' +require 'active_support/core_ext/module/remove_method' class Date DATE_FORMATS = { @@ -19,8 +20,10 @@ class Date # Ruby 1.9 has Date#to_time which converts to localtime only. remove_method :to_time - # Ruby 1.9 has Date#xmlschema which converts to a string without the time component. - remove_method :xmlschema + # Ruby 1.9 has Date#xmlschema which converts to a string without the time + # component. This removal may generate an issue on FreeBSD, that's why we + # need to use remove_possible_method here + remove_possible_method :xmlschema # Convert to a formatted string. See DATE_FORMATS for predefined formats. # @@ -37,12 +40,12 @@ class Date # date.to_formatted_s(:rfc822) # => "10 Nov 2007" # date.to_formatted_s(:iso8601) # => "2007-11-10" # - # == Adding your own time formats to to_formatted_s + # == Adding your own date formats to to_formatted_s # You can add your own formats to the Date::DATE_FORMATS hash. # Use the format name as the hash key and either a strftime string # or Proc instance that takes a date argument as the value. # - # # config/initializers/time_formats.rb + # # config/initializers/date_formats.rb # Date::DATE_FORMATS[:month_and_year] = '%B %Y' # Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") } def to_formatted_s(format = :default) diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb index c869a0e210..9525c10112 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb @@ -9,15 +9,26 @@ module DateAndTime :saturday => 5, :sunday => 6 } + WEEKEND_DAYS = [ 6, 0 ] # Returns a new date/time representing yesterday. def yesterday - advance(:days => -1) + advance(days: -1) + end + + # Returns a new date/time representing the previous day. + def prev_day + advance(days: -1) end # Returns a new date/time representing tomorrow. def tomorrow - advance(:days => 1) + advance(days: 1) + end + + # Returns a new date/time representing the next day. + def next_day + advance(days: 1) end # Returns true if the date/time is today. @@ -35,6 +46,11 @@ module DateAndTime self > self.class.current end + # Returns true if the date/time falls on a Saturday or Sunday. + def on_weekend? + WEEKEND_DAYS.include?(wday) + end + # Returns a new date/time the specified number of days ago. def days_ago(days) advance(:days => -days) @@ -111,9 +127,19 @@ module DateAndTime # Returns a new date/time representing the given day in the next week. # The +given_day_in_next_week+ defaults to the beginning of the week # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+ - # when set. +DateTime+ objects have their time set to 0:00. - def next_week(given_day_in_next_week = Date.beginning_of_week) - first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week))) + # when set. +DateTime+ objects have their time set to 0:00 unless +same_time+ is true. + def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week))) + same_time ? copy_time_to(result) : result + end + + # Returns a new date/time representing the next weekday. + def next_weekday + if next_day.on_weekend? + next_week(:monday, same_time: true) + else + next_day + end end # Short-hand for months_since(1). @@ -134,12 +160,23 @@ module DateAndTime # Returns a new date/time representing the given day in the previous week. # Week is assumed to start on +start_day+, default is # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. - # DateTime objects have their time set to 0:00. - def prev_week(start_day = Date.beginning_of_week) - first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day))) + # DateTime objects have their time set to 0:00 unless +same_time+ is true. + def prev_week(start_day = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day))) + same_time ? copy_time_to(result) : result end alias_method :last_week, :prev_week + # Returns a new date/time representing the previous weekday. + def prev_weekday + if prev_day.on_weekend? + copy_time_to(beginning_of_week(:friday)) + else + prev_day + end + end + alias_method :last_weekday, :prev_weekday + # Short-hand for months_ago(1). def prev_month months_ago(1) @@ -213,18 +250,42 @@ module DateAndTime end alias :at_end_of_year :end_of_year - private + # Returns a Range representing the whole week of the current date/time. + # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set. + def all_week(start_day = Date.beginning_of_week) + beginning_of_week(start_day)..end_of_week(start_day) + end - def first_hour(date_or_time) - date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time + # Returns a Range representing the whole month of the current date/time. + def all_month + beginning_of_month..end_of_month end - def last_hour(date_or_time) - date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time + # Returns a Range representing the whole quarter of the current date/time. + def all_quarter + beginning_of_quarter..end_of_quarter end - def days_span(day) - (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7 + # Returns a Range representing the whole year of the current date/time. + def all_year + beginning_of_year..end_of_year end + + private + def first_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time + end + + def last_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time + end + + def days_span(day) + (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7 + end + + def copy_time_to(other) + other.change(hour: hour, min: min, sec: sec, usec: try(:usec)) + end end end diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 8e5d723074..55ad384f4f 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -10,7 +10,11 @@ class DateTime end end - # Seconds since midnight: DateTime.now.seconds_since_midnight. + # Returns the number of seconds since 00:00:00. + # + # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0 + # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296 + # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399 def seconds_since_midnight sec + (min * 60) + (hour * 3600) end @@ -53,6 +57,16 @@ class DateTime # <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, # <tt>:minutes</tt>, <tt>:seconds</tt>. def advance(options) + unless options[:weeks].nil? + options[:weeks], partial_weeks = options[:weeks].divmod(1) + options[:days] = options.fetch(:days, 0) + 7 * partial_weeks + end + + unless options[:days].nil? + options[:days], partial_days = options[:days].divmod(1) + options[:hours] = options.fetch(:hours, 0) + 24 * partial_days + end + d = to_date.advance(options) datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) seconds_to_advance = \ @@ -63,7 +77,7 @@ class DateTime if seconds_to_advance.zero? datetime_advanced_by_date else - datetime_advanced_by_date.since seconds_to_advance + datetime_advanced_by_date.since(seconds_to_advance) end end @@ -151,7 +165,13 @@ class DateTime # Layers additional behavior on DateTime#<=> so that Time and # ActiveSupport::TimeWithZone instances can be compared with a DateTime. def <=>(other) - super other.to_datetime + if other.kind_of?(Infinity) + super + elsif other.respond_to? :to_datetime + super other.to_datetime + else + nil + end end end diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index 6ddfb72a0d..2a9c09fc29 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -71,9 +71,9 @@ class DateTime civil(year, month, day, hour, min, sec, offset) end - # Converts +self+ to a floating-point number of seconds since the Unix epoch. + # Converts +self+ to a floating-point number of seconds, including fractional microseconds, since the Unix epoch. def to_f - seconds_since_unix_epoch.to_f + seconds_since_unix_epoch.to_f + sec_fraction end # Converts +self+ to an integer number of seconds since the Unix epoch. diff --git a/activesupport/lib/active_support/core_ext/digest/uuid.rb b/activesupport/lib/active_support/core_ext/digest/uuid.rb new file mode 100644 index 0000000000..593c51bba2 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/digest/uuid.rb @@ -0,0 +1,51 @@ +require 'securerandom' + +module Digest + module UUID + DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + + # Generates a v5 non-random UUID (Universally Unique IDentifier). + # + # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs. + # uuid_from_hash always generates the same UUID for a given name and namespace combination. + # + # See RFC 4122 for details of UUID at: http://www.ietf.org/rfc/rfc4122.txt + def self.uuid_from_hash(hash_class, uuid_namespace, name) + if hash_class == Digest::MD5 + version = 3 + elsif hash_class == Digest::SHA1 + version = 5 + else + raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}." + end + + hash = hash_class.new + hash.update(uuid_namespace) + hash.update(name) + + ary = hash.digest.unpack('NnnnnN') + ary[2] = (ary[2] & 0x0FFF) | (version << 12) + ary[3] = (ary[3] & 0x3FFF) | 0x8000 + + "%08x-%04x-%04x-%04x-%04x%08x" % ary + end + + # Convenience method for uuid_from_hash using Digest::MD5. + def self.uuid_v3(uuid_namespace, name) + self.uuid_from_hash(Digest::MD5, uuid_namespace, name) + end + + # Convenience method for uuid_from_hash using Digest::SHA1. + def self.uuid_v5(uuid_namespace, name) + self.uuid_from_hash(Digest::SHA1, uuid_namespace, name) + end + + # Convenience method for SecureRandom.uuid. + def self.uuid_v4 + SecureRandom.uuid + end + end +end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 4501b7ff58..1343beb87a 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -35,7 +35,7 @@ module Enumerable if block_given? Hash[map { |elem| [yield(elem), elem] }] else - to_enum :index_by + to_enum(:index_by) { size if respond_to?(:size) } end end diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb index c3e6124a57..fad6fa8d9d 100644 --- a/activesupport/lib/active_support/core_ext/file/atomic.rb +++ b/activesupport/lib/active_support/core_ext/file/atomic.rb @@ -20,10 +20,10 @@ class File temp_file = Tempfile.new(basename(file_name), temp_dir) temp_file.binmode - yield temp_file + return_val = yield temp_file temp_file.close - if File.exists?(file_name) + if File.exist?(file_name) # Get original file permissions old_stat = stat(file_name) else @@ -40,7 +40,10 @@ class File chown(old_stat.uid, old_stat.gid, file_name) # This operation will affect filesystem ACL's chmod(old_stat.mode, file_name) - rescue Errno::EPERM + + # Make sure we return the result of the yielded block + return_val + rescue Errno::EPERM, Errno::EACCES # Changing file ownership failed, moving on. end end diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index 686f12c6da..af4d1da0eb 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/hash/compact' require 'active_support/core_ext/hash/conversions' require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/hash/except' @@ -5,3 +6,4 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/hash/transform_values' diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb new file mode 100644 index 0000000000..5dc9a05ec7 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/hash/compact.rb @@ -0,0 +1,20 @@ +class Hash + # Returns a hash with non +nil+ values. + # + # hash = { a: true, b: false, c: nil} + # hash.compact # => { a: true, b: false} + # hash # => { a: true, b: false, c: nil} + # { c: nil }.compact # => {} + def compact + self.select { |_, value| !value.nil? } + end + + # Replaces current hash with non +nil+ values. + # + # hash = { a: true, b: false, c: nil} + # hash.compact! # => { a: true, b: false} + # hash # => { a: true, b: false} + def compact! + self.reject! { |_, value| value.nil? } + end +end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 8930376ac8..2149d4439d 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -10,7 +10,7 @@ require 'active_support/core_ext/string/inflections' class Hash # Returns a string containing an XML representation of its receiver: # - # {'foo' => 1, 'bar' => 2}.to_xml + # { foo: 1, bar: 2 }.to_xml # # => # # <?xml version="1.0" encoding="UTF-8"?> # # <hash> @@ -43,7 +43,10 @@ class Hash # end # # { foo: Foo.new }.to_xml(skip_instruct: true) - # # => "<hash><bar>fooing!</bar></hash>" + # # => + # # <hash> + # # <bar>fooing!</bar> + # # </hash> # # * Otherwise, a node with +key+ as tag is created with a string representation of # +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added. @@ -102,7 +105,7 @@ class Hash # hash = Hash.from_xml(xml) # # => {"hash"=>{"foo"=>1, "bar"=>2}} # - # DisallowedType is raise if the XML contains attributes with <tt>type="yaml"</tt> or + # +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML. def from_xml(xml, disallowed_types = nil) ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h @@ -201,7 +204,7 @@ module ActiveSupport end def become_empty_string?(value) - # {"string" => true} + # { "string" => true } # No tests fail when the second term is removed. value['type'] == 'string' && value['nil'] != 'true' end @@ -218,7 +221,7 @@ module ActiveSupport def garbage?(value) # If the type is the only element which makes it then # this still makes the value nil, except if type is - # a XML node(where type['value'] is a Hash) + # an XML node(where type['value'] is a Hash) value['type'] && !value['type'].is_a?(::Hash) && value.size == 1 end @@ -238,4 +241,3 @@ module ActiveSupport end end - diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb index e07db50b77..9c9faf67ea 100644 --- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb @@ -1,27 +1,38 @@ class Hash # Returns a new hash with +self+ and +other_hash+ merged recursively. # - # h1 = { x: { y: [4,5,6] }, z: [7,8,9] } - # h2 = { x: { y: [7,8,9] }, z: 'xyz' } + # h1 = { a: true, b: { c: [1, 2, 3] } } + # h2 = { a: false, b: { x: [3, 4, 5] } } # - # h1.deep_merge(h2) #=> {x: {y: [7, 8, 9]}, z: "xyz"} - # h2.deep_merge(h1) #=> {x: {y: [4, 5, 6]}, z: [7, 8, 9]} - # h1.deep_merge(h2) { |key, old, new| Array.wrap(old) + Array.wrap(new) } - # #=> {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]} + # h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } } + # + # Like with Hash#merge in the standard library, a block can be provided + # to merge values: + # + # h1 = { a: 100, b: 200, c: { c1: 100 } } + # h2 = { b: 250, c: { c1: 200 } } + # h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val } + # # => { a: 100, b: 450, c: { c1: 300 } } def deep_merge(other_hash, &block) dup.deep_merge!(other_hash, &block) end # Same as +deep_merge+, but modifies +self+. def deep_merge!(other_hash, &block) - other_hash.each_pair do |k,v| - tv = self[k] - if tv.is_a?(Hash) && v.is_a?(Hash) - self[k] = tv.deep_merge(v, &block) + other_hash.each_pair do |current_key, other_value| + this_value = self[current_key] + + self[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash) + this_value.deep_merge(other_value, &block) else - self[k] = block && tv ? block.call(k, tv, v) : v + if block_given? && key?(current_key) + block.call(current_key, this_value, other_value) + else + other_value + end end end + self end end diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb index d90e996ad4..6e397abf51 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -1,13 +1,19 @@ class Hash - # Return a hash that includes everything but the given keys. This is useful for - # limiting a set of parameters to everything but a few known toggles: + # Returns a hash that includes everything but the given keys. + # hash = { a: true, b: false, c: nil} + # hash.except(:c) # => { a: true, b: false} + # hash # => { a: true, b: false, c: nil} # + # This is useful for limiting a set of parameters to everything but a few known toggles: # @person.update(params[:person].except(:admin)) def except(*keys) dup.except!(*keys) end # Replaces the hash without the given keys. + # hash = { a: true, b: false, c: nil} + # hash.except!(:c) # => { a: true, b: false} + # hash # => { a: true, b: false } def except!(*keys) keys.each { |key| delete(key) } self 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 981e8436bf..28cb3e2a3b 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -18,5 +18,6 @@ class Hash # # b = { b: 1 } # { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access + # # => {"b"=>1} alias nested_under_indifferent_access with_indifferent_access end diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index b4c451ace4..9297a59c46 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -1,12 +1,13 @@ class Hash - # Return a new hash with all keys converted using the block operation. + # Returns a new hash with all keys converted using the block operation. # # hash = { name: 'Rob', age: '28' } # # hash.transform_keys{ |key| key.to_s.upcase } - # # => { "NAME" => "Rob", "AGE" => "28" } + # # => {"NAME"=>"Rob", "AGE"=>"28"} def transform_keys - result = {} + return enum_for(:transform_keys) unless block_given? + result = self.class.new each_key do |key| result[yield(key)] = self[key] end @@ -16,35 +17,36 @@ class Hash # Destructively convert all keys using the block operations. # Same as transform_keys but modifies +self+. def transform_keys! + return enum_for(:transform_keys!) unless block_given? keys.each do |key| self[yield(key)] = delete(key) end self end - # Return a new hash with all keys converted to strings. + # Returns a new hash with all keys converted to strings. # # hash = { name: 'Rob', age: '28' } # # hash.stringify_keys - # #=> { "name" => "Rob", "age" => "28" } + # # => {"name"=>"Rob", "age"=>"28"} def stringify_keys - transform_keys{ |key| key.to_s } + transform_keys(&:to_s) end # Destructively convert all keys to strings. Same as # +stringify_keys+, but modifies +self+. def stringify_keys! - transform_keys!{ |key| key.to_s } + transform_keys!(&:to_s) end - # Return a new hash with all keys converted to symbols, as long as + # Returns a new hash with all keys converted to symbols, as long as # they respond to +to_sym+. # # hash = { 'name' => 'Rob', 'age' => '28' } # # hash.symbolize_keys - # #=> { name: "Rob", age: "28" } + # # => {:name=>"Rob", :age=>"28"} def symbolize_keys transform_keys{ |key| key.to_sym rescue key } end @@ -57,82 +59,108 @@ class Hash end alias_method :to_options!, :symbolize_keys! - # Validate all keys in a hash match <tt>*valid_keys</tt>, raising ArgumentError - # on a mismatch. Note that keys are NOT treated indifferently, meaning if you - # use strings for keys but assert symbols as keys, this will fail. + # Validate all keys in a hash match <tt>*valid_keys</tt>, raising + # ArgumentError on a mismatch. # - # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years" - # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name" + # Note that keys are treated differently than HashWithIndifferentAccess, + # meaning that string and symbol keys will not match. + # + # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age" + # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'" # { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing def assert_valid_keys(*valid_keys) valid_keys.flatten! each_key do |k| - raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k) + unless valid_keys.include?(k) + raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}") + end end end - # Return a new hash with all keys converted by the block operation. + # Returns a new hash with all keys converted by the block operation. # This includes the keys from the root hash and from all - # nested hashes. + # nested hashes and arrays. # # hash = { person: { name: 'Rob', age: '28' } } # # hash.deep_transform_keys{ |key| key.to_s.upcase } - # # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } } + # # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}} def deep_transform_keys(&block) - result = {} - each do |key, value| - result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value - end - result + _deep_transform_keys_in_object(self, &block) end # Destructively convert all keys by using the block operation. # This includes the keys from the root hash and from all - # nested hashes. + # nested hashes and arrays. def deep_transform_keys!(&block) - keys.each do |key| - value = delete(key) - self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value - end - self + _deep_transform_keys_in_object!(self, &block) end - # Return a new hash with all keys converted to strings. + # Returns a new hash with all keys converted to strings. # This includes the keys from the root hash and from all - # nested hashes. + # nested hashes and arrays. # # hash = { person: { name: 'Rob', age: '28' } } # # hash.deep_stringify_keys - # # => { "person" => { "name" => "Rob", "age" => "28" } } + # # => {"person"=>{"name"=>"Rob", "age"=>"28"}} def deep_stringify_keys - deep_transform_keys{ |key| key.to_s } + deep_transform_keys(&:to_s) end # Destructively convert all keys to strings. # This includes the keys from the root hash and from all - # nested hashes. + # nested hashes and arrays. def deep_stringify_keys! - deep_transform_keys!{ |key| key.to_s } + deep_transform_keys!(&:to_s) end - # Return a new hash with all keys converted to symbols, as long as + # Returns a new hash with all keys converted to symbols, as long as # they respond to +to_sym+. This includes the keys from the root hash - # and from all nested hashes. + # and from all nested hashes and arrays. # # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } } # # hash.deep_symbolize_keys - # # => { person: { name: "Rob", age: "28" } } + # # => {:person=>{:name=>"Rob", :age=>"28"}} def deep_symbolize_keys deep_transform_keys{ |key| key.to_sym rescue key } end # Destructively convert all keys to symbols, as long as they respond # to +to_sym+. This includes the keys from the root hash and from all - # nested hashes. + # nested hashes and arrays. def deep_symbolize_keys! deep_transform_keys!{ |key| key.to_sym rescue key } end + + private + # support methods for deep transforming nested hashes and arrays + def _deep_transform_keys_in_object(object, &block) + case object + when Hash + object.each_with_object({}) do |(key, value), result| + result[yield(key)] = _deep_transform_keys_in_object(value, &block) + end + when Array + object.map {|e| _deep_transform_keys_in_object(e, &block) } + else + object + end + end + + def _deep_transform_keys_in_object!(object, &block) + case object + when Hash + object.keys.each do |key| + value = object.delete(key) + object[yield(key)] = _deep_transform_keys_in_object!(value, &block) + end + object + when Array + object.map! {|e| _deep_transform_keys_in_object!(e, &block)} + else + object + end + end end diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index 9fa9b3dac4..41b2279013 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -1,6 +1,12 @@ class Hash - # Slice a hash to include only the given keys. This is useful for - # limiting an options hash to valid keys before passing to a method: + # Slice a hash to include only the given keys. Returns a hash containing + # the given keys. + # + # { a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b) + # # => {:a=>1, :b=>2} + # + # This is useful for limiting an options hash to valid keys before + # passing to a method: # # def search(criteria = {}) # criteria.assert_valid_keys(:mass, :velocity, :time) @@ -26,6 +32,8 @@ class Hash keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true) omit = slice(*self.keys - keys) hash = slice(*keys) + hash.default = default + hash.default_proc = default_proc if default_proc replace(hash) omit end diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb new file mode 100644 index 0000000000..e9bcce761f --- /dev/null +++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb @@ -0,0 +1,23 @@ +class Hash + # Returns a new hash with the results of running +block+ once for every value. + # The keys are unchanged. + # + # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 } + # # => { a: 2, b: 4, c: 6 } + def transform_values + return enum_for(:transform_values) unless block_given? + result = self.class.new + each do |key, value| + result[key] = yield(value) + end + result + end + + # Destructive +transform_values+ + def transform_values! + return enum_for(:transform_values!) unless block_given? + each do |key, value| + self[key] = yield(value) + end + end +end diff --git a/activesupport/lib/active_support/core_ext/integer/multiple.rb b/activesupport/lib/active_support/core_ext/integer/multiple.rb index 7c6c2f1ca7..c668c7c2eb 100644 --- a/activesupport/lib/active_support/core_ext/integer/multiple.rb +++ b/activesupport/lib/active_support/core_ext/integer/multiple.rb @@ -1,9 +1,9 @@ class Integer # Check whether the integer is evenly divisible by the argument. # - # 0.multiple_of?(0) #=> true - # 6.multiple_of?(5) #=> false - # 10.multiple_of?(2) #=> true + # 0.multiple_of?(0) # => true + # 6.multiple_of?(5) # => false + # 10.multiple_of?(2) # => true def multiple_of?(number) number != 0 ? self % number == 0 : zero? end diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb index 0275f4c037..364ed9d65f 100644 --- a/activesupport/lib/active_support/core_ext/kernel.rb +++ b/activesupport/lib/active_support/core_ext/kernel.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/agnostics' -require 'active_support/core_ext/kernel/debugger' +require 'active_support/core_ext/kernel/concern' +require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/singleton_class' diff --git a/activesupport/lib/active_support/core_ext/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb new file mode 100644 index 0000000000..bf72caa058 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb @@ -0,0 +1,10 @@ +require 'active_support/core_ext/module/concerning' + +module Kernel + # A shortcut to define a toplevel concern, not within a module. + # + # See Module::Concerning for more. + def concern(topic, &module_definition) + Object.concern topic, &module_definition + end +end diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb index 2073cac98d..1fde3db070 100644 --- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb +++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb @@ -1,10 +1,3 @@ -module Kernel - unless respond_to?(:debugger) - # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it). - def debugger - message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n" - defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message) - end - alias breakpoint debugger unless respond_to?(:breakpoint) - end -end +require 'active_support/deprecation' + +ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 36ab836457..9189e6d977 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -1,4 +1,3 @@ -require 'rbconfig' require 'tempfile' module Kernel @@ -29,27 +28,6 @@ module Kernel $VERBOSE = old_verbose end - # For compatibility - def silence_stderr #:nodoc: - silence_stream(STDERR) { yield } - end - - # Silences any stream for the duration of the block. - # - # silence_stream(STDOUT) do - # puts 'This will never be seen' - # end - # - # puts 'But this will' - def silence_stream(stream) - old_stream = stream.dup - stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null') - stream.sync = true - yield - ensure - stream.reopen(old_stream) - end - # Blocks and ignores any exception passed as argument if raised within the block. # # suppress(ZeroDivisionError) do @@ -60,51 +38,6 @@ module Kernel # puts 'This code gets executed and nothing related to ZeroDivisionError was seen' def suppress(*exception_classes) yield - rescue Exception => e - raise unless exception_classes.any? { |cls| e.kind_of?(cls) } - end - - # Captures the given stream and returns it: - # - # stream = capture(:stdout) { puts 'notice' } - # stream # => "notice\n" - # - # stream = capture(:stderr) { warn 'error' } - # stream # => "error\n" - # - # even for subprocesses: - # - # stream = capture(:stdout) { system('echo notice') } - # stream # => "notice\n" - # - # stream = capture(:stderr) { system('echo error 1>&2') } - # stream # => "error\n" - def capture(stream) - stream = stream.to_s - captured_stream = Tempfile.new(stream) - stream_io = eval("$#{stream}") - origin_stream = stream_io.dup - stream_io.reopen(captured_stream) - - yield - - stream_io.rewind - return captured_stream.read - ensure - captured_stream.close - captured_stream.unlink - stream_io.reopen(origin_stream) - end - alias :silence :capture - - # Silences both STDOUT and STDERR, even for subprocesses. - # - # quietly { system 'bundle install' } - def quietly - silence_stream(STDOUT) do - silence_stream(STDERR) do - yield - end - end + rescue *exception_classes end end diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index fe24f3716d..d9fb392752 100644 --- a/activesupport/lib/active_support/core_ext/load_error.rb +++ b/activesupport/lib/active_support/core_ext/load_error.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation/proxy_wrappers' + class LoadError REGEXPS = [ /^no such file to load -- (.+)$/i, @@ -7,6 +9,7 @@ class LoadError ] unless method_defined?(:path) + # Returns the path which was unable to be loaded. def path @path ||= begin REGEXPS.find do |regex| @@ -17,9 +20,11 @@ class LoadError end end + # Returns true if the given path name (except perhaps for the ".rb" + # extension) is the missing file which caused the exception to be raised. def is_missing?(location) location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '') end end -MissingSourceFile = LoadError
\ No newline at end of file +MissingSourceFile = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('MissingSourceFile', 'LoadError') diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index f2d4887df6..b4efff8b24 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/module/reachable' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/module/attr_internal' +require 'active_support/core_ext/module/concerning' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/deprecation' require 'active_support/core_ext/module/remove_method' diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb index 580cb80413..0a6fadf928 100644 --- a/activesupport/lib/active_support/core_ext/module/aliasing.rb +++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb @@ -19,9 +19,9 @@ class Module # alias_method :foo_without_feature?, :foo? # alias_method :foo?, :foo_with_feature? # - # so you can safely chain foo, foo?, and foo! with the same feature. + # so you can safely chain foo, foo?, foo! and/or foo= with the same feature. def alias_method_chain(target, feature) - # Strip out punctuation on predicates or bang methods since + # Strip out punctuation on predicates, bang or writer methods since # e.g. target?_without_feature is not a valid method name. aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 yield(aliased_target, punctuation) if block_given? diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb index db07d549b0..93fb598650 100644 --- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb +++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb @@ -27,10 +27,8 @@ class Module def attr_internal_define(attr_name, type) internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '') - class_eval do # class_eval is necessary on 1.9 or else the methods a made private - # use native attr_* methods as they are faster on some Ruby implementations - send("attr_#{type}", internal_name) - end + # use native attr_* methods as they are faster on some Ruby implementations + send("attr_#{type}", internal_name) attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer alias_method attr_name, internal_name remove_method internal_name 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 672cc0256f..d4e6b5a1ac 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -1,10 +1,59 @@ require 'active_support/core_ext/array/extract_options' +# Extends the module object with class/module and instance accessors for +# class/module attributes, just like the native attr* accessors for instance +# 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 + # defined. + # + # module HairColors + # mattr_reader :hair_colors + # end + # + # HairColors.hair_colors # => nil + # HairColors.class_variable_set("@@hair_colors", [:brown, :black]) + # HairColors.hair_colors # => [:brown, :black] + # + # The attribute name must be a valid method name in Ruby. + # + # module Foo + # mattr_reader :"1_Badname " + # end + # # => NameError: invalid attribute name + # + # If you want to opt out the creation on the instance reader method, pass + # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>. + # + # module HairColors + # mattr_writer :hair_colors, instance_reader: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors # => NoMethodError + # + # + # Also, you can pass a block to set up the attribute with a default value. + # + # module HairColors + # cattr_reader :hair_colors do + # [:brown, :black, :blonde, :red] + # end + # end + # + # class Person + # include HairColors + # end + # + # Person.hair_colors # => [:brown, :black, :blonde, :red] def mattr_reader(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@#{sym} = nil unless defined? @@#{sym} @@ -20,14 +69,60 @@ class Module end EOS end + class_variable_set("@@#{sym}", yield) if block_given? end end + alias :cattr_reader :mattr_reader + # Defines a class attribute and creates a class and instance writer methods to + # allow assignment to the attribute. + # + # module HairColors + # mattr_writer :hair_colors + # end + # + # class Person + # include HairColors + # end + # + # HairColors.hair_colors = [:brown, :black] + # Person.class_variable_get("@@hair_colors") # => [:brown, :black] + # Person.new.hair_colors = [:blonde, :red] + # HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red] + # + # If you want to opt out the instance writer method, pass + # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>. + # + # module HairColors + # mattr_writer :hair_colors, instance_writer: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:blonde, :red] # => NoMethodError + # + # Also, you can pass a block to set up the attribute with a default value. + # + # class HairColors + # mattr_writer :hair_colors do + # [:brown, :black, :blonde, :red] + # end + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] def mattr_writer(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) + @@#{sym} = nil unless defined? @@#{sym} + def self.#{sym}=(obj) @@#{sym} = obj end @@ -40,27 +135,78 @@ class Module end EOS end + send("#{sym}=", yield) if block_given? end end + alias :cattr_writer :mattr_writer - # Extends the module object with module and instance accessors for class attributes, - # just like the native attr* accessors for instance attributes. + # Defines both class and instance accessors for class attributes. # - # module AppConfiguration - # mattr_accessor :google_api_key + # module HairColors + # mattr_accessor :hair_colors + # end # - # self.google_api_key = "123456789" + # class Person + # include HairColors # end # - # AppConfiguration.google_api_key # => "123456789" - # AppConfiguration.google_api_key = "overriding the api key!" - # AppConfiguration.google_api_key # => "overriding the api key!" + # Person.hair_colors = [:brown, :black, :blonde, :red] + # Person.hair_colors # => [:brown, :black, :blonde, :red] + # Person.new.hair_colors # => [:brown, :black, :blonde, :red] + # + # If a subclass changes the value then that would also change the value for + # parent class. Similarly if parent class changes the value then that would + # change the value of subclasses too. + # + # class Male < Person + # end + # + # Male.hair_colors << :blue + # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue] # # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>. # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>. - # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>. - def mattr_accessor(*syms) - mattr_reader(*syms) - mattr_writer(*syms) + # + # module HairColors + # mattr_accessor :hair_colors, instance_writer: false, instance_reader: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:brown] # => NoMethodError + # Person.new.hair_colors # => NoMethodError + # + # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods. + # + # module HairColors + # mattr_accessor :hair_colors, instance_accessor: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:brown] # => NoMethodError + # Person.new.hair_colors # => NoMethodError + # + # Also you can pass a block to set up the attribute with a default value. + # + # module HairColors + # mattr_accessor :hair_colors do + # [:brown, :black, :blonde, :red] + # end + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] + def mattr_accessor(*syms, &blk) + mattr_reader(*syms, &blk) + mattr_writer(*syms, &blk) end + alias :cattr_accessor :mattr_accessor end diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb new file mode 100644 index 0000000000..07a392404e --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/concerning.rb @@ -0,0 +1,135 @@ +require 'active_support/concern' + +class Module + # = Bite-sized separation of concerns + # + # We often find ourselves with a medium-sized chunk of behavior that we'd + # like to extract, but only mix in to a single class. + # + # Extracting a plain old Ruby object to encapsulate it and collaborate or + # delegate to the original object is often a good choice, but when there's + # no additional state to encapsulate or we're making DSL-style declarations + # about the parent class, introducing new collaborators can obfuscate rather + # than simplify. + # + # The typical route is to just dump everything in a monolithic class, perhaps + # with a comment, as a least-bad alternative. Using modules in separate files + # means tedious sifting to get a big-picture view. + # + # = Dissatisfying ways to separate small concerns + # + # == Using comments: + # + # class Todo + # # Other todo implementation + # # ... + # + # ## Event tracking + # has_many :events + # + # before_create :track_creation + # after_destroy :track_deletion + # + # private + # def track_creation + # # ... + # end + # end + # + # == With an inline module: + # + # Noisy syntax. + # + # class Todo + # # Other todo implementation + # # ... + # + # module EventTracking + # extend ActiveSupport::Concern + # + # included do + # has_many :events + # before_create :track_creation + # after_destroy :track_deletion + # end + # + # private + # def track_creation + # # ... + # end + # end + # include EventTracking + # end + # + # == Mix-in noise exiled to its own file: + # + # Once our chunk of behavior starts pushing the scroll-to-understand it's + # boundary, we give in and move it to a separate file. At this size, the + # overhead feels in good proportion to the size of our extraction, despite + # diluting our at-a-glance sense of how things really work. + # + # class Todo + # # Other todo implementation + # # ... + # + # include TodoEventTracking + # end + # + # = Introducing Module#concerning + # + # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to + # separate bite-sized concerns. + # + # class Todo + # # Other todo implementation + # # ... + # + # concerning :EventTracking do + # included do + # has_many :events + # before_create :track_creation + # after_destroy :track_deletion + # end + # + # private + # def track_creation + # # ... + # end + # end + # end + # + # Todo.ancestors + # # => Todo, Todo::EventTracking, Object + # + # This small step has some wonderful ripple effects. We can + # * grok the behavior of our class in one glance, + # * clean up monolithic junk-drawer classes by separating their concerns, and + # * stop leaning on protected/private for crude "this is internal stuff" modularity. + module Concerning + # Define a new concern and mix it in. + def concerning(topic, &block) + include concern(topic, &block) + end + + # A low-cruft shortcut to define a concern. + # + # concern :EventTracking do + # ... + # end + # + # is equivalent to + # + # module EventTracking + # extend ActiveSupport::Concern + # + # ... + # end + def concern(topic, &module_definition) + const_set topic, Module.new { + extend ::ActiveSupport::Concern + module_eval(&module_definition) + } + end + end + include Concerning +end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 182e74d9e1..a5f4d03256 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -1,15 +1,23 @@ +require 'set' + class Module # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ # option is not used. class DelegationError < NoMethodError; end + RUBY_RESERVED_WORDS = Set.new( + %w(alias and BEGIN begin break case class def defined? do else elsif END + end ensure false for if in module next nil not or redo rescue retry + return self super then true undef unless until when while yield) + ).freeze + # Provides a +delegate+ class method to easily expose contained objects' # public methods as your own. # # ==== Options # * <tt>:to</tt> - Specifies the target object # * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix - # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ to be raised + # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ from being raised # # The macro receives one or more method names (specified as symbols or # strings) and the name of the target object via the <tt>:to</tt> option @@ -138,6 +146,8 @@ class Module # # Foo.new("Bar").name # raises NoMethodError: undefined method `name' # + # The target method must be public, otherwise it will raise +NoMethodError+. + # def delegate(*methods) options = methods.pop unless options.is_a?(Hash) && to = options[:to] @@ -161,14 +171,14 @@ class Module line = line.to_i to = to.to_s - to = 'self.class' if to == 'class' + to = "self.#{to}" if RUBY_RESERVED_WORDS.include?(to) methods.each do |method| # Attribute writer methods only accept one argument. Makes sure []= # methods still accept two arguments. definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block' - # The following generated methods call the target exactly once, storing + # The following generated method calls the target exactly once, storing # the returned value in a dummy variable. # # Reason is twofold: On one hand doing less calls is in general better. @@ -176,30 +186,32 @@ class Module # whereas conceptually, from the user point of view, the delegator should # be doing one call. if allow_nil - module_eval(<<-EOS, file, line - 3) - def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block) - _ = #{to} # _ = client - if !_.nil? || nil.respond_to?(:#{method}) # if !_.nil? || nil.respond_to?(:name) - _.#{method}(#{definition}) # _.name(*args, &block) - end # end - end # end - EOS + method_def = [ + "def #{method_prefix}#{method}(#{definition})", + "_ = #{to}", + "if !_.nil? || nil.respond_to?(:#{method})", + " _.#{method}(#{definition})", + "end", + "end" + ].join ';' else exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") - module_eval(<<-EOS, file, line - 2) - def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block) - _ = #{to} # _ = client - _.#{method}(#{definition}) # _.name(*args, &block) - rescue NoMethodError => e # rescue NoMethodError => e - if _.nil? && e.name == :#{method} # if _.nil? && e.name == :name - #{exception} # # add helpful message to the exception - else # else - raise # raise - end # end - end # end - EOS + method_def = [ + "def #{method_prefix}#{method}(#{definition})", + " _ = #{to}", + " _.#{method}(#{definition})", + "rescue NoMethodError => e", + " if _.nil? && e.name == :#{method}", + " #{exception}", + " else", + " raise", + " end", + "end" + ].join ';' end + + module_eval(method_def, file, line) end end end diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb index d873de197f..56d670fbe8 100644 --- a/activesupport/lib/active_support/core_ext/module/deprecation.rb +++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb @@ -1,5 +1,3 @@ -require 'active_support/deprecation/method_wrappers' - class Module # deprecate :foo # deprecate bar: 'message' diff --git a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb index b1097cc83b..1fde3db070 100644 --- a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb +++ b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb @@ -1,11 +1,3 @@ -class Module - ### - # TODO: remove this after 1.9 support is dropped - def methods_transplantable? # :nodoc: - x = Module.new { def foo; end } - Module.new { define_method :bar, x.instance_method(:foo) } - true - rescue TypeError - false - end -end +require 'active_support/deprecation' + +ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb index 719071d1c2..8a2569a7d0 100644 --- a/activesupport/lib/active_support/core_ext/module/remove_method.rb +++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb @@ -1,10 +1,13 @@ class Module + # Remove the named method, if it exists. def remove_possible_method(method) if method_defined?(method) || private_method_defined?(method) undef_method(method) end end + # Replace the existing method definition, if there is one, with the contents + # of the block. def redefine_method(method, &block) remove_possible_method(method) define_method(method, &block) diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb index e1ebd4f91c..6b447d772b 100644 --- a/activesupport/lib/active_support/core_ext/name_error.rb +++ b/activesupport/lib/active_support/core_ext/name_error.rb @@ -1,5 +1,12 @@ class NameError # Extract the name of the missing constant from the exception message. + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name + # end + # # => "HelloWorld" def missing_name if /undefined local variable or method/ !~ message $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message @@ -7,10 +14,16 @@ class NameError end # Was this exception raised because the given name was missing? + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name?("HelloWorld") + # end + # # => true def missing_name?(name) if name.is_a? Symbol - last_name = (missing_name || '').split('::').last - last_name == name.to_s + self.name == name else missing_name == name.to_s end diff --git a/activesupport/lib/active_support/core_ext/numeric/bytes.rb b/activesupport/lib/active_support/core_ext/numeric/bytes.rb index deea8e9358..dfbca32474 100644 --- a/activesupport/lib/active_support/core_ext/numeric/bytes.rb +++ b/activesupport/lib/active_support/core_ext/numeric/bytes.rb @@ -7,36 +7,56 @@ class Numeric EXABYTE = PETABYTE * 1024 # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes + # + # 2.bytes # => 2 def bytes self end alias :byte :bytes + # Returns the number of bytes equivalent to the kilobytes provided. + # + # 2.kilobytes # => 2048 def kilobytes self * KILOBYTE end alias :kilobyte :kilobytes + # Returns the number of bytes equivalent to the megabytes provided. + # + # 2.megabytes # => 2_097_152 def megabytes self * MEGABYTE end alias :megabyte :megabytes + # Returns the number of bytes equivalent to the gigabytes provided. + # + # 2.gigabytes # => 2_147_483_648 def gigabytes self * GIGABYTE end alias :gigabyte :gigabytes + # Returns the number of bytes equivalent to the terabytes provided. + # + # 2.terabytes # => 2_199_023_255_552 def terabytes self * TERABYTE end alias :terabyte :terabytes + # Returns the number of bytes equivalent to the petabytes provided. + # + # 2.petabytes # => 2_251_799_813_685_248 def petabytes self * PETABYTE end alias :petabyte :petabytes + # Returns the number of bytes equivalent to the exabytes provided. + # + # 2.exabytes # => 2_305_843_009_213_693_952 def exabytes self * EXABYTE end diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index 6d3635c69a..0c8ff79237 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -118,18 +118,28 @@ class Numeric end end - [Float, Fixnum, Bignum, BigDecimal].each do |klass| - klass.send(:alias_method, :to_default_s, :to_s) - - klass.send(:define_method, :to_s) do |*args| - if args[0].is_a?(Symbol) - format = args[0] - options = args[1] || {} + [Fixnum, Bignum].each do |klass| + klass.class_eval do + alias_method :to_default_s, :to_s + def to_s(base_or_format = 10, options = nil) + if base_or_format.is_a?(Symbol) + to_formatted_s(base_or_format, options || {}) + else + to_default_s(base_or_format) + end + end + end + end - self.to_formatted_s(format, options) + Float.class_eval do + alias_method :to_default_s, :to_s + def to_s(*args) + if args.empty? + to_default_s else - to_default_s(*args) + to_formatted_s(*args) end end end + end diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index 87b9a23aef..98716383f4 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -1,6 +1,8 @@ require 'active_support/duration' require 'active_support/core_ext/time/calculations' require 'active_support/core_ext/time/acts_like' +require 'active_support/core_ext/date/calculations' +require 'active_support/core_ext/date/acts_like' class Numeric # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years. @@ -36,44 +38,52 @@ class Numeric end alias :second :seconds + # Returns a Duration instance matching the number of minutes provided. + # + # 2.minutes # => 120 seconds def minutes ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]]) end alias :minute :minutes + # Returns a Duration instance matching the number of hours provided. + # + # 2.hours # => 7_200 seconds def hours ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]]) end alias :hour :hours + # Returns a Duration instance matching the number of days provided. + # + # 2.days # => 2 days def days ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) end alias :day :days + # Returns a Duration instance matching the number of weeks provided. + # + # 2.weeks # => 14 days def weeks ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) end alias :week :weeks + # Returns a Duration instance matching the number of fortnights provided. + # + # 2.fortnights # => 28 days def fortnights ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]]) end alias :fortnight :fortnights - # Reads best without arguments: 10.minutes.ago - def ago(time = ::Time.current) - time - self - end - - # Reads best with argument: 10.minutes.until(time) - alias :until :ago - - # Reads best with argument: 10.minutes.since(time) - def since(time = ::Time.current) - time + self + # Returns the number of milliseconds equivalent to the seconds provided. + # Used with the standard time durations, like 1.hour.in_milliseconds -- + # so we can feed them to JavaScript functions like getTime(). + # + # 2.in_milliseconds # => 2_000 + def in_milliseconds + self * 1000 end - - # Reads best without arguments: 10.minutes.from_now - alias :from_now :since end diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index ec2157221f..f4f9152d6a 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -8,7 +8,7 @@ require 'active_support/core_ext/object/inclusion' require 'active_support/core_ext/object/conversions' require 'active_support/core_ext/object/instance_variables' -require 'active_support/core_ext/object/to_json' +require 'active_support/core_ext/object/json' require 'active_support/core_ext/object/to_param' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/object/with_options' diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 8a5eb4bc93..38e43478df 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -4,36 +4,42 @@ class Object # An object is blank if it's false, empty, or a whitespace string. # For example, '', ' ', +nil+, [], and {} are all blank. # - # This simplifies: + # This simplifies # - # if address.nil? || address.empty? + # address.nil? || address.empty? # - # ...to: + # to # - # if address.blank? + # address.blank? + # + # @return [true, false] def blank? - respond_to?(:empty?) ? empty? : !self + respond_to?(:empty?) ? !!empty? : !self end - # An object is present if it's not <tt>blank?</tt>. + # An object is present if it's not blank. + # + # @return [true, false] def present? !blank? end - # Returns object if it's <tt>present?</tt> otherwise returns +nil+. - # <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>. + # Returns the receiver if it's present otherwise returns +nil+. + # <tt>object.presence</tt> is equivalent to # - # This is handy for any representation of objects where blank is the same - # as not present at all. For example, this simplifies a common check for - # HTTP POST/query parameters: + # object.present? ? object : nil + # + # For example, something like # # state = params[:state] if params[:state].present? # country = params[:country] if params[:country].present? # region = state || country || 'US' # - # ...becomes: + # becomes # # region = params[:state].presence || params[:country].presence || 'US' + # + # @return [Object] def presence self if present? end @@ -43,6 +49,8 @@ class NilClass # +nil+ is blank: # # nil.blank? # => true + # + # @return [true] def blank? true end @@ -52,6 +60,8 @@ class FalseClass # +false+ is blank: # # false.blank? # => true + # + # @return [true] def blank? true end @@ -61,6 +71,8 @@ class TrueClass # +true+ is not blank: # # true.blank? # => false + # + # @return [false] def blank? false end @@ -71,6 +83,8 @@ class Array # # [].blank? # => true # [1,2,3].blank? # => false + # + # @return [true, false] alias_method :blank?, :empty? end @@ -79,18 +93,28 @@ class Hash # # {}.blank? # => true # { key: 'value' }.blank? # => false + # + # @return [true, false] alias_method :blank?, :empty? end class String + BLANK_RE = /\A[[:space:]]*\z/ + # A string is blank if it's empty or contains whitespaces only: # - # ''.blank? # => true - # ' '.blank? # => true - # ' '.blank? # => true - # ' something here '.blank? # => false + # ''.blank? # => true + # ' '.blank? # => true + # "\t\n\r".blank? # => true + # ' blah '.blank? # => false + # + # Unicode whitespace is supported: + # + # "\u00a0".blank? # => true + # + # @return [true, false] def blank? - self !~ /[^[:space:]]/ + BLANK_RE === self end end @@ -99,6 +123,8 @@ class Numeric #:nodoc: # # 1.blank? # => false # 0.blank? # => false + # + # @return [false] def blank? false end diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb index 1d639f3af6..0191d2e973 100644 --- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb +++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb @@ -8,8 +8,8 @@ class Object # dup = object.deep_dup # dup.instance_variable_set(:@a, 1) # - # object.instance_variable_defined?(:@a) #=> false - # dup.instance_variable_defined?(:@a) #=> true + # object.instance_variable_defined?(:@a) # => false + # dup.instance_variable_defined?(:@a) # => true def deep_dup duplicable? ? dup : self end @@ -22,10 +22,10 @@ class Array # dup = array.deep_dup # dup[1][2] = 4 # - # array[1][2] #=> nil - # dup[1][2] #=> 4 + # array[1][2] # => nil + # dup[1][2] # => 4 def deep_dup - map { |it| it.deep_dup } + map(&:deep_dup) end end @@ -36,8 +36,8 @@ class Hash # dup = hash.deep_dup # dup[:a][:c] = 'c' # - # hash[:a][:c] #=> nil - # dup[:a][:c] #=> "c" + # hash[:a][:c] # => nil + # dup[:a][:c] # => "c" def deep_dup each_with_object(dup) do |(key, value), hash| hash[key.deep_dup] = value.deep_dup diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 9cd7485e2e..620f7b6561 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -19,7 +19,7 @@ class Object # Can you safely dup this object? # - # False for +nil+, +false+, +true+, symbol, and number objects; + # False for +nil+, +false+, +true+, symbol, number objects; # true otherwise. def duplicable? true @@ -78,13 +78,17 @@ end require 'bigdecimal' class BigDecimal - begin - BigDecimal.new('4.56').dup + def duplicable? + true + end +end - def duplicable? - true - end - rescue TypeError - # can't dup, so use superclass implementation +class Method + # Methods are not duplicable: + # + # method(:puts).duplicable? # => false + # method(:puts).dup # => TypeError: allocator undefined for Method + def duplicable? + false end end diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb index b5671f66d0..55f281b213 100644 --- a/activesupport/lib/active_support/core_ext/object/inclusion.rb +++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb @@ -12,4 +12,16 @@ class Object rescue NoMethodError raise ArgumentError.new("The parameter passed to #in? must respond to #include?") end + + # Returns the receiver if it's included in the argument otherwise returns +nil+. + # Argument must be any object which responds to +#include?+. Usage: + # + # params[:bucket_type].presence_in %w( project calendar ) + # + # This will throw an ArgumentError if the argument doesn't respond to +#include?+. + # + # @return [Object] + def presence_in(another_object) + self.in?(another_object) ? self : nil + end end diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb index 755e1c6b16..593a7a4940 100644 --- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb +++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb @@ -23,6 +23,6 @@ class Object # # C.new(0, 1).instance_variable_names # => ["@y", "@x"] def instance_variable_names - instance_variables.map { |var| var.to_s } + instance_variables.map(&:to_s) end end diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb new file mode 100644 index 0000000000..698b2d1920 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/object/json.rb @@ -0,0 +1,197 @@ +# Hack to load json gem first so we can overwrite its to_json. +require 'json' +require 'bigdecimal' +require 'active_support/core_ext/big_decimal/conversions' # for #to_s +require 'active_support/core_ext/hash/except' +require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/object/instance_variables' +require 'time' +require 'active_support/core_ext/time/conversions' +require 'active_support/core_ext/date_time/conversions' +require 'active_support/core_ext/date/conversions' +require 'active_support/core_ext/module/aliasing' + +# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting +# their default behavior. That said, we need to define the basic to_json method in all of them, +# otherwise they will always use to_json gem implementation, which is backwards incompatible in +# several cases (for instance, the JSON implementation for Hash does not work) with inheritance +# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json. +# +# On the other hand, we should avoid conflict with ::JSON.{generate,dump}(obj). Unfortunately, the +# JSON gem's encoder relies on its own to_json implementation to encode objects. Since it always +# passes a ::JSON::State object as the only argument to to_json, we can detect that and forward the +# calls to the original to_json method. +# +# It should be noted that when using ::JSON.{generate,dump} directly, ActiveSupport's encoder is +# bypassed completely. This means that as_json won't be invoked and the JSON gem will simply +# ignore any options it does not natively understand. This also means that ::JSON.{generate,dump} +# should give exactly the same results with or without active support. +[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].each do |klass| + klass.class_eval do + def to_json_with_active_support_encoder(options = nil) + if options.is_a?(::JSON::State) + # Called from JSON.{generate,dump}, forward it to JSON gem's to_json + self.to_json_without_active_support_encoder(options) + else + # to_json is being invoked directly, use ActiveSupport's encoder + ActiveSupport::JSON.encode(self, options) + end + end + + alias_method_chain :to_json, :active_support_encoder + end +end + +class Object + def as_json(options = nil) #:nodoc: + if respond_to?(:to_hash) + to_hash.as_json(options) + else + instance_values.as_json(options) + end + end +end + +class Struct #:nodoc: + def as_json(options = nil) + Hash[members.zip(values)].as_json(options) + end +end + +class TrueClass + def as_json(options = nil) #:nodoc: + self + end +end + +class FalseClass + def as_json(options = nil) #:nodoc: + self + end +end + +class NilClass + def as_json(options = nil) #:nodoc: + self + end +end + +class String + def as_json(options = nil) #:nodoc: + self + end +end + +class Symbol + def as_json(options = nil) #:nodoc: + to_s + end +end + +class Numeric + def as_json(options = nil) #:nodoc: + self + end +end + +class Float + # Encoding Infinity or NaN to JSON should return "null". The default returns + # "Infinity" or "NaN" which are not valid JSON. + def as_json(options = nil) #:nodoc: + finite? ? self : nil + end +end + +class BigDecimal + # A BigDecimal would be naturally represented as a JSON number. Most libraries, + # however, parse non-integer JSON numbers directly as floats. Clients using + # those libraries would get in general a wrong number and no way to recover + # other than manually inspecting the string with the JSON code itself. + # + # That's why a JSON string is returned. The JSON literal is not numeric, but + # if the other end knows by contract that the data is supposed to be a + # BigDecimal, it still has the chance to post-process the string and get the + # real value. + def as_json(options = nil) #:nodoc: + finite? ? to_s : nil + end +end + +class Regexp + def as_json(options = nil) #:nodoc: + to_s + end +end + +module Enumerable + def as_json(options = nil) #:nodoc: + to_a.as_json(options) + end +end + +class Range + def as_json(options = nil) #:nodoc: + to_s + end +end + +class Array + def as_json(options = nil) #:nodoc: + map { |v| options ? v.as_json(options.dup) : v.as_json } + end +end + +class Hash + def as_json(options = nil) #:nodoc: + # create a subset of the hash by applying :only or :except + subset = if options + if attrs = options[:only] + slice(*Array(attrs)) + elsif attrs = options[:except] + except(*Array(attrs)) + else + self + end + else + self + end + + Hash[subset.map { |k, v| [k.to_s, options ? v.as_json(options.dup) : v.as_json] }] + end +end + +class Time + def as_json(options = nil) #:nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + xmlschema(ActiveSupport::JSON::Encoding.time_precision) + else + %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) + end + end +end + +class Date + def as_json(options = nil) #:nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + strftime("%Y-%m-%d") + else + strftime("%Y/%m/%d") + end + end +end + +class DateTime + def as_json(options = nil) #:nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + xmlschema(ActiveSupport::JSON::Encoding.time_precision) + else + strftime('%Y/%m/%d %H:%M:%S %z') + end + end +end + +class Process::Status #:nodoc: + def as_json(options = nil) + { :exitstatus => exitstatus, :pid => pid } + end +end diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb deleted file mode 100644 index 83cc8066e7..0000000000 --- a/activesupport/lib/active_support/core_ext/object/to_json.rb +++ /dev/null @@ -1,27 +0,0 @@ -# Hack to load json gem first so we can overwrite its to_json. -begin - require 'json' -rescue LoadError -end - -# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting -# their default behavior. That said, we need to define the basic to_json method in all of them, -# otherwise they will always use to_json gem implementation, which is backwards incompatible in -# several cases (for instance, the JSON implementation for Hash does not work) with inheritance -# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json. -[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass| - klass.class_eval do - # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info. - def to_json(options = nil) - ActiveSupport::JSON.encode(self, options) - end - end -end - -module Process - class Status - def as_json(options = nil) - { :exitstatus => exitstatus, :pid => pid } - end - end -end diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index 3b137ce6ae..684d4ef57e 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -1,58 +1 @@ -class Object - # Alias of <tt>to_s</tt>. - def to_param - to_s - end -end - -class NilClass - # Returns +self+. - def to_param - self - end -end - -class TrueClass - # Returns +self+. - def to_param - self - end -end - -class FalseClass - # Returns +self+. - def to_param - self - end -end - -class Array - # Calls <tt>to_param</tt> on all its elements and joins the result with - # slashes. This is used by <tt>url_for</tt> in Action Pack. - def to_param - collect { |e| e.to_param }.join '/' - end -end - -class Hash - # Returns a string representation of the receiver suitable for use as a URL - # query string: - # - # {name: 'David', nationality: 'Danish'}.to_param - # # => "name=David&nationality=Danish" - # - # An optional namespace can be passed to enclose the param names: - # - # {name: 'David', nationality: 'Danish'}.to_param('user') - # # => "user[name]=David&user[nationality]=Danish" - # - # The string pairs "key=value" that conform the query string - # are sorted lexicographically in ascending order. - # - # This method is also aliased as +to_query+. - def to_param(namespace = nil) - collect do |key, value| - value.to_query(namespace ? "#{namespace}[#{key}]" : key) - end.sort! * '&' - end -end +require 'active_support/core_ext/object/to_query' diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb index 5d5fcf00e0..ec5ace4e16 100644 --- a/activesupport/lib/active_support/core_ext/object/to_query.rb +++ b/activesupport/lib/active_support/core_ext/object/to_query.rb @@ -1,27 +1,84 @@ -require 'active_support/core_ext/object/to_param' +require 'cgi' class Object - # Converts an object into a string suitable for use as a URL query string, using the given <tt>key</tt> as the - # param name. - # - # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work. + # Alias of <tt>to_s</tt>. + def to_param + to_s + end + + # Converts an object into a string suitable for use as a URL query string, + # using the given <tt>key</tt> as the param name. def to_query(key) - require 'cgi' unless defined?(CGI) && defined?(CGI::escape) "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}" end end +class NilClass + # Returns +self+. + def to_param + self + end +end + +class TrueClass + # Returns +self+. + def to_param + self + end +end + +class FalseClass + # Returns +self+. + def to_param + self + end +end + class Array + # Calls <tt>to_param</tt> on all its elements and joins the result with + # slashes. This is used by <tt>url_for</tt> in Action Pack. + def to_param + collect(&:to_param).join '/' + end + # Converts an array into a string suitable for use as a URL query string, # using the given +key+ as the param name. # # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" def to_query(key) prefix = "#{key}[]" - collect { |value| value.to_query(prefix) }.join '&' + + if empty? + nil.to_query(prefix) + else + collect { |value| value.to_query(prefix) }.join '&' + end end end class Hash - alias_method :to_query, :to_param + # Returns a string representation of the receiver suitable for use as a URL + # query string: + # + # {name: 'David', nationality: 'Danish'}.to_query + # # => "name=David&nationality=Danish" + # + # An optional namespace can be passed to enclose key names: + # + # {name: 'David', nationality: 'Danish'}.to_query('user') + # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" + # + # The string pairs "key=value" that conform the query string + # are sorted lexicographically in ascending order. + # + # This method is also aliased as +to_param+. + def to_query(namespace = nil) + collect do |key, value| + unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty? + value.to_query(namespace ? "#{namespace}[#{key}]" : key) + end + end.compact.sort! * '&' + end + + alias_method :to_param, :to_query end diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 534bbe3c42..e0f70b9caa 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -9,7 +9,23 @@ class Object # # instead of # - # @person ? @person.name : nil + # @person.name if @person + # + # +try+ calls can be chained: + # + # @person.try(:spouse).try(:name) + # + # instead of + # + # @person.spouse.name if @person && @person.spouse + # + # +try+ will also return +nil+ if the receiver does not respond to the method: + # + # @person.try(:non_existing_method) # => nil + # + # instead of + # + # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil # # +try+ returns +nil+ when called on +nil+ regardless of whether it responds # to the method: @@ -24,7 +40,7 @@ class Object # # The number of arguments in the signature must match. If the object responds # to the method the call is attempted and +ArgumentError+ is still raised - # otherwise. + # in case of argument mismatch. # # If +try+ is called without arguments it yields the receiver to a given # block unless it is +nil+: @@ -33,24 +49,33 @@ class Object # ... # end # - # Please also note that +try+ is defined on +Object+, therefore it won't work + # You can also call try with a block without accepting an argument, and the block + # will be instance_eval'ed instead: + # + # @person.try { upcase.truncate(50) } + # + # Please also note that +try+ is defined on +Object+. Therefore, it won't work # with instances of classes that do not have +Object+ among their ancestors, # like direct subclasses of +BasicObject+. For example, using +try+ with # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on - # delegator itself. + # the delegator itself. def try(*a, &b) - if a.empty? && block_given? - yield self - else - public_send(*a, &b) if respond_to?(a.first) - end + try!(*a, &b) if a.empty? || respond_to?(a.first) end - # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and - # does not implemented the tried method. + # Same as #try, but raises a NoMethodError exception if the receiver is + # not +nil+ and does not implement the tried method. + # + # "a".try!(:upcase) # => "A" + # nil.try!(:upcase) # => nil + # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum def try!(*a, &b) if a.empty? && block_given? - yield self + if b.arity.zero? + instance_eval(&b) + else + yield self + end else public_send(*a, &b) end @@ -59,12 +84,12 @@ end class NilClass # Calling +try+ on +nil+ always returns +nil+. - # It becomes specially helpful when navigating through associations that may return +nil+. + # It becomes especially helpful when navigating through associations that may return +nil+. # # nil.try(:name) # => nil # # Without +try+ - # @person && !@person.children.blank? && @person.children.first.name + # @person && @person.children.any? && @person.children.first.name # # With +try+ # @person.try(:children).try(:first).try(:name) @@ -72,6 +97,9 @@ class NilClass nil end + # Calling +try!+ on +nil+ always returns +nil+. + # + # nil.try!(:name) # => nil def try!(*args) nil end diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index 42e388b065..7d38e1d134 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -34,9 +34,36 @@ class Object # body i18n.t :body, user_name: user.name # end # + # When you don't pass an explicit receiver, it executes the whole block + # in merging options context: + # + # class Account < ActiveRecord::Base + # with_options dependent: :destroy do + # has_many :customers + # has_many :products + # has_many :invoices + # has_many :expenses + # end + # end + # # <tt>with_options</tt> can also be nested since the call is forwarded to its receiver. - # Each nesting level will merge inherited defaults in addition to their own. - def with_options(options) - yield ActiveSupport::OptionMerger.new(self, options) + # + # NOTE: Each nesting level will merge inherited defaults in addition to their own. + # + # class Post < ActiveRecord::Base + # with_options if: :persisted?, length: { minimum: 50 } do + # validates :content, if: -> { content.present? } + # end + # end + # + # The code is equivalent to: + # + # validates :content, length: { minimum: 50 }, if: -> { content.present? } + # + # Hence the inherited default for `if` key is ignored. + # + def with_options(options, &block) + option_merger = ActiveSupport::OptionMerger.new(self, options) + block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger) end end diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb index 1d8b1ede5a..9368e81235 100644 --- a/activesupport/lib/active_support/core_ext/range.rb +++ b/activesupport/lib/active_support/core_ext/range.rb @@ -1,3 +1,4 @@ require 'active_support/core_ext/range/conversions' require 'active_support/core_ext/range/include_range' require 'active_support/core_ext/range/overlaps' +require 'active_support/core_ext/range/each' diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb new file mode 100644 index 0000000000..ecef78f55f --- /dev/null +++ b/activesupport/lib/active_support/core_ext/range/each.rb @@ -0,0 +1,23 @@ +require 'active_support/core_ext/module/aliasing' + +class Range #:nodoc: + + def each_with_time_with_zone(&block) + ensure_iteration_allowed + each_without_time_with_zone(&block) + end + alias_method_chain :each, :time_with_zone + + def step_with_time_with_zone(n = 1, &block) + ensure_iteration_allowed + step_without_time_with_zone(n, &block) + end + alias_method_chain :step, :time_with_zone + + private + def ensure_iteration_allowed + if first.is_a?(Time) + raise TypeError, "can't iterate from #{first.class}" + end + end +end diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb new file mode 100644 index 0000000000..6cdbea1f37 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/securerandom.rb @@ -0,0 +1,23 @@ +require 'securerandom' + +module SecureRandom + BASE58_ALPHABET = ('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a - ['0', 'O', 'I', 'l'] + # SecureRandom.base58 generates a random base58 string. + # + # The argument _n_ specifies the length, of the random string to be generated. + # + # If _n_ is not specified or is nil, 16 is assumed. It may be larger in the future. + # + # The result may contain alphanumeric characters except 0, O, I and l + # + # p SecureRandom.base58 #=> "4kUgL2pdQMSCQtjE" + # p SecureRandom.base58(24) #=> "77TMHrHJFvFDwodq8w7Ev2m7" + # + def self.base58(n = 16) + SecureRandom.random_bytes(n).unpack("C*").map do |byte| + idx = byte % 64 + idx = SecureRandom.random_number(58) if idx >= 58 + BASE58_ALPHABET[idx] + end.join + end +end diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index ee3b6d2b3f..ebd0dd3fc7 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -8,22 +8,22 @@ class String # the beginning of the range is greater than the end of the string. # # str = "hello" - # str.at(0) #=> "h" - # str.at(1..3) #=> "ell" - # str.at(-2) #=> "l" - # str.at(-2..-1) #=> "lo" - # str.at(5) #=> nil - # str.at(5..-1) #=> "" + # str.at(0) # => "h" + # str.at(1..3) # => "ell" + # str.at(-2) # => "l" + # str.at(-2..-1) # => "lo" + # str.at(5) # => nil + # str.at(5..-1) # => "" # # If a Regexp is given, the matching portion of the string is returned. # If a String is given, that given string is returned if it occurs in # the string. In both cases, nil is returned if there is no match. # # str = "hello" - # str.at(/lo/) #=> "lo" - # str.at(/ol/) #=> nil - # str.at("lo") #=> "lo" - # str.at("ol") #=> nil + # str.at(/lo/) # => "lo" + # str.at(/ol/) # => nil + # str.at("lo") # => "lo" + # str.at("ol") # => nil def at(position) self[position] end @@ -32,15 +32,15 @@ class String # If the position is negative, it is counted from the end of the string. # # str = "hello" - # str.from(0) #=> "hello" - # str.from(3) #=> "lo" - # str.from(-2) #=> "lo" + # str.from(0) # => "hello" + # str.from(3) # => "lo" + # str.from(-2) # => "lo" # # You can mix it with +to+ method and do fun things like: # # str = "hello" - # str.from(0).to(-1) #=> "hello" - # str.from(1).to(-2) #=> "ell" + # str.from(0).to(-1) # => "hello" + # str.from(1).to(-2) # => "ell" def from(position) self[position..-1] end @@ -49,34 +49,34 @@ class String # If the position is negative, it is counted from the end of the string. # # str = "hello" - # str.to(0) #=> "h" - # str.to(3) #=> "hell" - # str.to(-2) #=> "hell" + # str.to(0) # => "h" + # str.to(3) # => "hell" + # str.to(-2) # => "hell" # # You can mix it with +from+ method and do fun things like: # # str = "hello" - # str.from(0).to(-1) #=> "hello" - # str.from(1).to(-2) #=> "ell" + # str.from(0).to(-1) # => "hello" + # str.from(1).to(-2) # => "ell" def to(position) - self[0, position + 1] + self[0..position] end # Returns the first character. If a limit is supplied, returns a substring # from the beginning of the string until it reaches the limit value. If the - # given limit is greater than or equal to the string length, returns self. + # given limit is greater than or equal to the string length, returns a copy of self. # # str = "hello" - # str.first #=> "h" - # str.first(1) #=> "h" - # str.first(2) #=> "he" - # str.first(0) #=> "" - # str.first(6) #=> "hello" + # str.first # => "h" + # str.first(1) # => "h" + # str.first(2) # => "he" + # str.first(0) # => "" + # str.first(6) # => "hello" def first(limit = 1) if limit == 0 '' elsif limit >= size - self + self.dup else to(limit - 1) end @@ -84,19 +84,19 @@ class String # Returns the last character of the string. If a limit is supplied, returns a substring # from the end of the string until it reaches the limit value (counting backwards). If - # the given limit is greater than or equal to the string length, returns self. + # the given limit is greater than or equal to the string length, returns a copy of self. # # str = "hello" - # str.last #=> "o" - # str.last(1) #=> "o" - # str.last(2) #=> "lo" - # str.last(0) #=> "" - # str.last(6) #=> "hello" + # str.last # => "o" + # str.last(1) # => "o" + # str.last(2) # => "lo" + # str.last(0) # => "" + # str.last(6) # => "hello" def last(limit = 1) if limit == 0 '' elsif limit >= size - self + self.dup else from(-limit) end diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index 6691fc0995..3e0cb8a7ac 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -36,20 +36,20 @@ class String # Converts a string to a Date value. # - # "1-1-2012".to_date #=> Sun, 01 Jan 2012 - # "01/01/2012".to_date #=> Sun, 01 Jan 2012 - # "2012-12-13".to_date #=> Thu, 13 Dec 2012 - # "12/13/2012".to_date #=> ArgumentError: invalid date + # "1-1-2012".to_date # => Sun, 01 Jan 2012 + # "01/01/2012".to_date # => Sun, 01 Jan 2012 + # "2012-12-13".to_date # => Thu, 13 Dec 2012 + # "12/13/2012".to_date # => ArgumentError: invalid date def to_date ::Date.parse(self, false) unless blank? end # Converts a string to a DateTime value. # - # "1-1-2012".to_datetime #=> Sun, 01 Jan 2012 00:00:00 +0000 - # "01/01/2012 23:59:59".to_datetime #=> Sun, 01 Jan 2012 23:59:59 +0000 - # "2012-12-13 12:50".to_datetime #=> Thu, 13 Dec 2012 12:50:00 +0000 - # "12/13/2012".to_datetime #=> ArgumentError: invalid date + # "1-1-2012".to_datetime # => Sun, 01 Jan 2012 00:00:00 +0000 + # "01/01/2012 23:59:59".to_datetime # => Sun, 01 Jan 2012 23:59:59 +0000 + # "2012-12-13 12:50".to_datetime # => Thu, 13 Dec 2012 12:50:00 +0000 + # "12/13/2012".to_datetime # => ArgumentError: invalid date def to_datetime ::DateTime.parse(self, false) unless blank? end diff --git a/activesupport/lib/active_support/core_ext/string/exclude.rb b/activesupport/lib/active_support/core_ext/string/exclude.rb index 114bcb87f0..0ac684f6ee 100644 --- a/activesupport/lib/active_support/core_ext/string/exclude.rb +++ b/activesupport/lib/active_support/core_ext/string/exclude.rb @@ -2,9 +2,9 @@ class String # The inverse of <tt>String#include?</tt>. Returns true if the string # does not include the other string. # - # "hello".exclude? "lo" #=> false - # "hello".exclude? "ol" #=> true - # "hello".exclude? ?h #=> false + # "hello".exclude? "lo" # => false + # "hello".exclude? "ol" # => true + # "hello".exclude? ?h # => false def exclude?(string) !include?(string) end diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index 49c0df6026..096292dc58 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -3,7 +3,7 @@ class String # the string, and then changing remaining consecutive whitespace # groups into one space each. # - # Note that it handles both ASCII and Unicode whitespace like mongolian vowel separator (U+180E). + # Note that it handles both ASCII and Unicode whitespace. # # %{ Multi-line # string }.squish # => "Multi-line string" @@ -13,6 +13,9 @@ class String end # Performs a destructive squish. See String#squish. + # str = " foo bar \n \t boo" + # str.squish! # => "foo bar boo" + # str # => "foo bar boo" def squish! gsub!(/\A[[:space:]]+/, '') gsub!(/[[:space:]]+\z/, '') @@ -20,14 +23,24 @@ class String self end - # Returns a new string with all occurrences of the pattern removed. Short-hand for String#gsub(pattern, ''). - def remove(pattern) - gsub pattern, '' + # Returns a new string with all occurrences of the patterns removed. + # str = "foo bar test" + # str.remove(" test") # => "foo bar" + # str # => "foo bar test" + def remove(*patterns) + dup.remove!(*patterns) end - # Alters the string by removing all occurrences of the pattern. Short-hand for String#gsub!(pattern, ''). - def remove!(pattern) - gsub! pattern, '' + # Alters the string by removing all occurrences of the patterns. + # str = "foo bar test" + # str.remove!(" test") # => "foo bar" + # str # => "foo bar" + def remove!(*patterns) + patterns.each do |pattern| + gsub! pattern, "" + end + + self end # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>: @@ -62,4 +75,28 @@ class String "#{self[0, stop]}#{omission}" end + + # Truncates a given +text+ after a given number of words (<tt>words_count</tt>): + # + # 'Once upon a time in a world far far away'.truncate_words(4) + # # => "Once upon a time..." + # + # Pass a string or regexp <tt>:separator</tt> to specify a different separator of words: + # + # 'Once<br>upon<br>a<br>time<br>in<br>a<br>world'.truncate_words(5, separator: '<br>') + # # => "Once<br>upon<br>a<br>time<br>in..." + # + # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "..."): + # + # 'And they found that many people were sleeping better.'.truncate_words(5, omission: '... (continued)') + # # => "And they found that many... (continued)" + def truncate_words(words_count, options = {}) + sep = options[:separator] || /\s+/ + sep = Regexp.escape(sep.to_s) unless Regexp === sep + if self =~ /\A((?:.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m + $1 + (options[:omission] || '...') + else + dup + end + end end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 56e8a5f98d..38d567c014 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -31,7 +31,7 @@ class String def pluralize(count = nil, locale = :en) locale = count if count.is_a?(Symbol) if count == 1 - self + self.dup else ActiveSupport::Inflector.pluralize(self, locale) end @@ -130,6 +130,8 @@ class String # # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => '' # # See also +deconstantize+. def demodulize @@ -182,21 +184,24 @@ class String # # 'egg_and_hams'.classify # => "EggAndHam" # 'posts'.classify # => "Post" - # - # Singular names are not handled correctly. - # - # 'business'.classify # => "Business" def classify ActiveSupport::Inflector.classify(self) end - # Capitalizes the first word, turns underscores into spaces, and strips '_id'. + # Capitalizes the first word, turns underscores into spaces, and strips a + # trailing '_id' if present. # Like +titleize+, this is meant for creating pretty output. # - # 'employee_salary'.humanize # => "Employee salary" - # 'author_id'.humanize # => "Author" - def humanize - ActiveSupport::Inflector.humanize(self) + # The capitalization of the first word can be turned off by setting the + # optional parameter +capitalize+ to false. + # By default, this parameter is true. + # + # 'employee_salary'.humanize # => "Employee salary" + # 'author_id'.humanize # => "Author" + # 'author_id'.humanize(capitalize: false) # => "author" + # '_id'.humanize # => "Id" + def humanize(options = {}) + ActiveSupport::Inflector.humanize(self, options) end # Creates a foreign key name from a class name. diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index a124202936..2eedd4fdb1 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'active_support/multibyte' class String 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 dc033ed11b..bae4e206e6 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -4,9 +4,10 @@ require 'active_support/core_ext/kernel/singleton_class' class ERB module Util HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } - JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' } - HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/ - JSON_ESCAPE_REGEXP = /[&"><]/ + JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' } + HTML_ESCAPE_REGEXP = /[&"'><]/ + HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/ + JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u # A utility method for escaping HTML tag characters. # This method is also aliased as <tt>h</tt>. @@ -17,12 +18,7 @@ class ERB # puts html_escape('is a > 0 & a < 10?') # # => is a > 0 & a < 10? def html_escape(s) - s = s.to_s - if s.html_safe? - s - else - s.gsub(/[&"'><]/, HTML_ESCAPE).html_safe - end + unwrapped_html_escape(s).html_safe end # Aliasing twice issues a warning "discarding old...". Remove first to avoid it. @@ -34,6 +30,18 @@ class ERB singleton_class.send(:remove_method, :html_escape) module_function :html_escape + # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer. + # This method is not for public consumption! Seriously! + def unwrapped_html_escape(s) # :nodoc: + s = s.to_s + if s.html_safe? + s + else + s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE) + end + end + module_function :unwrapped_html_escape + # A utility method for escaping HTML without affecting existing escaped entities. # # html_escape_once('1 < 2 & 3') @@ -48,17 +56,61 @@ class ERB module_function :html_escape_once - # A utility method for escaping HTML entities in JSON strings - # using \uXXXX JavaScript escape sequences for string literals: + # A utility method for escaping HTML entities in JSON strings. Specifically, the + # &, > and < characters are replaced with their equivalent unicode escaped form - + # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also + # escaped as they are treated as newline characters in some JavaScript engines. + # These sequences have identical meaning as the original characters inside the + # context of a JSON string, so assuming the input is a valid and well-formed + # JSON value, the output will have equivalent meaning when parsed: + # + # json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"}) + # # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}" + # + # json_escape(json) + # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}" + # + # JSON.parse(json) == JSON.parse(json_escape(json)) + # # => true + # + # The intended use case for this method is to escape JSON strings before including + # them inside a script tag to avoid XSS vulnerability: + # + # <script> + # var currentUser = <%= raw json_escape(current_user.to_json) %>; + # </script> # - # json_escape('is a > 0 & a < 10?') - # # => is a \u003E 0 \u0026 a \u003C 10? + # It is necessary to +raw+ the result of +json_escape+, so that quotation marks + # don't get converted to <tt>"</tt> entities. +json_escape+ doesn't + # automatically flag the result as HTML safe, since the raw value is unsafe to + # use inside HTML attributes. # - # Note that after this operation is performed the output is not - # valid JSON. In particular double quotes are removed: + # If your JSON is being used downstream for insertion into the DOM, be aware of + # whether or not it is being inserted via +html()+. Most JQuery plugins do this. + # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated + # content returned by your JSON. # - # json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}') - # # => {name:john,created_at:2010-04-28T01:39:31Z,id:1} + # If you need to output JSON elsewhere in your HTML, you can just do something + # like this, as any unsafe characters (including quotation marks) will be + # automatically escaped for you: + # + # <div data-user-info="<%= current_user.to_json %>">...</div> + # + # WARNING: this helper only works with valid JSON. Using this on non-JSON values + # will open up serious XSS vulnerabilities. For example, if you replace the + # +current_user.to_json+ in the example above with user input instead, the browser + # will happily eval() that string as JavaScript. + # + # The escaping performed in this method is identical to those performed in the + # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is + # set to true. Because this transformation is idempotent, this helper can be + # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true. + # + # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+ + # is enabled, or if you are unsure where your JSON string originated from, it + # is recommended that you always apply this helper (other libraries, such as the + # JSON gem, do not provide this kind of protection by default; also some gems + # might override +to_json+ to bypass Active Support's encoder). def json_escape(s) result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE) s.html_safe? ? result.html_safe : result @@ -84,7 +136,7 @@ module ActiveSupport #:nodoc: class SafeBuffer < String UNSAFE_STRING_METHODS = %w( capitalize chomp chop delete downcase gsub lstrip next reverse rstrip - slice squeeze strip sub succ swapcase tr tr_s upcase prepend + slice squeeze strip sub succ swapcase tr tr_s upcase ) alias_method :original_concat, :concat @@ -102,7 +154,11 @@ module ActiveSupport #:nodoc: else if html_safe? new_safe_buffer = super - new_safe_buffer.instance_eval { @html_safe = true } + + if new_safe_buffer + new_safe_buffer.instance_variable_set :@html_safe, true + end + new_safe_buffer else to_str[*args] @@ -130,28 +186,27 @@ module ActiveSupport #:nodoc: end def concat(value) - if !html_safe? || value.html_safe? - super(value) - else - super(ERB::Util.h(value)) - end + super(html_escape_interpolated_argument(value)) end alias << concat + def prepend(value) + super(html_escape_interpolated_argument(value)) + end + def +(other) dup.concat(other) end def %(args) - args = Array(args).map do |arg| - if !html_safe? || arg.html_safe? - arg - else - ERB::Util.h(arg) - end + case args + when Hash + escaped_args = Hash[args.map { |k,arg| [k, html_escape_interpolated_argument(arg)] }] + else + escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) } end - self.class.new(super(args)) + self.class.new(super(escaped_args)) end def html_safe? @@ -167,11 +222,11 @@ module ActiveSupport #:nodoc: end def encode_with(coder) - coder.represent_scalar nil, to_str + coder.represent_object nil, to_str end UNSAFE_STRING_METHODS.each do |unsafe_method| - if 'String'.respond_to?(unsafe_method) + if unsafe_method.respond_to?(unsafe_method) class_eval <<-EOT, __FILE__, __LINE__ + 1 def #{unsafe_method}(*args, &block) # def capitalize(*args, &block) to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block) @@ -184,10 +239,22 @@ module ActiveSupport #:nodoc: EOT end end + + private + + def html_escape_interpolated_argument(arg) + (!html_safe? || arg.html_safe?) ? arg : + arg.to_s.gsub(ERB::Util::HTML_ESCAPE_REGEXP, ERB::Util::HTML_ESCAPE) + end end end class String + # Marks a string as trusted safe. It will be inserted into HTML with no + # additional escaping performed. It is your responsibilty to ensure that the + # string contains no malicious content. This method is equivalent to the + # `raw` helper in views. It is recommended that you use `sanitize` instead of + # this method. It should never be called on user input. def html_safe ActiveSupport::SafeBuffer.new(self) end diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb index c2c30044f2..1fde3db070 100644 --- a/activesupport/lib/active_support/core_ext/struct.rb +++ b/activesupport/lib/active_support/core_ext/struct.rb @@ -1,6 +1,3 @@ -# Backport of Struct#to_h from Ruby 2.0 -class Struct # :nodoc: - def to_h - Hash[members.zip(values)] - end -end unless Struct.instance_methods.include?(:to_h) +require 'active_support/deprecation' + +ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb deleted file mode 100644 index e80f442973..0000000000 --- a/activesupport/lib/active_support/core_ext/thread.rb +++ /dev/null @@ -1,79 +0,0 @@ -class Thread - LOCK = Mutex.new # :nodoc: - - # Returns the value of a thread local variable that has been set. Note that - # these are different than fiber local values. - # - # Thread local values are carried along with threads, and do not respect - # fibers. For example: - # - # Thread.new { - # Thread.current.thread_variable_set("foo", "bar") # set a thread local - # Thread.current["foo"] = "bar" # set a fiber local - # - # Fiber.new { - # Fiber.yield [ - # Thread.current.thread_variable_get("foo"), # get the thread local - # Thread.current["foo"], # get the fiber local - # ] - # }.resume - # }.join.value # => ['bar', nil] - # - # The value <tt>"bar"</tt> is returned for the thread local, where +nil+ is returned - # for the fiber local. The fiber is executed in the same thread, so the - # thread local values are available. - def thread_variable_get(key) - _locals[key.to_sym] - end - - # Sets a thread local with +key+ to +value+. Note that these are local to - # threads, and not to fibers. Please see Thread#thread_variable_get for - # more information. - def thread_variable_set(key, value) - _locals[key.to_sym] = value - end - - # Returns an an array of the names of the thread-local variables (as Symbols). - # - # thr = Thread.new do - # Thread.current.thread_variable_set(:cat, 'meow') - # Thread.current.thread_variable_set("dog", 'woof') - # end - # thr.join #=> #<Thread:0x401b3f10 dead> - # thr.thread_variables #=> [:dog, :cat] - # - # Note that these are not fiber local variables. Please see Thread#thread_variable_get - # for more details. - def thread_variables - _locals.keys - end - - # Returns <tt>true</tt> if the given string (or symbol) exists as a - # thread-local variable. - # - # me = Thread.current - # me.thread_variable_set(:oliver, "a") - # me.thread_variable?(:oliver) #=> true - # me.thread_variable?(:stanley) #=> false - # - # Note that these are not fiber local variables. Please see Thread#thread_variable_get - # for more details. - def thread_variable?(key) - _locals.has_key?(key.to_sym) - end - - def freeze - _locals.freeze - super - end - - private - - def _locals - if defined?(@_locals) - @_locals - else - LOCK.synchronize { @_locals ||= {} } - end - end -end unless Thread.instance_methods.include?(:thread_variable_set) diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb index 32cffe237d..72c3234630 100644 --- a/activesupport/lib/active_support/core_ext/time.rb +++ b/activesupport/lib/active_support/core_ext/time.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/time/acts_like' require 'active_support/core_ext/time/calculations' require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/time/marshal' require 'active_support/core_ext/time/zones' diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 6e0af0db4d..13610ba19f 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -64,11 +64,12 @@ class Time # Returns a new Time where one or more of the elements have been changed according # to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>, - # <tt>:sec</tt>, <tt>:usec</tt>) reset cascadingly, so if only the hour is passed, - # then minute, sec, and usec is set to 0. If the hour and minute is passed, then - # sec and usec is set to 0. The +options+ parameter takes a hash with any of these - # keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, - # <tt>:sec</tt>, <tt>:usec</tt>. + # <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only + # the hour is passed, then minute, sec, usec and nsec is set to 0. If the hour + # and minute is passed, then sec, usec and nsec is set to 0. The +options+ + # parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>, + # <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt> + # <tt>:nsec</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both. # # Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0) # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0) @@ -80,21 +81,29 @@ class Time new_hour = options.fetch(:hour, hour) new_min = options.fetch(:min, options[:hour] ? 0 : min) new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) - new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + + if new_nsec = options[:nsec] + raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec] + new_usec = Rational(new_nsec, 1000) + else + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + end if utc? ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) elsif zone ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) else + raise ArgumentError, 'argument out of range' if new_usec > 999999 ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset) end end - # Uses Date to provide precise Time calculations for years, months, and days. - # The +options+ parameter takes a hash with any of these keys: <tt>:years</tt>, - # <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, - # <tt>:minutes</tt>, <tt>:seconds</tt>. + # Uses Date to provide precise Time calculations for years, months, and days + # according to the proleptic Gregorian calendar. The +options+ parameter + # takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>, + # <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>, + # <tt>:seconds</tt>. def advance(options) unless options[:weeks].nil? options[:weeks], partial_weeks = options[:weeks].divmod(1) @@ -107,6 +116,7 @@ class Time end d = to_date.advance(options) + d = d.gregorian if d.julian? time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) seconds_to_advance = \ options.fetch(:seconds, 0) + @@ -152,7 +162,7 @@ class Time alias :at_noon :middle_of_day alias :at_middle_of_day :middle_of_day - # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9) + # Returns a new Time representing the end of the day, 23:59:59.999999 def end_of_day change( :hour => 23, @@ -169,7 +179,7 @@ class Time end alias :at_beginning_of_hour :beginning_of_hour - # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9) + # Returns a new Time representing the end of the hour, x:59:59.999999 def end_of_hour change( :min => 59, @@ -185,7 +195,7 @@ class Time end alias :at_beginning_of_minute :beginning_of_minute - # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9) + # Returns a new Time representing the end of the minute, x:xx:59.999999 def end_of_minute change( :sec => 59, @@ -199,27 +209,6 @@ class Time beginning_of_day..end_of_day end - # Returns a Range representing the whole week of the current time. - # Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set. - def all_week(start_day = Date.beginning_of_week) - beginning_of_week(start_day)..end_of_week(start_day) - 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/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index 9fd26156c7..dbf1f2f373 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -20,7 +20,7 @@ class Time :iso8601 => lambda { |time| time.iso8601 } } - # Converts to a formatted string. See DATE_FORMATS for builtin formats. + # Converts to a formatted string. See DATE_FORMATS for built-in formats. # # This method is aliased to <tt>to_s</tt>. # diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb index 497c4c3fb8..467bad1726 100644 --- a/activesupport/lib/active_support/core_ext/time/marshal.rb +++ b/activesupport/lib/active_support/core_ext/time/marshal.rb @@ -1,30 +1,3 @@ -# Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only -# preserves utc_offset. Preserve zone also, even though it may not -# work in some edge cases. -if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone - class Time - class << self - alias_method :_load_without_zone, :_load - def _load(marshaled_time) - time = _load_without_zone(marshaled_time) - time.instance_eval do - if zone = defined?(@_zone) && remove_instance_variable('@_zone') - ary = to_a - ary[0] += subsec if ary[0] == sec - ary[-1] = zone - utc? ? Time.utc(*ary) : Time.local(*ary) - else - self - end - end - end - end +require 'active_support/deprecation' - alias_method :_dump_without_zone, :_dump - def _dump(*args) - obj = dup - obj.instance_variable_set('@_zone', zone) - obj.send :_dump_without_zone, *args - end - end -end +ActiveSupport::Deprecation.warn("This is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index bbda04d60c..0668eadb1e 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -1,4 +1,5 @@ require 'active_support/time_with_zone' +require 'active_support/core_ext/time/acts_like' require 'active_support/core_ext/date_and_time/zones' class Time @@ -50,7 +51,16 @@ class Time end end - # Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones. + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by <tt>Time.zone=</tt>. + # Raises an ArgumentError for invalid time zones. + # + # Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...> + # Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...> + # Time.find_zone! -5.hours # => #<ActiveSupport::TimeZone @name="Bogota" ...> + # Time.find_zone! nil # => nil + # Time.find_zone! false # => false + # Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE def find_zone!(time_zone) if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone) time_zone @@ -71,6 +81,12 @@ class Time raise ArgumentError, "Invalid Timezone: #{time_zone}" end + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by <tt>Time.zone=</tt>. + # Returns +nil+ for invalid time zones. + # + # Time.find_zone "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...> + # Time.find_zone "NOT-A-TIMEZONE" # => nil def find_zone(time_zone) find_zone!(time_zone) rescue nil end |