aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/core_ext
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/core_ext')
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/array/prepend_and_append.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb174
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb21
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb91
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/digest/uuid.rb51
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/hash.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/compact.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_merge.rb33
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb106
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/hash/transform_values.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/integer/multiple.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/kernel.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/concern.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/debugger.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb69
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_internal.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb174
-rw-r--r--activesupport/lib/active_support/core_ext/module/concerning.rb135
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb58
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/method_transplanting.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/name_error.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/bytes.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb60
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object/instance_variables.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb197
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_json.rb27
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.rb59
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_query.rb73
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb56
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb33
-rw-r--r--activesupport/lib/active_support/core_ext/range.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/range/each.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb70
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/string/exclude.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb51
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb25
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb133
-rw-r--r--activesupport/lib/active_support/core_ext/struct.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/thread.rb79
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb57
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/marshal.rb31
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb18
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 = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
- 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 &gt; 0 &amp; a &lt; 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 &amp; 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>&quot;</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