Use ActiveSupport’s #days.
This commit is contained in:
parent
728189d3e9
commit
a9128c543c
@ -4,7 +4,7 @@ require "cask/cask_loader"
|
||||
require "set"
|
||||
|
||||
module CleanupRefinement
|
||||
LATEST_CASK_DAYS = 7
|
||||
LATEST_CASK_OUTDATED = 7.days.ago
|
||||
|
||||
refine Enumerator do
|
||||
def parallel
|
||||
@ -51,8 +51,7 @@ module CleanupRefinement
|
||||
|
||||
return true if symlink? && !exist?
|
||||
|
||||
# TODO: Replace with ActiveSupport's `.days.ago`.
|
||||
mtime < ((@time ||= Time.now) - days * 60 * 60 * 24)
|
||||
mtime < days.days.ago
|
||||
end
|
||||
|
||||
def stale?(scrub = false)
|
||||
@ -124,10 +123,7 @@ module CleanupRefinement
|
||||
|
||||
return true if scrub && !cask.versions.include?(cask.version)
|
||||
|
||||
if cask.version.latest?
|
||||
# TODO: Replace with ActiveSupport's `.days.ago`.
|
||||
return mtime < ((@time ||= Time.now) - LATEST_CASK_DAYS * 60 * 60 * 24)
|
||||
end
|
||||
return mtime < LATEST_CASK_OUTDATED if cask.version.latest?
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
@ -6,9 +6,6 @@ using CleanupRefinement
|
||||
module Cask
|
||||
class Cmd
|
||||
class Cleanup < AbstractCommand
|
||||
OUTDATED_DAYS = 10
|
||||
OUTDATED_TIMESTAMP = Time.now - (60 * 60 * 24 * OUTDATED_DAYS)
|
||||
|
||||
def self.help
|
||||
"cleans up cached downloads and tracker symlinks"
|
||||
end
|
||||
|
||||
@ -7,6 +7,7 @@ require "pp"
|
||||
|
||||
require_relative "load_path"
|
||||
|
||||
require "active_support/core_ext/numeric/time"
|
||||
require "config"
|
||||
require "os"
|
||||
require "extend/ARGV"
|
||||
|
||||
@ -16,7 +16,7 @@ describe CleanupRefinement do
|
||||
end
|
||||
|
||||
it "returns true when path_modified_time < days_default" do
|
||||
allow_any_instance_of(Pathname).to receive(:mtime).and_return(Time.now - 2 * 60 * 60 * 24)
|
||||
allow_any_instance_of(Pathname).to receive(:mtime).and_return(2.days.ago)
|
||||
expect(path.prune?(1)).to be true
|
||||
end
|
||||
|
||||
@ -181,7 +181,7 @@ describe Homebrew::Cleanup do
|
||||
it "removes the download for the latest version after a week" do
|
||||
download = Cask::Cache.path/"#{cask.token}--#{cask.version}"
|
||||
|
||||
FileUtils.touch download, mtime: Time.now - 7 * 60 * 60 * 24
|
||||
FileUtils.touch download, mtime: 7.days.ago - 1.hour
|
||||
|
||||
subject.cleanup_cask(cask)
|
||||
|
||||
@ -203,13 +203,13 @@ describe Homebrew::Cleanup do
|
||||
end
|
||||
|
||||
it "cleans up logs if older than 14 days" do
|
||||
allow_any_instance_of(Pathname).to receive(:mtime).and_return(Time.now - 15 * 60 * 60 * 24)
|
||||
allow_any_instance_of(Pathname).to receive(:mtime).and_return(15.days.ago)
|
||||
subject.cleanup_logs
|
||||
expect(path).not_to exist
|
||||
end
|
||||
|
||||
it "does not clean up logs less than 14 days old" do
|
||||
allow_any_instance_of(Pathname).to receive(:mtime).and_return(Time.now - 2 * 60 * 60 * 24)
|
||||
allow_any_instance_of(Pathname).to receive(:mtime).and_return(2.days.ago)
|
||||
subject.cleanup_logs
|
||||
expect(path).to exist
|
||||
end
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/array/wrap"
|
||||
require "active_support/core_ext/array/access"
|
||||
require "active_support/core_ext/array/conversions"
|
||||
require "active_support/core_ext/array/extract_options"
|
||||
require "active_support/core_ext/array/grouping"
|
||||
require "active_support/core_ext/array/prepend_and_append"
|
||||
require "active_support/core_ext/array/inquiry"
|
||||
@ -0,0 +1,92 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Array
|
||||
# Returns the tail of the array from +position+.
|
||||
#
|
||||
# %w( a b c d ).from(0) # => ["a", "b", "c", "d"]
|
||||
# %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
|
||||
|
||||
# Returns the beginning of the array up to +position+.
|
||||
#
|
||||
# %w( a b c d ).to(0) # => ["a"]
|
||||
# %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)
|
||||
if position >= 0
|
||||
take position + 1
|
||||
else
|
||||
self[0..position]
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a copy of the Array without the specified elements.
|
||||
#
|
||||
# people = ["David", "Rafael", "Aaron", "Todd"]
|
||||
# people.without "Aaron", "Todd"
|
||||
# # => ["David", "Rafael"]
|
||||
#
|
||||
# Note: This is an optimization of <tt>Enumerable#without</tt> that uses <tt>Array#-</tt>
|
||||
# instead of <tt>Array#reject</tt> for performance reasons.
|
||||
def without(*elements)
|
||||
self - elements
|
||||
end
|
||||
|
||||
# Equal to <tt>self[1]</tt>.
|
||||
#
|
||||
# %w( a b c d e ).second # => "b"
|
||||
def second
|
||||
self[1]
|
||||
end
|
||||
|
||||
# Equal to <tt>self[2]</tt>.
|
||||
#
|
||||
# %w( a b c d e ).third # => "c"
|
||||
def third
|
||||
self[2]
|
||||
end
|
||||
|
||||
# Equal to <tt>self[3]</tt>.
|
||||
#
|
||||
# %w( a b c d e ).fourth # => "d"
|
||||
def fourth
|
||||
self[3]
|
||||
end
|
||||
|
||||
# Equal to <tt>self[4]</tt>.
|
||||
#
|
||||
# %w( a b c d e ).fifth # => "e"
|
||||
def fifth
|
||||
self[4]
|
||||
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
|
||||
|
||||
# Equal to <tt>self[-3]</tt>.
|
||||
#
|
||||
# %w( a b c d e ).third_to_last # => "c"
|
||||
def third_to_last
|
||||
self[-3]
|
||||
end
|
||||
|
||||
# Equal to <tt>self[-2]</tt>.
|
||||
#
|
||||
# %w( a b c d e ).second_to_last # => "d"
|
||||
def second_to_last
|
||||
self[-2]
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,213 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/xml_mini"
|
||||
require "active_support/core_ext/hash/keys"
|
||||
require "active_support/core_ext/string/inflections"
|
||||
require "active_support/core_ext/object/to_param"
|
||||
require "active_support/core_ext/object/to_query"
|
||||
|
||||
class Array
|
||||
# Converts the array to a comma-separated sentence where the last element is
|
||||
# joined by the connector word.
|
||||
#
|
||||
# You can pass the following options to change the default behavior. If you
|
||||
# pass an option key that doesn't exist in the list below, it will raise an
|
||||
# <tt>ArgumentError</tt>.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:words_connector</tt> - The sign or word used to join the elements
|
||||
# in arrays with two or more elements (default: ", ").
|
||||
# * <tt>:two_words_connector</tt> - The sign or word used to join the elements
|
||||
# in arrays with two elements (default: " and ").
|
||||
# * <tt>:last_word_connector</tt> - The sign or word used to join the last element
|
||||
# in arrays with three or more elements (default: ", and ").
|
||||
# * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use
|
||||
# the connector options defined on the 'support.array' namespace in the
|
||||
# corresponding dictionary file.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# [].to_sentence # => ""
|
||||
# ['one'].to_sentence # => "one"
|
||||
# ['one', 'two'].to_sentence # => "one and two"
|
||||
# ['one', 'two', 'three'].to_sentence # => "one, two, and three"
|
||||
#
|
||||
# ['one', 'two'].to_sentence(passing: 'invalid option')
|
||||
# # => ArgumentError: Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale
|
||||
#
|
||||
# ['one', 'two'].to_sentence(two_words_connector: '-')
|
||||
# # => "one-two"
|
||||
#
|
||||
# ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ')
|
||||
# # => "one or two or at least three"
|
||||
#
|
||||
# Using <tt>:locale</tt> option:
|
||||
#
|
||||
# # Given this locale dictionary:
|
||||
# #
|
||||
# # es:
|
||||
# # support:
|
||||
# # array:
|
||||
# # words_connector: " o "
|
||||
# # two_words_connector: " y "
|
||||
# # last_word_connector: " o al menos "
|
||||
#
|
||||
# ['uno', 'dos'].to_sentence(locale: :es)
|
||||
# # => "uno y dos"
|
||||
#
|
||||
# ['uno', 'dos', 'tres'].to_sentence(locale: :es)
|
||||
# # => "uno o dos o al menos tres"
|
||||
def to_sentence(options = {})
|
||||
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
|
||||
|
||||
default_connectors = {
|
||||
words_connector: ", ",
|
||||
two_words_connector: " and ",
|
||||
last_word_connector: ", and "
|
||||
}
|
||||
if defined?(I18n)
|
||||
i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
|
||||
default_connectors.merge!(i18n_connectors)
|
||||
end
|
||||
options = default_connectors.merge!(options)
|
||||
|
||||
case length
|
||||
when 0
|
||||
""
|
||||
when 1
|
||||
"#{self[0]}"
|
||||
when 2
|
||||
"#{self[0]}#{options[:two_words_connector]}#{self[1]}"
|
||||
else
|
||||
"#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
|
||||
end
|
||||
end
|
||||
|
||||
# Extends <tt>Array#to_s</tt> to convert a collection of elements into a
|
||||
# comma separated id list if <tt>:db</tt> argument is given as the format.
|
||||
#
|
||||
# Blog.all.to_formatted_s(:db) # => "1,2,3"
|
||||
# Blog.none.to_formatted_s(:db) # => "null"
|
||||
# [1,2].to_formatted_s # => "[1, 2]"
|
||||
def to_formatted_s(format = :default)
|
||||
case format
|
||||
when :db
|
||||
if empty?
|
||||
"null"
|
||||
else
|
||||
collect(&:id).join(",")
|
||||
end
|
||||
else
|
||||
to_default_s
|
||||
end
|
||||
end
|
||||
alias_method :to_default_s, :to_s
|
||||
alias_method :to_s, :to_formatted_s
|
||||
|
||||
# Returns a string that represents the array in XML by invoking +to_xml+
|
||||
# on each element. Active Record collections delegate their representation
|
||||
# in XML to this method.
|
||||
#
|
||||
# All elements are expected to respond to +to_xml+, if any of them does
|
||||
# not then an exception is raised.
|
||||
#
|
||||
# The root node reflects the class name of the first element in plural
|
||||
# if all elements belong to the same type and that's not Hash:
|
||||
#
|
||||
# customer.projects.to_xml
|
||||
#
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <projects type="array">
|
||||
# <project>
|
||||
# <amount type="decimal">20000.0</amount>
|
||||
# <customer-id type="integer">1567</customer-id>
|
||||
# <deal-date type="date">2008-04-09</deal-date>
|
||||
# ...
|
||||
# </project>
|
||||
# <project>
|
||||
# <amount type="decimal">57230.0</amount>
|
||||
# <customer-id type="integer">1567</customer-id>
|
||||
# <deal-date type="date">2008-04-15</deal-date>
|
||||
# ...
|
||||
# </project>
|
||||
# </projects>
|
||||
#
|
||||
# Otherwise the root element is "objects":
|
||||
#
|
||||
# [{ foo: 1, bar: 2}, { baz: 3}].to_xml
|
||||
#
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <objects type="array">
|
||||
# <object>
|
||||
# <bar type="integer">2</bar>
|
||||
# <foo type="integer">1</foo>
|
||||
# </object>
|
||||
# <object>
|
||||
# <baz type="integer">3</baz>
|
||||
# </object>
|
||||
# </objects>
|
||||
#
|
||||
# If the collection is empty the root element is "nil-classes" by default:
|
||||
#
|
||||
# [].to_xml
|
||||
#
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <nil-classes type="array"/>
|
||||
#
|
||||
# To ensure a meaningful root element use the <tt>:root</tt> option:
|
||||
#
|
||||
# customer_with_no_projects.projects.to_xml(root: 'projects')
|
||||
#
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <projects type="array"/>
|
||||
#
|
||||
# By default name of the node for the children of root is <tt>root.singularize</tt>.
|
||||
# You can change it with the <tt>:children</tt> option.
|
||||
#
|
||||
# The +options+ hash is passed downwards:
|
||||
#
|
||||
# Message.all.to_xml(skip_types: true)
|
||||
#
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <messages>
|
||||
# <message>
|
||||
# <created-at>2008-03-07T09:58:18+01:00</created-at>
|
||||
# <id>1</id>
|
||||
# <name>1</name>
|
||||
# <updated-at>2008-03-07T09:58:18+01:00</updated-at>
|
||||
# <user-id>1</user-id>
|
||||
# </message>
|
||||
# </messages>
|
||||
#
|
||||
def to_xml(options = {})
|
||||
require "active_support/builder" unless defined?(Builder)
|
||||
|
||||
options = options.dup
|
||||
options[:indent] ||= 2
|
||||
options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
|
||||
options[:root] ||= \
|
||||
if first.class != Hash && all? { |e| e.is_a?(first.class) }
|
||||
underscored = ActiveSupport::Inflector.underscore(first.class.name)
|
||||
ActiveSupport::Inflector.pluralize(underscored).tr("/", "_")
|
||||
else
|
||||
"objects"
|
||||
end
|
||||
|
||||
builder = options[:builder]
|
||||
builder.instruct! unless options.delete(:skip_instruct)
|
||||
|
||||
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
|
||||
children = options.delete(:children) || root.singularize
|
||||
attributes = options[:skip_types] ? {} : { type: "array" }
|
||||
|
||||
if empty?
|
||||
builder.tag!(root, attributes)
|
||||
else
|
||||
builder.tag!(root, attributes) do
|
||||
each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) }
|
||||
yield builder if block_given?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Hash
|
||||
# By default, only instances of Hash itself are extractable.
|
||||
# Subclasses of Hash may implement this method and return
|
||||
# true to declare themselves as extractable. If a Hash
|
||||
# is extractable, Array#extract_options! pops it from
|
||||
# the Array when it is the last element of the Array.
|
||||
def extractable_options?
|
||||
instance_of?(Hash)
|
||||
end
|
||||
end
|
||||
|
||||
class Array
|
||||
# Extracts options from a set of arguments. Removes and returns the last
|
||||
# element in the array if it's a hash, otherwise returns a blank hash.
|
||||
#
|
||||
# def options(*args)
|
||||
# args.extract_options!
|
||||
# end
|
||||
#
|
||||
# options(1, 2) # => {}
|
||||
# options(1, 2, a: :b) # => {:a=>:b}
|
||||
def extract_options!
|
||||
if last.is_a?(Hash) && last.extractable_options?
|
||||
pop
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,109 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Array
|
||||
# Splits or iterates over the array in groups of size +number+,
|
||||
# padding any remaining slots with +fill_with+ unless it is +false+.
|
||||
#
|
||||
# %w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group}
|
||||
# ["1", "2", "3"]
|
||||
# ["4", "5", "6"]
|
||||
# ["7", "8", "9"]
|
||||
# ["10", nil, nil]
|
||||
#
|
||||
# %w(1 2 3 4 5).in_groups_of(2, ' ') {|group| p group}
|
||||
# ["1", "2"]
|
||||
# ["3", "4"]
|
||||
# ["5", " "]
|
||||
#
|
||||
# %w(1 2 3 4 5).in_groups_of(2, false) {|group| p group}
|
||||
# ["1", "2"]
|
||||
# ["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
|
||||
# size % number gives how many extra we have;
|
||||
# subtracting from number gives how many to add;
|
||||
# modulo number ensures we don't add group of just fill.
|
||||
padding = (number - size % number) % number
|
||||
collection = dup.concat(Array.new(padding, fill_with))
|
||||
end
|
||||
|
||||
if block_given?
|
||||
collection.each_slice(number) { |slice| yield(slice) }
|
||||
else
|
||||
collection.each_slice(number).to_a
|
||||
end
|
||||
end
|
||||
|
||||
# Splits or iterates over the array in +number+ of groups, padding any
|
||||
# remaining slots with +fill_with+ unless it is +false+.
|
||||
#
|
||||
# %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group}
|
||||
# ["1", "2", "3", "4"]
|
||||
# ["5", "6", "7", nil]
|
||||
# ["8", "9", "10", nil]
|
||||
#
|
||||
# %w(1 2 3 4 5 6 7 8 9 10).in_groups(3, ' ') {|group| p group}
|
||||
# ["1", "2", "3", "4"]
|
||||
# ["5", "6", "7", " "]
|
||||
# ["8", "9", "10", " "]
|
||||
#
|
||||
# %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
|
||||
# ["1", "2", "3"]
|
||||
# ["4", "5"]
|
||||
# ["6", "7"]
|
||||
def in_groups(number, fill_with = nil)
|
||||
# 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
|
||||
modulo = size % number
|
||||
|
||||
# create a new array avoiding dup
|
||||
groups = []
|
||||
start = 0
|
||||
|
||||
number.times do |index|
|
||||
length = division + (modulo > 0 && modulo > index ? 1 : 0)
|
||||
groups << last_group = slice(start, length)
|
||||
last_group << fill_with if fill_with != false &&
|
||||
modulo > 0 && length == division
|
||||
start += length
|
||||
end
|
||||
|
||||
if block_given?
|
||||
groups.each { |g| yield(g) }
|
||||
else
|
||||
groups
|
||||
end
|
||||
end
|
||||
|
||||
# Divides the array into one or more subarrays based on a delimiting +value+
|
||||
# or the result of an optional block.
|
||||
#
|
||||
# [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)
|
||||
arr = dup
|
||||
result = []
|
||||
if block_given?
|
||||
while (idx = arr.index { |i| yield i })
|
||||
result << arr.shift(idx)
|
||||
arr.shift
|
||||
end
|
||||
else
|
||||
while (idx = arr.index(value))
|
||||
result << arr.shift(idx)
|
||||
arr.shift
|
||||
end
|
||||
end
|
||||
result << arr
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/array_inquirer"
|
||||
|
||||
class Array
|
||||
# Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way
|
||||
# to check its string-like contents.
|
||||
#
|
||||
# pets = [:cat, :dog].inquiry
|
||||
#
|
||||
# pets.cat? # => true
|
||||
# pets.ferret? # => false
|
||||
#
|
||||
# pets.any?(:cat, :ferret) # => true
|
||||
# pets.any?(:ferret, :alligator) # => false
|
||||
def inquiry
|
||||
ActiveSupport::ArrayInquirer.new(self)
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Array
|
||||
# The human way of thinking about adding stuff to the end of a list is with append.
|
||||
alias_method :append, :push unless [].respond_to?(:append)
|
||||
|
||||
# The human way of thinking about adding stuff to the beginning of a list is with prepend.
|
||||
alias_method :prepend, :unshift unless [].respond_to?(:prepend)
|
||||
end
|
||||
@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Array
|
||||
# Wraps its argument in an array unless it is already an array (or array-like).
|
||||
#
|
||||
# Specifically:
|
||||
#
|
||||
# * If the argument is +nil+ an empty array is returned.
|
||||
# * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
|
||||
# * Otherwise, returns an array with the argument as its single element.
|
||||
#
|
||||
# Array.wrap(nil) # => []
|
||||
# Array.wrap([1, 2, 3]) # => [1, 2, 3]
|
||||
# Array.wrap(0) # => [0]
|
||||
#
|
||||
# This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
|
||||
#
|
||||
# * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
|
||||
# moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
|
||||
# an array with the argument as its single element right away.
|
||||
# * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
|
||||
# raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
|
||||
# * It does not call +to_a+ on the argument, if the argument does not respond to +to_ary+
|
||||
# it returns an array with the argument as its single element.
|
||||
#
|
||||
# The last point is easily explained with some enumerables:
|
||||
#
|
||||
# Array(foo: :bar) # => [[:foo, :bar]]
|
||||
# Array.wrap(foo: :bar) # => [{:foo=>:bar}]
|
||||
#
|
||||
# There's also a related idiom that uses the splat operator:
|
||||
#
|
||||
# [*object]
|
||||
#
|
||||
# which returns <tt>[]</tt> for +nil+, but calls to <tt>Array(object)</tt> otherwise.
|
||||
#
|
||||
# The differences with <tt>Kernel#Array</tt> explained above
|
||||
# apply to the rest of <tt>object</tt>s.
|
||||
def self.wrap(object)
|
||||
if object.nil?
|
||||
[]
|
||||
elsif object.respond_to?(:to_ary)
|
||||
object.to_ary || [object]
|
||||
else
|
||||
[object]
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "benchmark"
|
||||
|
||||
class << Benchmark
|
||||
# Benchmark realtime in milliseconds.
|
||||
#
|
||||
# Benchmark.realtime { User.all }
|
||||
# # => 8.0e-05
|
||||
#
|
||||
# Benchmark.ms { User.all }
|
||||
# # => 0.074
|
||||
def ms
|
||||
1000 * realtime { yield }
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/big_decimal/conversions"
|
||||
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bigdecimal"
|
||||
require "bigdecimal/util"
|
||||
|
||||
module ActiveSupport
|
||||
module BigDecimalWithDefaultFormat #:nodoc:
|
||||
def to_s(format = "F")
|
||||
super(format)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
BigDecimal.prepend(ActiveSupport::BigDecimalWithDefaultFormat)
|
||||
@ -0,0 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/class/attribute"
|
||||
require "active_support/core_ext/class/subclasses"
|
||||
@ -0,0 +1,146 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/kernel/singleton_class"
|
||||
require "active_support/core_ext/module/redefine_method"
|
||||
require "active_support/core_ext/array/extract_options"
|
||||
|
||||
class Class
|
||||
# Declare a class-level attribute whose value is inheritable by subclasses.
|
||||
# Subclasses can change their own value and it will not impact parent class.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:instance_reader</tt> - Sets the instance reader method (defaults to true).
|
||||
# * <tt>:instance_writer</tt> - Sets the instance writer method (defaults to true).
|
||||
# * <tt>:instance_accessor</tt> - Sets both instance methods (defaults to true).
|
||||
# * <tt>:instance_predicate</tt> - Sets a predicate method (defaults to true).
|
||||
# * <tt>:default</tt> - Sets a default value for the attribute (defaults to nil).
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# class Base
|
||||
# class_attribute :setting
|
||||
# end
|
||||
#
|
||||
# class Subclass < Base
|
||||
# end
|
||||
#
|
||||
# Base.setting = true
|
||||
# Subclass.setting # => true
|
||||
# Subclass.setting = false
|
||||
# Subclass.setting # => false
|
||||
# Base.setting # => true
|
||||
#
|
||||
# In the above case as long as Subclass does not assign a value to setting
|
||||
# by performing <tt>Subclass.setting = _something_</tt>, <tt>Subclass.setting</tt>
|
||||
# would read value assigned to parent class. Once Subclass assigns a value then
|
||||
# the value assigned by Subclass would be returned.
|
||||
#
|
||||
# This matches normal Ruby method inheritance: think of writing an attribute
|
||||
# on a subclass as overriding the reader method. However, you need to be aware
|
||||
# when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
|
||||
# In such cases, you don't want to do changes in place. Instead use setters:
|
||||
#
|
||||
# Base.setting = []
|
||||
# Base.setting # => []
|
||||
# Subclass.setting # => []
|
||||
#
|
||||
# # Appending in child changes both parent and child because it is the same object:
|
||||
# Subclass.setting << :foo
|
||||
# Base.setting # => [:foo]
|
||||
# Subclass.setting # => [:foo]
|
||||
#
|
||||
# # Use setters to not propagate changes:
|
||||
# Base.setting = []
|
||||
# Subclass.setting += [:foo]
|
||||
# Base.setting # => []
|
||||
# Subclass.setting # => [:foo]
|
||||
#
|
||||
# For convenience, an instance predicate method is defined as well.
|
||||
# To skip it, pass <tt>instance_predicate: false</tt>.
|
||||
#
|
||||
# Subclass.setting? # => false
|
||||
#
|
||||
# Instances may overwrite the class value in the same way:
|
||||
#
|
||||
# Base.setting = true
|
||||
# object = Base.new
|
||||
# object.setting # => true
|
||||
# object.setting = false
|
||||
# object.setting # => false
|
||||
# Base.setting # => true
|
||||
#
|
||||
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
|
||||
#
|
||||
# object.setting # => NoMethodError
|
||||
# object.setting? # => NoMethodError
|
||||
#
|
||||
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
|
||||
#
|
||||
# object.setting = false # => NoMethodError
|
||||
#
|
||||
# To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
|
||||
#
|
||||
# To set a default value for the attribute, pass <tt>default:</tt>, like so:
|
||||
#
|
||||
# class_attribute :settings, default: {}
|
||||
def class_attribute(*attrs)
|
||||
options = attrs.extract_options!
|
||||
instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
|
||||
instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
|
||||
instance_predicate = options.fetch(:instance_predicate, true)
|
||||
default_value = options.fetch(:default, nil)
|
||||
|
||||
attrs.each do |name|
|
||||
singleton_class.silence_redefinition_of_method(name)
|
||||
define_singleton_method(name) { nil }
|
||||
|
||||
singleton_class.silence_redefinition_of_method("#{name}?")
|
||||
define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
|
||||
|
||||
ivar = "@#{name}"
|
||||
|
||||
singleton_class.silence_redefinition_of_method("#{name}=")
|
||||
define_singleton_method("#{name}=") do |val|
|
||||
singleton_class.class_eval do
|
||||
redefine_method(name) { val }
|
||||
end
|
||||
|
||||
if singleton_class?
|
||||
class_eval do
|
||||
redefine_method(name) do
|
||||
if instance_variable_defined? ivar
|
||||
instance_variable_get ivar
|
||||
else
|
||||
singleton_class.send name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
val
|
||||
end
|
||||
|
||||
if instance_reader
|
||||
redefine_method(name) do
|
||||
if instance_variable_defined?(ivar)
|
||||
instance_variable_get ivar
|
||||
else
|
||||
self.class.public_send name
|
||||
end
|
||||
end
|
||||
|
||||
redefine_method("#{name}?") { !!public_send(name) } if instance_predicate
|
||||
end
|
||||
|
||||
if instance_writer
|
||||
redefine_method("#{name}=") do |val|
|
||||
instance_variable_set ivar, val
|
||||
end
|
||||
end
|
||||
|
||||
unless default_value.nil?
|
||||
self.send("#{name}=", default_value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# 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"
|
||||
@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Class
|
||||
begin
|
||||
# Test if this Ruby supports each_object against singleton_class
|
||||
ObjectSpace.each_object(Numeric.singleton_class) {}
|
||||
|
||||
# Returns an array with all classes that are < than its receiver.
|
||||
#
|
||||
# class C; end
|
||||
# C.descendants # => []
|
||||
#
|
||||
# class B < C; end
|
||||
# C.descendants # => [B]
|
||||
#
|
||||
# class A < B; end
|
||||
# C.descendants # => [B, A]
|
||||
#
|
||||
# class D < C; end
|
||||
# C.descendants # => [B, A, D]
|
||||
def descendants
|
||||
descendants = []
|
||||
ObjectSpace.each_object(singleton_class) do |k|
|
||||
next if k.singleton_class?
|
||||
descendants.unshift k unless k == self
|
||||
end
|
||||
descendants
|
||||
end
|
||||
rescue StandardError # JRuby 9.0.4.0 and earlier
|
||||
def descendants
|
||||
descendants = []
|
||||
ObjectSpace.each_object(Class) do |k|
|
||||
descendants.unshift k if k < self
|
||||
end
|
||||
descendants.uniq!
|
||||
descendants
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an array with the direct children of +self+.
|
||||
#
|
||||
# class Foo; end
|
||||
# class Bar < Foo; end
|
||||
# class Baz < Bar; end
|
||||
#
|
||||
# Foo.subclasses # => [Bar]
|
||||
def subclasses
|
||||
subclasses, chain = [], descendants
|
||||
chain.each do |k|
|
||||
subclasses << k unless chain.any? { |c| c > k }
|
||||
end
|
||||
subclasses
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/date/acts_like"
|
||||
require "active_support/core_ext/date/blank"
|
||||
require "active_support/core_ext/date/calculations"
|
||||
require "active_support/core_ext/date/conversions"
|
||||
require "active_support/core_ext/date/zones"
|
||||
@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/object/acts_like"
|
||||
|
||||
class Date
|
||||
# Duck-types as a Date-like class. See Object#acts_like?.
|
||||
def acts_like_date?
|
||||
true
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "date"
|
||||
|
||||
class Date #:nodoc:
|
||||
# No Date is blank:
|
||||
#
|
||||
# Date.today.blank? # => false
|
||||
#
|
||||
# @return [false]
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,145 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "date"
|
||||
require "active_support/duration"
|
||||
require "active_support/core_ext/object/acts_like"
|
||||
require "active_support/core_ext/date/zones"
|
||||
require "active_support/core_ext/time/zones"
|
||||
require "active_support/core_ext/date_and_time/calculations"
|
||||
|
||||
class Date
|
||||
include DateAndTime::Calculations
|
||||
|
||||
class << self
|
||||
attr_accessor :beginning_of_week_default
|
||||
|
||||
# Returns the week start (e.g. :monday) for the current request, if this has been set (via Date.beginning_of_week=).
|
||||
# If <tt>Date.beginning_of_week</tt> has not been set for the current request, returns the week start specified in <tt>config.beginning_of_week</tt>.
|
||||
# If no config.beginning_of_week was specified, returns :monday.
|
||||
def beginning_of_week
|
||||
Thread.current[:beginning_of_week] || beginning_of_week_default || :monday
|
||||
end
|
||||
|
||||
# Sets <tt>Date.beginning_of_week</tt> to a week start (e.g. :monday) for current request/thread.
|
||||
#
|
||||
# This method accepts any of the following day symbols:
|
||||
# :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday
|
||||
def beginning_of_week=(week_start)
|
||||
Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start)
|
||||
end
|
||||
|
||||
# Returns week start day symbol (e.g. :monday), or raises an +ArgumentError+ for invalid day symbol.
|
||||
def find_beginning_of_week!(week_start)
|
||||
raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start)
|
||||
week_start
|
||||
end
|
||||
|
||||
# Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
|
||||
def yesterday
|
||||
::Date.current.yesterday
|
||||
end
|
||||
|
||||
# Returns a new Date representing the date 1 day after today (i.e. tomorrow's date).
|
||||
def tomorrow
|
||||
::Date.current.tomorrow
|
||||
end
|
||||
|
||||
# Returns Time.zone.today when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns Date.today.
|
||||
def current
|
||||
::Time.zone ? ::Time.zone.today : ::Date.today
|
||||
end
|
||||
end
|
||||
|
||||
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
|
||||
# and then subtracts the specified number of seconds.
|
||||
def ago(seconds)
|
||||
in_time_zone.since(-seconds)
|
||||
end
|
||||
|
||||
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
|
||||
# and then adds the specified number of seconds
|
||||
def since(seconds)
|
||||
in_time_zone.since(seconds)
|
||||
end
|
||||
alias :in :since
|
||||
|
||||
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
|
||||
def beginning_of_day
|
||||
in_time_zone
|
||||
end
|
||||
alias :midnight :beginning_of_day
|
||||
alias :at_midnight :beginning_of_day
|
||||
alias :at_beginning_of_day :beginning_of_day
|
||||
|
||||
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the middle of the day (12:00)
|
||||
def middle_of_day
|
||||
in_time_zone.middle_of_day
|
||||
end
|
||||
alias :midday :middle_of_day
|
||||
alias :noon :middle_of_day
|
||||
alias :at_midday :middle_of_day
|
||||
alias :at_noon :middle_of_day
|
||||
alias :at_middle_of_day :middle_of_day
|
||||
|
||||
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
|
||||
def end_of_day
|
||||
in_time_zone.end_of_day
|
||||
end
|
||||
alias :at_end_of_day :end_of_day
|
||||
|
||||
def plus_with_duration(other) #:nodoc:
|
||||
if ActiveSupport::Duration === other
|
||||
other.since(self)
|
||||
else
|
||||
plus_without_duration(other)
|
||||
end
|
||||
end
|
||||
alias_method :plus_without_duration, :+
|
||||
alias_method :+, :plus_with_duration
|
||||
|
||||
def minus_with_duration(other) #:nodoc:
|
||||
if ActiveSupport::Duration === other
|
||||
plus_with_duration(-other)
|
||||
else
|
||||
minus_without_duration(other)
|
||||
end
|
||||
end
|
||||
alias_method :minus_without_duration, :-
|
||||
alias_method :-, :minus_with_duration
|
||||
|
||||
# Provides precise Date 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>.
|
||||
def advance(options)
|
||||
options = options.dup
|
||||
d = self
|
||||
d = d >> options.delete(:years) * 12 if options[:years]
|
||||
d = d >> options.delete(:months) if options[:months]
|
||||
d = d + options.delete(:weeks) * 7 if options[:weeks]
|
||||
d = d + options.delete(:days) if options[:days]
|
||||
d
|
||||
end
|
||||
|
||||
# Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
|
||||
# The +options+ parameter is a hash with a combination of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>.
|
||||
#
|
||||
# Date.new(2007, 5, 12).change(day: 1) # => Date.new(2007, 5, 1)
|
||||
# Date.new(2007, 5, 12).change(year: 2005, month: 1) # => Date.new(2005, 1, 12)
|
||||
def change(options)
|
||||
::Date.new(
|
||||
options.fetch(:year, year),
|
||||
options.fetch(:month, month),
|
||||
options.fetch(:day, day)
|
||||
)
|
||||
end
|
||||
|
||||
# Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there.
|
||||
def compare_with_coercion(other)
|
||||
if other.is_a?(Time)
|
||||
to_datetime <=> other
|
||||
else
|
||||
compare_without_coercion(other)
|
||||
end
|
||||
end
|
||||
alias_method :compare_without_coercion, :<=>
|
||||
alias_method :<=>, :compare_with_coercion
|
||||
end
|
||||
@ -0,0 +1,96 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "date"
|
||||
require "active_support/inflector/methods"
|
||||
require "active_support/core_ext/date/zones"
|
||||
require "active_support/core_ext/module/redefine_method"
|
||||
|
||||
class Date
|
||||
DATE_FORMATS = {
|
||||
short: "%d %b",
|
||||
long: "%B %d, %Y",
|
||||
db: "%Y-%m-%d",
|
||||
number: "%Y%m%d",
|
||||
long_ordinal: lambda { |date|
|
||||
day_format = ActiveSupport::Inflector.ordinalize(date.day)
|
||||
date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
|
||||
},
|
||||
rfc822: "%d %b %Y",
|
||||
iso8601: lambda { |date| date.iso8601 }
|
||||
}
|
||||
|
||||
# Convert to a formatted string. See DATE_FORMATS for predefined formats.
|
||||
#
|
||||
# This method is aliased to <tt>to_s</tt>.
|
||||
#
|
||||
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
|
||||
#
|
||||
# date.to_formatted_s(:db) # => "2007-11-10"
|
||||
# date.to_s(:db) # => "2007-11-10"
|
||||
#
|
||||
# date.to_formatted_s(:short) # => "10 Nov"
|
||||
# date.to_formatted_s(:number) # => "20071110"
|
||||
# date.to_formatted_s(:long) # => "November 10, 2007"
|
||||
# date.to_formatted_s(:long_ordinal) # => "November 10th, 2007"
|
||||
# date.to_formatted_s(:rfc822) # => "10 Nov 2007"
|
||||
# date.to_formatted_s(:iso8601) # => "2007-11-10"
|
||||
#
|
||||
# == 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/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)
|
||||
if formatter = DATE_FORMATS[format]
|
||||
if formatter.respond_to?(:call)
|
||||
formatter.call(self).to_s
|
||||
else
|
||||
strftime(formatter)
|
||||
end
|
||||
else
|
||||
to_default_s
|
||||
end
|
||||
end
|
||||
alias_method :to_default_s, :to_s
|
||||
alias_method :to_s, :to_formatted_s
|
||||
|
||||
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005"
|
||||
def readable_inspect
|
||||
strftime("%a, %d %b %Y")
|
||||
end
|
||||
alias_method :default_inspect, :inspect
|
||||
alias_method :inspect, :readable_inspect
|
||||
|
||||
silence_redefinition_of_method :to_time
|
||||
|
||||
# Converts a Date instance to a Time, where the time is set to the beginning of the day.
|
||||
# The timezone can be either :local or :utc (default :local).
|
||||
#
|
||||
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
|
||||
#
|
||||
# date.to_time # => 2007-11-10 00:00:00 0800
|
||||
# date.to_time(:local) # => 2007-11-10 00:00:00 0800
|
||||
#
|
||||
# date.to_time(:utc) # => 2007-11-10 00:00:00 UTC
|
||||
#
|
||||
# NOTE: The :local timezone is Ruby's *process* timezone, i.e. ENV['TZ'].
|
||||
# If the *application's* timezone is needed, then use +in_time_zone+ instead.
|
||||
def to_time(form = :local)
|
||||
raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form)
|
||||
::Time.send(form, year, month, day)
|
||||
end
|
||||
|
||||
silence_redefinition_of_method :xmlschema
|
||||
|
||||
# Returns a string which represents the time in used time zone as DateTime
|
||||
# defined by XML Schema:
|
||||
#
|
||||
# date = Date.new(2015, 05, 23) # => Sat, 23 May 2015
|
||||
# date.xmlschema # => "2015-05-23T00:00:00+04:00"
|
||||
def xmlschema
|
||||
in_time_zone.xmlschema
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "date"
|
||||
require "active_support/core_ext/date_and_time/zones"
|
||||
|
||||
class Date
|
||||
include DateAndTime::Zones
|
||||
end
|
||||
@ -0,0 +1,374 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/object/try"
|
||||
|
||||
module DateAndTime
|
||||
module Calculations
|
||||
DAYS_INTO_WEEK = {
|
||||
monday: 0,
|
||||
tuesday: 1,
|
||||
wednesday: 2,
|
||||
thursday: 3,
|
||||
friday: 4,
|
||||
saturday: 5,
|
||||
sunday: 6
|
||||
}
|
||||
WEEKEND_DAYS = [ 6, 0 ]
|
||||
|
||||
# Returns a new date/time representing yesterday.
|
||||
def yesterday
|
||||
advance(days: -1)
|
||||
end
|
||||
|
||||
# Returns a new date/time the specified number of days ago.
|
||||
def prev_day(days = 1)
|
||||
advance(days: -days)
|
||||
end
|
||||
|
||||
# Returns a new date/time representing tomorrow.
|
||||
def tomorrow
|
||||
advance(days: 1)
|
||||
end
|
||||
|
||||
# Returns a new date/time the specified number of days in the future.
|
||||
def next_day(days = 1)
|
||||
advance(days: days)
|
||||
end
|
||||
|
||||
# Returns true if the date/time is today.
|
||||
def today?
|
||||
to_date == ::Date.current
|
||||
end
|
||||
|
||||
# Returns true if the date/time is in the past.
|
||||
def past?
|
||||
self < self.class.current
|
||||
end
|
||||
|
||||
# Returns true if the date/time is in the future.
|
||||
def future?
|
||||
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 true if the date/time does not fall on a Saturday or Sunday.
|
||||
def on_weekday?
|
||||
!WEEKEND_DAYS.include?(wday)
|
||||
end
|
||||
|
||||
# Returns a new date/time the specified number of days ago.
|
||||
def days_ago(days)
|
||||
advance(days: -days)
|
||||
end
|
||||
|
||||
# Returns a new date/time the specified number of days in the future.
|
||||
def days_since(days)
|
||||
advance(days: days)
|
||||
end
|
||||
|
||||
# Returns a new date/time the specified number of weeks ago.
|
||||
def weeks_ago(weeks)
|
||||
advance(weeks: -weeks)
|
||||
end
|
||||
|
||||
# Returns a new date/time the specified number of weeks in the future.
|
||||
def weeks_since(weeks)
|
||||
advance(weeks: weeks)
|
||||
end
|
||||
|
||||
# Returns a new date/time the specified number of months ago.
|
||||
def months_ago(months)
|
||||
advance(months: -months)
|
||||
end
|
||||
|
||||
# Returns a new date/time the specified number of months in the future.
|
||||
def months_since(months)
|
||||
advance(months: months)
|
||||
end
|
||||
|
||||
# Returns a new date/time the specified number of years ago.
|
||||
def years_ago(years)
|
||||
advance(years: -years)
|
||||
end
|
||||
|
||||
# Returns a new date/time the specified number of years in the future.
|
||||
def years_since(years)
|
||||
advance(years: years)
|
||||
end
|
||||
|
||||
# Returns a new date/time at the start of the month.
|
||||
#
|
||||
# today = Date.today # => Thu, 18 Jun 2015
|
||||
# today.beginning_of_month # => Mon, 01 Jun 2015
|
||||
#
|
||||
# +DateTime+ objects will have a time set to 0:00.
|
||||
#
|
||||
# now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000
|
||||
# now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000
|
||||
def beginning_of_month
|
||||
first_hour(change(day: 1))
|
||||
end
|
||||
alias :at_beginning_of_month :beginning_of_month
|
||||
|
||||
# Returns a new date/time at the start of the quarter.
|
||||
#
|
||||
# today = Date.today # => Fri, 10 Jul 2015
|
||||
# today.beginning_of_quarter # => Wed, 01 Jul 2015
|
||||
#
|
||||
# +DateTime+ objects will have a time set to 0:00.
|
||||
#
|
||||
# now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
|
||||
# now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000
|
||||
def beginning_of_quarter
|
||||
first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
|
||||
beginning_of_month.change(month: first_quarter_month)
|
||||
end
|
||||
alias :at_beginning_of_quarter :beginning_of_quarter
|
||||
|
||||
# Returns a new date/time at the end of the quarter.
|
||||
#
|
||||
# today = Date.today # => Fri, 10 Jul 2015
|
||||
# today.end_of_quarter # => Wed, 30 Sep 2015
|
||||
#
|
||||
# +DateTime+ objects will have a time set to 23:59:59.
|
||||
#
|
||||
# now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
|
||||
# now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000
|
||||
def end_of_quarter
|
||||
last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
|
||||
beginning_of_month.change(month: last_quarter_month).end_of_month
|
||||
end
|
||||
alias :at_end_of_quarter :end_of_quarter
|
||||
|
||||
# Returns a new date/time at the beginning of the year.
|
||||
#
|
||||
# today = Date.today # => Fri, 10 Jul 2015
|
||||
# today.beginning_of_year # => Thu, 01 Jan 2015
|
||||
#
|
||||
# +DateTime+ objects will have a time set to 0:00.
|
||||
#
|
||||
# now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
|
||||
# now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000
|
||||
def beginning_of_year
|
||||
change(month: 1).beginning_of_month
|
||||
end
|
||||
alias :at_beginning_of_year :beginning_of_year
|
||||
|
||||
# Returns a new date/time representing the given day in the next week.
|
||||
#
|
||||
# today = Date.today # => Thu, 07 May 2015
|
||||
# today.next_week # => Mon, 11 May 2015
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# today = Date.today # => Thu, 07 May 2015
|
||||
# today.next_week(:friday) # => Fri, 15 May 2015
|
||||
#
|
||||
# +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
|
||||
#
|
||||
# now = DateTime.current # => Thu, 07 May 2015 13:31:16 +0000
|
||||
# now.next_week # => Mon, 11 May 2015 00:00:00 +0000
|
||||
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
|
||||
|
||||
# Returns a new date/time the specified number of months in the future.
|
||||
def next_month(months = 1)
|
||||
advance(months: months)
|
||||
end
|
||||
|
||||
# Short-hand for months_since(3)
|
||||
def next_quarter
|
||||
months_since(3)
|
||||
end
|
||||
|
||||
# Returns a new date/time the specified number of years in the future.
|
||||
def next_year(years = 1)
|
||||
advance(years: years)
|
||||
end
|
||||
|
||||
# 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 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
|
||||
|
||||
# Returns a new date/time the specified number of months ago.
|
||||
def prev_month(months = 1)
|
||||
advance(months: -months)
|
||||
end
|
||||
|
||||
# Short-hand for months_ago(1).
|
||||
def last_month
|
||||
months_ago(1)
|
||||
end
|
||||
|
||||
# Short-hand for months_ago(3).
|
||||
def prev_quarter
|
||||
months_ago(3)
|
||||
end
|
||||
alias_method :last_quarter, :prev_quarter
|
||||
|
||||
# Returns a new date/time the specified number of years ago.
|
||||
def prev_year(years = 1)
|
||||
advance(years: -years)
|
||||
end
|
||||
|
||||
# Short-hand for years_ago(1).
|
||||
def last_year
|
||||
years_ago(1)
|
||||
end
|
||||
|
||||
# Returns the number of days to the start of the week on the given day.
|
||||
# Week is assumed to start on +start_day+, default is
|
||||
# +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
|
||||
def days_to_week_start(start_day = Date.beginning_of_week)
|
||||
start_day_number = DAYS_INTO_WEEK[start_day]
|
||||
current_day_number = wday != 0 ? wday - 1 : 6
|
||||
(current_day_number - start_day_number) % 7
|
||||
end
|
||||
|
||||
# Returns a new date/time representing the start of this week on the given day.
|
||||
# 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 beginning_of_week(start_day = Date.beginning_of_week)
|
||||
result = days_ago(days_to_week_start(start_day))
|
||||
acts_like?(:time) ? result.midnight : result
|
||||
end
|
||||
alias :at_beginning_of_week :beginning_of_week
|
||||
|
||||
# Returns Monday of this week assuming that week starts on Monday.
|
||||
# +DateTime+ objects have their time set to 0:00.
|
||||
def monday
|
||||
beginning_of_week(:monday)
|
||||
end
|
||||
|
||||
# Returns a new date/time representing the end of this week on the given day.
|
||||
# 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 23:59:59.
|
||||
def end_of_week(start_day = Date.beginning_of_week)
|
||||
last_hour(days_since(6 - days_to_week_start(start_day)))
|
||||
end
|
||||
alias :at_end_of_week :end_of_week
|
||||
|
||||
# Returns Sunday of this week assuming that week starts on Monday.
|
||||
# +DateTime+ objects have their time set to 23:59:59.
|
||||
def sunday
|
||||
end_of_week(:monday)
|
||||
end
|
||||
|
||||
# Returns a new date/time representing the end of the month.
|
||||
# DateTime objects will have a time set to 23:59:59.
|
||||
def end_of_month
|
||||
last_day = ::Time.days_in_month(month, year)
|
||||
last_hour(days_since(last_day - day))
|
||||
end
|
||||
alias :at_end_of_month :end_of_month
|
||||
|
||||
# Returns a new date/time representing the end of the year.
|
||||
# DateTime objects will have a time set to 23:59:59.
|
||||
def end_of_year
|
||||
change(month: 12).end_of_month
|
||||
end
|
||||
alias :at_end_of_year :end_of_year
|
||||
|
||||
# Returns a Range representing the whole day of the current date/time.
|
||||
def all_day
|
||||
beginning_of_day..end_of_day
|
||||
end
|
||||
|
||||
# Returns a Range representing the whole week of the current date/time.
|
||||
# Week starts on start_day, default is <tt>Date.beginning_of_week</tt> or <tt>config.beginning_of_week</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 date/time.
|
||||
def all_month
|
||||
beginning_of_month..end_of_month
|
||||
end
|
||||
|
||||
# Returns a Range representing the whole quarter of the current date/time.
|
||||
def all_quarter
|
||||
beginning_of_quarter..end_of_quarter
|
||||
end
|
||||
|
||||
# Returns a Range representing the whole year of the current date/time.
|
||||
def all_year
|
||||
beginning_of_year..end_of_year
|
||||
end
|
||||
|
||||
# Returns a new date/time representing the next occurrence of the specified day of week.
|
||||
#
|
||||
# today = Date.today # => Thu, 14 Dec 2017
|
||||
# today.next_occurring(:monday) # => Mon, 18 Dec 2017
|
||||
# today.next_occurring(:thursday) # => Thu, 21 Dec 2017
|
||||
def next_occurring(day_of_week)
|
||||
current_day_number = wday != 0 ? wday - 1 : 6
|
||||
from_now = DAYS_INTO_WEEK.fetch(day_of_week) - current_day_number
|
||||
from_now += 7 unless from_now > 0
|
||||
advance(days: from_now)
|
||||
end
|
||||
|
||||
# Returns a new date/time representing the previous occurrence of the specified day of week.
|
||||
#
|
||||
# today = Date.today # => Thu, 14 Dec 2017
|
||||
# today.prev_occurring(:monday) # => Mon, 11 Dec 2017
|
||||
# today.prev_occurring(:thursday) # => Thu, 07 Dec 2017
|
||||
def prev_occurring(day_of_week)
|
||||
current_day_number = wday != 0 ? wday - 1 : 6
|
||||
ago = current_day_number - DAYS_INTO_WEEK.fetch(day_of_week)
|
||||
ago += 7 unless ago > 0
|
||||
advance(days: -ago)
|
||||
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, nsec: try(:nsec))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/module/attribute_accessors"
|
||||
|
||||
module DateAndTime
|
||||
module Compatibility
|
||||
# If true, +to_time+ preserves the timezone offset of receiver.
|
||||
#
|
||||
# NOTE: With Ruby 2.4+ the default for +to_time+ changed from
|
||||
# converting to the local system time, to preserving the offset
|
||||
# of the receiver. For backwards compatibility we're overriding
|
||||
# this behavior, but new apps will have an initializer that sets
|
||||
# this to true, because the new behavior is preferred.
|
||||
mattr_accessor :preserve_timezone, instance_writer: false, default: false
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DateAndTime
|
||||
module Zones
|
||||
# Returns the simultaneous time in <tt>Time.zone</tt> if a zone is given or
|
||||
# if Time.zone_default is set. Otherwise, it returns the current time.
|
||||
#
|
||||
# Time.zone = 'Hawaii' # => 'Hawaii'
|
||||
# Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
|
||||
# Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00
|
||||
#
|
||||
# This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone
|
||||
# instead of the operating system's time zone.
|
||||
#
|
||||
# You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument,
|
||||
# and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
|
||||
#
|
||||
# Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
|
||||
# Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
|
||||
def in_time_zone(zone = ::Time.zone)
|
||||
time_zone = ::Time.find_zone! zone
|
||||
time = acts_like?(:time) ? self : nil
|
||||
|
||||
if time_zone
|
||||
time_with_zone(time, time_zone)
|
||||
else
|
||||
time || to_time
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def time_with_zone(time, zone)
|
||||
if time
|
||||
ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone)
|
||||
else
|
||||
ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/date_time/acts_like"
|
||||
require "active_support/core_ext/date_time/blank"
|
||||
require "active_support/core_ext/date_time/calculations"
|
||||
require "active_support/core_ext/date_time/compatibility"
|
||||
require "active_support/core_ext/date_time/conversions"
|
||||
@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "date"
|
||||
require "active_support/core_ext/object/acts_like"
|
||||
|
||||
class DateTime
|
||||
# Duck-types as a Date-like class. See Object#acts_like?.
|
||||
def acts_like_date?
|
||||
true
|
||||
end
|
||||
|
||||
# Duck-types as a Time-like class. See Object#acts_like?.
|
||||
def acts_like_time?
|
||||
true
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "date"
|
||||
|
||||
class DateTime #:nodoc:
|
||||
# No DateTime is ever blank:
|
||||
#
|
||||
# DateTime.now.blank? # => false
|
||||
#
|
||||
# @return [false]
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,211 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "date"
|
||||
|
||||
class DateTime
|
||||
class << self
|
||||
# Returns <tt>Time.zone.now.to_datetime</tt> when <tt>Time.zone</tt> or
|
||||
# <tt>config.time_zone</tt> are set, otherwise returns
|
||||
# <tt>Time.now.to_datetime</tt>.
|
||||
def current
|
||||
::Time.zone ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime
|
||||
end
|
||||
end
|
||||
|
||||
# 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
|
||||
|
||||
# Returns the number of seconds until 23:59:59.
|
||||
#
|
||||
# DateTime.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399
|
||||
# DateTime.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103
|
||||
# DateTime.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0
|
||||
def seconds_until_end_of_day
|
||||
end_of_day.to_i - to_i
|
||||
end
|
||||
|
||||
# Returns the fraction of a second as a +Rational+
|
||||
#
|
||||
# DateTime.new(2012, 8, 29, 0, 0, 0.5).subsec # => (1/2)
|
||||
def subsec
|
||||
sec_fraction
|
||||
end
|
||||
|
||||
# Returns a new DateTime 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>) reset cascadingly, so if only the hour is
|
||||
# passed, then minute and sec is set to 0. If the hour and minute is passed,
|
||||
# then sec 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>:offset</tt>, <tt>:start</tt>.
|
||||
#
|
||||
# DateTime.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => DateTime.new(2012, 8, 1, 22, 35, 0)
|
||||
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0)
|
||||
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0)
|
||||
def change(options)
|
||||
if new_nsec = options[:nsec]
|
||||
raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
|
||||
new_fraction = Rational(new_nsec, 1000000000)
|
||||
else
|
||||
new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
|
||||
new_fraction = Rational(new_usec, 1000000)
|
||||
end
|
||||
|
||||
raise ArgumentError, "argument out of range" if new_fraction >= 1
|
||||
|
||||
::DateTime.civil(
|
||||
options.fetch(:year, year),
|
||||
options.fetch(:month, month),
|
||||
options.fetch(:day, day),
|
||||
options.fetch(:hour, hour),
|
||||
options.fetch(:min, options[:hour] ? 0 : min),
|
||||
options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_fraction,
|
||||
options.fetch(:offset, offset),
|
||||
options.fetch(:start, start)
|
||||
)
|
||||
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>.
|
||||
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 = \
|
||||
options.fetch(:seconds, 0) +
|
||||
options.fetch(:minutes, 0) * 60 +
|
||||
options.fetch(:hours, 0) * 3600
|
||||
|
||||
if seconds_to_advance.zero?
|
||||
datetime_advanced_by_date
|
||||
else
|
||||
datetime_advanced_by_date.since(seconds_to_advance)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a new DateTime representing the time a number of seconds ago.
|
||||
# Do not use this method in combination with x.months, use months_ago instead!
|
||||
def ago(seconds)
|
||||
since(-seconds)
|
||||
end
|
||||
|
||||
# Returns a new DateTime representing the time a number of seconds since the
|
||||
# instance time. Do not use this method in combination with x.months, use
|
||||
# months_since instead!
|
||||
def since(seconds)
|
||||
self + Rational(seconds.round, 86400)
|
||||
end
|
||||
alias :in :since
|
||||
|
||||
# Returns a new DateTime representing the start of the day (0:00).
|
||||
def beginning_of_day
|
||||
change(hour: 0)
|
||||
end
|
||||
alias :midnight :beginning_of_day
|
||||
alias :at_midnight :beginning_of_day
|
||||
alias :at_beginning_of_day :beginning_of_day
|
||||
|
||||
# Returns a new DateTime representing the middle of the day (12:00)
|
||||
def middle_of_day
|
||||
change(hour: 12)
|
||||
end
|
||||
alias :midday :middle_of_day
|
||||
alias :noon :middle_of_day
|
||||
alias :at_midday :middle_of_day
|
||||
alias :at_noon :middle_of_day
|
||||
alias :at_middle_of_day :middle_of_day
|
||||
|
||||
# Returns a new DateTime representing the end of the day (23:59:59).
|
||||
def end_of_day
|
||||
change(hour: 23, min: 59, sec: 59, usec: Rational(999999999, 1000))
|
||||
end
|
||||
alias :at_end_of_day :end_of_day
|
||||
|
||||
# Returns a new DateTime representing the start of the hour (hh:00:00).
|
||||
def beginning_of_hour
|
||||
change(min: 0)
|
||||
end
|
||||
alias :at_beginning_of_hour :beginning_of_hour
|
||||
|
||||
# Returns a new DateTime representing the end of the hour (hh:59:59).
|
||||
def end_of_hour
|
||||
change(min: 59, sec: 59, usec: Rational(999999999, 1000))
|
||||
end
|
||||
alias :at_end_of_hour :end_of_hour
|
||||
|
||||
# Returns a new DateTime representing the start of the minute (hh:mm:00).
|
||||
def beginning_of_minute
|
||||
change(sec: 0)
|
||||
end
|
||||
alias :at_beginning_of_minute :beginning_of_minute
|
||||
|
||||
# Returns a new DateTime representing the end of the minute (hh:mm:59).
|
||||
def end_of_minute
|
||||
change(sec: 59, usec: Rational(999999999, 1000))
|
||||
end
|
||||
alias :at_end_of_minute :end_of_minute
|
||||
|
||||
# Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone.
|
||||
def localtime(utc_offset = nil)
|
||||
utc = new_offset(0)
|
||||
|
||||
Time.utc(
|
||||
utc.year, utc.month, utc.day,
|
||||
utc.hour, utc.min, utc.sec + utc.sec_fraction
|
||||
).getlocal(utc_offset)
|
||||
end
|
||||
alias_method :getlocal, :localtime
|
||||
|
||||
# Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
|
||||
#
|
||||
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
|
||||
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 UTC
|
||||
def utc
|
||||
utc = new_offset(0)
|
||||
|
||||
Time.utc(
|
||||
utc.year, utc.month, utc.day,
|
||||
utc.hour, utc.min, utc.sec + utc.sec_fraction
|
||||
)
|
||||
end
|
||||
alias_method :getgm, :utc
|
||||
alias_method :getutc, :utc
|
||||
alias_method :gmtime, :utc
|
||||
|
||||
# Returns +true+ if <tt>offset == 0</tt>.
|
||||
def utc?
|
||||
offset == 0
|
||||
end
|
||||
|
||||
# Returns the offset value in seconds.
|
||||
def utc_offset
|
||||
(offset * 86400).to_i
|
||||
end
|
||||
|
||||
# Layers additional behavior on DateTime#<=> so that Time and
|
||||
# ActiveSupport::TimeWithZone instances can be compared with a DateTime.
|
||||
def <=>(other)
|
||||
if other.respond_to? :to_datetime
|
||||
super other.to_datetime rescue nil
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/date_and_time/compatibility"
|
||||
require "active_support/core_ext/module/redefine_method"
|
||||
|
||||
class DateTime
|
||||
include DateAndTime::Compatibility
|
||||
|
||||
silence_redefinition_of_method :to_time
|
||||
|
||||
# Either return an instance of +Time+ with the same UTC offset
|
||||
# as +self+ or an instance of +Time+ representing the same time
|
||||
# in the local system timezone depending on the setting of
|
||||
# on the setting of +ActiveSupport.to_time_preserves_timezone+.
|
||||
def to_time
|
||||
preserve_timezone ? getlocal(utc_offset) : getlocal
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,107 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "date"
|
||||
require "active_support/inflector/methods"
|
||||
require "active_support/core_ext/time/conversions"
|
||||
require "active_support/core_ext/date_time/calculations"
|
||||
require "active_support/values/time_zone"
|
||||
|
||||
class DateTime
|
||||
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
|
||||
#
|
||||
# This method is aliased to <tt>to_s</tt>.
|
||||
#
|
||||
# === Examples
|
||||
# datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000
|
||||
#
|
||||
# datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00"
|
||||
# datetime.to_s(:db) # => "2007-12-04 00:00:00"
|
||||
# datetime.to_s(:number) # => "20071204000000"
|
||||
# datetime.to_formatted_s(:short) # => "04 Dec 00:00"
|
||||
# datetime.to_formatted_s(:long) # => "December 04, 2007 00:00"
|
||||
# datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00"
|
||||
# datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000"
|
||||
# datetime.to_formatted_s(:iso8601) # => "2007-12-04T00:00:00+00:00"
|
||||
#
|
||||
# == Adding your own datetime formats to to_formatted_s
|
||||
# DateTime formats are shared with Time. You can add your own to the
|
||||
# Time::DATE_FORMATS hash. Use the format name as the hash key and
|
||||
# either a strftime string or Proc instance that takes a time or
|
||||
# datetime argument as the value.
|
||||
#
|
||||
# # config/initializers/time_formats.rb
|
||||
# Time::DATE_FORMATS[:month_and_year] = '%B %Y'
|
||||
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
|
||||
def to_formatted_s(format = :default)
|
||||
if formatter = ::Time::DATE_FORMATS[format]
|
||||
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
|
||||
else
|
||||
to_default_s
|
||||
end
|
||||
end
|
||||
alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s)
|
||||
alias_method :to_s, :to_formatted_s
|
||||
|
||||
# Returns a formatted string of the offset from UTC, or an alternative
|
||||
# string if the time zone is already UTC.
|
||||
#
|
||||
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
|
||||
# datetime.formatted_offset # => "-06:00"
|
||||
# datetime.formatted_offset(false) # => "-0600"
|
||||
def formatted_offset(colon = true, alternate_utc_string = nil)
|
||||
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
|
||||
end
|
||||
|
||||
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000".
|
||||
def readable_inspect
|
||||
to_s(:rfc822)
|
||||
end
|
||||
alias_method :default_inspect, :inspect
|
||||
alias_method :inspect, :readable_inspect
|
||||
|
||||
# Returns DateTime with local offset for given year if format is local else
|
||||
# offset is zero.
|
||||
#
|
||||
# DateTime.civil_from_format :local, 2012
|
||||
# # => Sun, 01 Jan 2012 00:00:00 +0300
|
||||
# DateTime.civil_from_format :local, 2012, 12, 17
|
||||
# # => Mon, 17 Dec 2012 00:00:00 +0000
|
||||
def self.civil_from_format(utc_or_local, year, month = 1, day = 1, hour = 0, min = 0, sec = 0)
|
||||
if utc_or_local.to_sym == :local
|
||||
offset = ::Time.local(year, month, day).utc_offset.to_r / 86400
|
||||
else
|
||||
offset = 0
|
||||
end
|
||||
civil(year, month, day, hour, min, sec, offset)
|
||||
end
|
||||
|
||||
# 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 + sec_fraction
|
||||
end
|
||||
|
||||
# Converts +self+ to an integer number of seconds since the Unix epoch.
|
||||
def to_i
|
||||
seconds_since_unix_epoch.to_i
|
||||
end
|
||||
|
||||
# Returns the fraction of a second as microseconds
|
||||
def usec
|
||||
(sec_fraction * 1_000_000).to_i
|
||||
end
|
||||
|
||||
# Returns the fraction of a second as nanoseconds
|
||||
def nsec
|
||||
(sec_fraction * 1_000_000_000).to_i
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def offset_in_seconds
|
||||
(offset * 86400).to_i
|
||||
end
|
||||
|
||||
def seconds_since_unix_epoch
|
||||
(jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,53 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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: https://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)
|
||||
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)
|
||||
uuid_from_hash(Digest::SHA1, uuid_namespace, name)
|
||||
end
|
||||
|
||||
# Convenience method for SecureRandom.uuid.
|
||||
def self.uuid_v4
|
||||
SecureRandom.uuid
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,164 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Enumerable
|
||||
# Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements
|
||||
# when we omit an identity.
|
||||
#
|
||||
# We tried shimming it to attempt the fast native method, rescue TypeError,
|
||||
# and fall back to the compatible implementation, but that's much slower than
|
||||
# just calling the compat method in the first place.
|
||||
if Enumerable.instance_methods(false).include?(:sum) && !((?a..?b).sum rescue false)
|
||||
# :stopdoc:
|
||||
|
||||
# We can't use Refinements here because Refinements with Module which will be prepended
|
||||
# doesn't work well https://bugs.ruby-lang.org/issues/13446
|
||||
alias :_original_sum_with_required_identity :sum
|
||||
private :_original_sum_with_required_identity
|
||||
|
||||
# :startdoc:
|
||||
|
||||
# Calculates a sum from the elements.
|
||||
#
|
||||
# payments.sum { |p| p.price * p.tax_rate }
|
||||
# payments.sum(&:price)
|
||||
#
|
||||
# The latter is a shortcut for:
|
||||
#
|
||||
# payments.inject(0) { |sum, p| sum + p.price }
|
||||
#
|
||||
# It can also calculate the sum without the use of a block.
|
||||
#
|
||||
# [5, 15, 10].sum # => 30
|
||||
# ['foo', 'bar'].sum # => "foobar"
|
||||
# [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5]
|
||||
#
|
||||
# The default sum of an empty list is zero. You can override this default:
|
||||
#
|
||||
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
|
||||
def sum(identity = nil, &block)
|
||||
if identity
|
||||
_original_sum_with_required_identity(identity, &block)
|
||||
elsif block_given?
|
||||
map(&block).sum(identity)
|
||||
else
|
||||
inject(:+) || 0
|
||||
end
|
||||
end
|
||||
else
|
||||
def sum(identity = nil, &block)
|
||||
if block_given?
|
||||
map(&block).sum(identity)
|
||||
else
|
||||
sum = identity ? inject(identity, :+) : inject(:+)
|
||||
sum || identity || 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Convert an enumerable to a hash.
|
||||
#
|
||||
# people.index_by(&:login)
|
||||
# # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
|
||||
# people.index_by { |person| "#{person.first_name} #{person.last_name}" }
|
||||
# # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
|
||||
def index_by
|
||||
if block_given?
|
||||
result = {}
|
||||
each { |elem| result[yield(elem)] = elem }
|
||||
result
|
||||
else
|
||||
to_enum(:index_by) { size if respond_to?(:size) }
|
||||
end
|
||||
end
|
||||
|
||||
# Returns +true+ if the enumerable has more than 1 element. Functionally
|
||||
# equivalent to <tt>enum.to_a.size > 1</tt>. Can be called with a block too,
|
||||
# much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns +true+
|
||||
# if more than one person is over 26.
|
||||
def many?
|
||||
cnt = 0
|
||||
if block_given?
|
||||
any? do |element|
|
||||
cnt += 1 if yield element
|
||||
cnt > 1
|
||||
end
|
||||
else
|
||||
any? { (cnt += 1) > 1 }
|
||||
end
|
||||
end
|
||||
|
||||
# The negative of the <tt>Enumerable#include?</tt>. Returns +true+ if the
|
||||
# collection does not include the object.
|
||||
def exclude?(object)
|
||||
!include?(object)
|
||||
end
|
||||
|
||||
# Returns a copy of the enumerable without the specified elements.
|
||||
#
|
||||
# ["David", "Rafael", "Aaron", "Todd"].without "Aaron", "Todd"
|
||||
# # => ["David", "Rafael"]
|
||||
#
|
||||
# {foo: 1, bar: 2, baz: 3}.without :bar
|
||||
# # => {foo: 1, baz: 3}
|
||||
def without(*elements)
|
||||
reject { |element| elements.include?(element) }
|
||||
end
|
||||
|
||||
# Convert an enumerable to an array based on the given key.
|
||||
#
|
||||
# [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
|
||||
# # => ["David", "Rafael", "Aaron"]
|
||||
#
|
||||
# [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name)
|
||||
# # => [[1, "David"], [2, "Rafael"]]
|
||||
def pluck(*keys)
|
||||
if keys.many?
|
||||
map { |element| keys.map { |key| element[key] } }
|
||||
else
|
||||
map { |element| element[keys.first] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Range #:nodoc:
|
||||
# Optimize range sum to use arithmetic progression if a block is not given and
|
||||
# we have a range of numeric values.
|
||||
def sum(identity = nil)
|
||||
if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
|
||||
super
|
||||
else
|
||||
actual_last = exclude_end? ? (last - 1) : last
|
||||
if actual_last >= first
|
||||
sum = identity || 0
|
||||
sum + (actual_last - first + 1) * (actual_last + first) / 2
|
||||
else
|
||||
identity || 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Array#sum was added in Ruby 2.4 but it only works with Numeric elements.
|
||||
#
|
||||
# We tried shimming it to attempt the fast native method, rescue TypeError,
|
||||
# and fall back to the compatible implementation, but that's much slower than
|
||||
# just calling the compat method in the first place.
|
||||
if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)
|
||||
# Using Refinements here in order not to expose our internal method
|
||||
using Module.new {
|
||||
refine Array do
|
||||
alias :orig_sum :sum
|
||||
end
|
||||
}
|
||||
|
||||
class Array
|
||||
def sum(init = nil, &block) #:nodoc:
|
||||
if init.is_a?(Numeric) || first.is_a?(Numeric)
|
||||
init ||= 0
|
||||
orig_sum(init, &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/file/atomic"
|
||||
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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"
|
||||
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"
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Hash
|
||||
unless Hash.instance_methods(false).include?(:compact)
|
||||
# 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 # => {}
|
||||
# { c: true }.compact # => { c: true }
|
||||
def compact
|
||||
select { |_, value| !value.nil? }
|
||||
end
|
||||
end
|
||||
|
||||
unless Hash.instance_methods(false).include?(:compact!)
|
||||
# Replaces current hash with non +nil+ values.
|
||||
# Returns +nil+ if no changes were made, otherwise returns the hash.
|
||||
#
|
||||
# hash = { a: true, b: false, c: nil }
|
||||
# hash.compact! # => { a: true, b: false }
|
||||
# hash # => { a: true, b: false }
|
||||
# { c: true }.compact! # => nil
|
||||
def compact!
|
||||
reject! { |_, value| value.nil? }
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,263 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/xml_mini"
|
||||
require "active_support/time"
|
||||
require "active_support/core_ext/object/blank"
|
||||
require "active_support/core_ext/object/to_param"
|
||||
require "active_support/core_ext/object/to_query"
|
||||
require "active_support/core_ext/array/wrap"
|
||||
require "active_support/core_ext/hash/reverse_merge"
|
||||
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
|
||||
# # =>
|
||||
# # <?xml version="1.0" encoding="UTF-8"?>
|
||||
# # <hash>
|
||||
# # <foo type="integer">1</foo>
|
||||
# # <bar type="integer">2</bar>
|
||||
# # </hash>
|
||||
#
|
||||
# To do so, the method loops over the pairs and builds nodes that depend on
|
||||
# the _values_. Given a pair +key+, +value+:
|
||||
#
|
||||
# * If +value+ is a hash there's a recursive call with +key+ as <tt>:root</tt>.
|
||||
#
|
||||
# * If +value+ is an array there's a recursive call with +key+ as <tt>:root</tt>,
|
||||
# and +key+ singularized as <tt>:children</tt>.
|
||||
#
|
||||
# * If +value+ is a callable object it must expect one or two arguments. Depending
|
||||
# on the arity, the callable is invoked with the +options+ hash as first argument
|
||||
# with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
|
||||
# callable can add nodes by using <tt>options[:builder]</tt>.
|
||||
#
|
||||
# {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml
|
||||
# # => "<b>foo</b>"
|
||||
#
|
||||
# * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
|
||||
#
|
||||
# class Foo
|
||||
# def to_xml(options)
|
||||
# options[:builder].bar 'fooing!'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# { foo: Foo.new }.to_xml(skip_instruct: true)
|
||||
# # =>
|
||||
# # <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.
|
||||
# Unless the option <tt>:skip_types</tt> exists and is true, an attribute "type" is
|
||||
# added as well according to the following mapping:
|
||||
#
|
||||
# XML_TYPE_NAMES = {
|
||||
# "Symbol" => "symbol",
|
||||
# "Integer" => "integer",
|
||||
# "BigDecimal" => "decimal",
|
||||
# "Float" => "float",
|
||||
# "TrueClass" => "boolean",
|
||||
# "FalseClass" => "boolean",
|
||||
# "Date" => "date",
|
||||
# "DateTime" => "dateTime",
|
||||
# "Time" => "dateTime"
|
||||
# }
|
||||
#
|
||||
# By default the root node is "hash", but that's configurable via the <tt>:root</tt> option.
|
||||
#
|
||||
# The default XML builder is a fresh instance of <tt>Builder::XmlMarkup</tt>. You can
|
||||
# configure your own builder with the <tt>:builder</tt> option. The method also accepts
|
||||
# options like <tt>:dasherize</tt> and friends, they are forwarded to the builder.
|
||||
def to_xml(options = {})
|
||||
require "active_support/builder" unless defined?(Builder)
|
||||
|
||||
options = options.dup
|
||||
options[:indent] ||= 2
|
||||
options[:root] ||= "hash"
|
||||
options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
|
||||
|
||||
builder = options[:builder]
|
||||
builder.instruct! unless options.delete(:skip_instruct)
|
||||
|
||||
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
|
||||
|
||||
builder.tag!(root) do
|
||||
each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) }
|
||||
yield builder if block_given?
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
# Returns a Hash containing a collection of pairs when the key is the node name and the value is
|
||||
# its content
|
||||
#
|
||||
# xml = <<-XML
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <hash>
|
||||
# <foo type="integer">1</foo>
|
||||
# <bar type="integer">2</bar>
|
||||
# </hash>
|
||||
# XML
|
||||
#
|
||||
# hash = Hash.from_xml(xml)
|
||||
# # => {"hash"=>{"foo"=>1, "bar"=>2}}
|
||||
#
|
||||
# +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.
|
||||
#
|
||||
# Custom +disallowed_types+ can also be passed in the form of an
|
||||
# array.
|
||||
#
|
||||
# xml = <<-XML
|
||||
# <?xml version="1.0" encoding="UTF-8"?>
|
||||
# <hash>
|
||||
# <foo type="integer">1</foo>
|
||||
# <bar type="string">"David"</bar>
|
||||
# </hash>
|
||||
# XML
|
||||
#
|
||||
# hash = Hash.from_xml(xml, ['integer'])
|
||||
# # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer"
|
||||
#
|
||||
# Note that passing custom disallowed types will override the default types,
|
||||
# which are Symbol and YAML.
|
||||
def from_xml(xml, disallowed_types = nil)
|
||||
ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
|
||||
end
|
||||
|
||||
# Builds a Hash from XML just like <tt>Hash.from_xml</tt>, but also allows Symbol and YAML.
|
||||
def from_trusted_xml(xml)
|
||||
from_xml xml, []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ActiveSupport
|
||||
class XMLConverter # :nodoc:
|
||||
# Raised if the XML contains attributes with type="yaml" or
|
||||
# type="symbol". Read Hash#from_xml for more details.
|
||||
class DisallowedType < StandardError
|
||||
def initialize(type)
|
||||
super "Disallowed type attribute: #{type.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
DISALLOWED_TYPES = %w(symbol yaml)
|
||||
|
||||
def initialize(xml, disallowed_types = nil)
|
||||
@xml = normalize_keys(XmlMini.parse(xml))
|
||||
@disallowed_types = disallowed_types || DISALLOWED_TYPES
|
||||
end
|
||||
|
||||
def to_h
|
||||
deep_to_h(@xml)
|
||||
end
|
||||
|
||||
private
|
||||
def normalize_keys(params)
|
||||
case params
|
||||
when Hash
|
||||
Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ]
|
||||
when Array
|
||||
params.map { |v| normalize_keys(v) }
|
||||
else
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
def deep_to_h(value)
|
||||
case value
|
||||
when Hash
|
||||
process_hash(value)
|
||||
when Array
|
||||
process_array(value)
|
||||
when String
|
||||
value
|
||||
else
|
||||
raise "can't typecast #{value.class.name} - #{value.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def process_hash(value)
|
||||
if value.include?("type") && !value["type"].is_a?(Hash) && @disallowed_types.include?(value["type"])
|
||||
raise DisallowedType, value["type"]
|
||||
end
|
||||
|
||||
if become_array?(value)
|
||||
_, entries = Array.wrap(value.detect { |k, v| not v.is_a?(String) })
|
||||
if entries.nil? || value["__content__"].try(:empty?)
|
||||
[]
|
||||
else
|
||||
case entries
|
||||
when Array
|
||||
entries.collect { |v| deep_to_h(v) }
|
||||
when Hash
|
||||
[deep_to_h(entries)]
|
||||
else
|
||||
raise "can't typecast #{entries.inspect}"
|
||||
end
|
||||
end
|
||||
elsif become_content?(value)
|
||||
process_content(value)
|
||||
|
||||
elsif become_empty_string?(value)
|
||||
""
|
||||
elsif become_hash?(value)
|
||||
xml_value = Hash[value.map { |k, v| [k, deep_to_h(v)] }]
|
||||
|
||||
# Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
|
||||
# how multipart uploaded files from HTML appear
|
||||
xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value
|
||||
end
|
||||
end
|
||||
|
||||
def become_content?(value)
|
||||
value["type"] == "file" || (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?))
|
||||
end
|
||||
|
||||
def become_array?(value)
|
||||
value["type"] == "array"
|
||||
end
|
||||
|
||||
def become_empty_string?(value)
|
||||
# { "string" => true }
|
||||
# No tests fail when the second term is removed.
|
||||
value["type"] == "string" && value["nil"] != "true"
|
||||
end
|
||||
|
||||
def become_hash?(value)
|
||||
!nothing?(value) && !garbage?(value)
|
||||
end
|
||||
|
||||
def nothing?(value)
|
||||
# blank or nil parsed values are represented by nil
|
||||
value.blank? || value["nil"] == "true"
|
||||
end
|
||||
|
||||
def garbage?(value)
|
||||
# If the type is the only element which makes it then
|
||||
# this still makes the value nil, except if type is
|
||||
# an XML node(where type['value'] is a Hash)
|
||||
value["type"] && !value["type"].is_a?(::Hash) && value.size == 1
|
||||
end
|
||||
|
||||
def process_content(value)
|
||||
content = value["__content__"]
|
||||
if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
|
||||
parser.arity == 1 ? parser.call(content) : parser.call(content, value)
|
||||
else
|
||||
content
|
||||
end
|
||||
end
|
||||
|
||||
def process_array(value)
|
||||
value.map! { |i| deep_to_h(i) }
|
||||
value.length > 1 ? value : value.first
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Hash
|
||||
# Returns a hash that includes everything except given keys.
|
||||
# hash = { a: true, b: false, c: nil }
|
||||
# hash.except(:c) # => { a: true, b: false }
|
||||
# hash.except(:a, :b) # => { c: nil }
|
||||
# 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
|
||||
|
||||
# Removes the given keys from hash and returns it.
|
||||
# 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
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/hash_with_indifferent_access"
|
||||
|
||||
class Hash
|
||||
# Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:
|
||||
#
|
||||
# { a: 1 }.with_indifferent_access['a'] # => 1
|
||||
def with_indifferent_access
|
||||
ActiveSupport::HashWithIndifferentAccess.new(self)
|
||||
end
|
||||
|
||||
# Called when object is nested under an object that receives
|
||||
# #with_indifferent_access. This method will be called on the current object
|
||||
# by the enclosing object and is aliased to #with_indifferent_access by
|
||||
# default. Subclasses of Hash may overwrite this method to return +self+ if
|
||||
# converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
|
||||
# desirable.
|
||||
#
|
||||
# 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
|
||||
@ -0,0 +1,172 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Hash
|
||||
# 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"}
|
||||
#
|
||||
# If you do not provide a +block+, it will return an Enumerator
|
||||
# for chaining with other methods:
|
||||
#
|
||||
# hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
|
||||
def transform_keys
|
||||
return enum_for(:transform_keys) { size } unless block_given?
|
||||
result = {}
|
||||
each_key do |key|
|
||||
result[yield(key)] = self[key]
|
||||
end
|
||||
result
|
||||
end unless method_defined? :transform_keys
|
||||
|
||||
# Destructively converts all keys using the +block+ operations.
|
||||
# Same as +transform_keys+ but modifies +self+.
|
||||
def transform_keys!
|
||||
return enum_for(:transform_keys!) { size } unless block_given?
|
||||
keys.each do |key|
|
||||
self[yield(key)] = delete(key)
|
||||
end
|
||||
self
|
||||
end unless method_defined? :transform_keys!
|
||||
|
||||
# Returns a new hash with all keys converted to strings.
|
||||
#
|
||||
# hash = { name: 'Rob', age: '28' }
|
||||
#
|
||||
# hash.stringify_keys
|
||||
# # => {"name"=>"Rob", "age"=>"28"}
|
||||
def stringify_keys
|
||||
transform_keys(&:to_s)
|
||||
end
|
||||
|
||||
# Destructively converts all keys to strings. Same as
|
||||
# +stringify_keys+, but modifies +self+.
|
||||
def stringify_keys!
|
||||
transform_keys!(&:to_s)
|
||||
end
|
||||
|
||||
# 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"}
|
||||
def symbolize_keys
|
||||
transform_keys { |key| key.to_sym rescue key }
|
||||
end
|
||||
alias_method :to_options, :symbolize_keys
|
||||
|
||||
# Destructively converts all keys to symbols, as long as they respond
|
||||
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
|
||||
def symbolize_keys!
|
||||
transform_keys! { |key| key.to_sym rescue key }
|
||||
end
|
||||
alias_method :to_options!, :symbolize_keys!
|
||||
|
||||
# Validates all keys in a hash match <tt>*valid_keys</tt>, raising
|
||||
# +ArgumentError+ on a mismatch.
|
||||
#
|
||||
# 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|
|
||||
unless valid_keys.include?(k)
|
||||
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# 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 and arrays.
|
||||
#
|
||||
# hash = { person: { name: 'Rob', age: '28' } }
|
||||
#
|
||||
# hash.deep_transform_keys{ |key| key.to_s.upcase }
|
||||
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
|
||||
def deep_transform_keys(&block)
|
||||
_deep_transform_keys_in_object(self, &block)
|
||||
end
|
||||
|
||||
# Destructively converts all keys by using the block operation.
|
||||
# This includes the keys from the root hash and from all
|
||||
# nested hashes and arrays.
|
||||
def deep_transform_keys!(&block)
|
||||
_deep_transform_keys_in_object!(self, &block)
|
||||
end
|
||||
|
||||
# Returns a new hash with all keys converted to strings.
|
||||
# This includes the keys from the root hash and from all
|
||||
# nested hashes and arrays.
|
||||
#
|
||||
# hash = { person: { name: 'Rob', age: '28' } }
|
||||
#
|
||||
# hash.deep_stringify_keys
|
||||
# # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
|
||||
def deep_stringify_keys
|
||||
deep_transform_keys(&:to_s)
|
||||
end
|
||||
|
||||
# Destructively converts all keys to strings.
|
||||
# This includes the keys from the root hash and from all
|
||||
# nested hashes and arrays.
|
||||
def deep_stringify_keys!
|
||||
deep_transform_keys!(&:to_s)
|
||||
end
|
||||
|
||||
# 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 arrays.
|
||||
#
|
||||
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
|
||||
#
|
||||
# hash.deep_symbolize_keys
|
||||
# # => {:person=>{:name=>"Rob", :age=>"28"}}
|
||||
def deep_symbolize_keys
|
||||
deep_transform_keys { |key| key.to_sym rescue key }
|
||||
end
|
||||
|
||||
# Destructively converts 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 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
|
||||
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Hash
|
||||
# Merges the caller into +other_hash+. For example,
|
||||
#
|
||||
# options = options.reverse_merge(size: 25, velocity: 10)
|
||||
#
|
||||
# is equivalent to
|
||||
#
|
||||
# options = { size: 25, velocity: 10 }.merge(options)
|
||||
#
|
||||
# This is particularly useful for initializing an options hash
|
||||
# with default values.
|
||||
def reverse_merge(other_hash)
|
||||
other_hash.merge(self)
|
||||
end
|
||||
alias_method :with_defaults, :reverse_merge
|
||||
|
||||
# Destructive +reverse_merge+.
|
||||
def reverse_merge!(other_hash)
|
||||
replace(reverse_merge(other_hash))
|
||||
end
|
||||
alias_method :reverse_update, :reverse_merge!
|
||||
alias_method :with_defaults!, :reverse_merge!
|
||||
end
|
||||
@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Hash
|
||||
# Slices 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)
|
||||
# end
|
||||
#
|
||||
# search(options.slice(:mass, :velocity, :time))
|
||||
#
|
||||
# If you have an array of keys you want to limit to, you should splat them:
|
||||
#
|
||||
# valid_keys = [:mass, :velocity, :time]
|
||||
# search(options.slice(*valid_keys))
|
||||
def slice(*keys)
|
||||
keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
|
||||
end unless method_defined?(:slice)
|
||||
|
||||
# Replaces the hash with only the given keys.
|
||||
# Returns a hash containing the removed key/value pairs.
|
||||
#
|
||||
# { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
|
||||
# # => {:c=>3, :d=>4}
|
||||
def slice!(*keys)
|
||||
omit = slice(*self.keys - keys)
|
||||
hash = slice(*keys)
|
||||
hash.default = default
|
||||
hash.default_proc = default_proc if default_proc
|
||||
replace(hash)
|
||||
omit
|
||||
end
|
||||
|
||||
# Removes and returns the key/value pairs matching the given keys.
|
||||
#
|
||||
# { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
|
||||
# { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
|
||||
def extract!(*keys)
|
||||
keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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 }
|
||||
#
|
||||
# If you do not provide a +block+, it will return an Enumerator
|
||||
# for chaining with other methods:
|
||||
#
|
||||
# { a: 1, b: 2 }.transform_values.with_index { |v, i| [v, i].join.to_i } # => { a: 10, b: 21 }
|
||||
def transform_values
|
||||
return enum_for(:transform_values) { size } unless block_given?
|
||||
return {} if empty?
|
||||
result = self.class.new
|
||||
each do |key, value|
|
||||
result[key] = yield(value)
|
||||
end
|
||||
result
|
||||
end unless method_defined? :transform_values
|
||||
|
||||
# Destructively converts all values using the +block+ operations.
|
||||
# Same as +transform_values+ but modifies +self+.
|
||||
def transform_values!
|
||||
return enum_for(:transform_values!) { size } unless block_given?
|
||||
each do |key, value|
|
||||
self[key] = yield(value)
|
||||
end
|
||||
end unless method_defined? :transform_values!
|
||||
# TODO: Remove this file when supporting only Ruby 2.4+.
|
||||
end
|
||||
@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/integer/multiple"
|
||||
require "active_support/core_ext/integer/inflections"
|
||||
require "active_support/core_ext/integer/time"
|
||||
@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/inflector"
|
||||
|
||||
class Integer
|
||||
# Ordinalize turns a number into an ordinal string used to denote the
|
||||
# position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
|
||||
#
|
||||
# 1.ordinalize # => "1st"
|
||||
# 2.ordinalize # => "2nd"
|
||||
# 1002.ordinalize # => "1002nd"
|
||||
# 1003.ordinalize # => "1003rd"
|
||||
# -11.ordinalize # => "-11th"
|
||||
# -1001.ordinalize # => "-1001st"
|
||||
def ordinalize
|
||||
ActiveSupport::Inflector.ordinalize(self)
|
||||
end
|
||||
|
||||
# Ordinal returns the suffix used to denote the position
|
||||
# in an ordered sequence such as 1st, 2nd, 3rd, 4th.
|
||||
#
|
||||
# 1.ordinal # => "st"
|
||||
# 2.ordinal # => "nd"
|
||||
# 1002.ordinal # => "nd"
|
||||
# 1003.ordinal # => "rd"
|
||||
# -11.ordinal # => "th"
|
||||
# -1001.ordinal # => "st"
|
||||
def ordinal
|
||||
ActiveSupport::Inflector.ordinal(self)
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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
|
||||
def multiple_of?(number)
|
||||
number != 0 ? self % number == 0 : zero?
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/duration"
|
||||
require "active_support/core_ext/numeric/time"
|
||||
|
||||
class Integer
|
||||
# Returns a Duration instance matching the number of months provided.
|
||||
#
|
||||
# 2.months # => 2 months
|
||||
def months
|
||||
ActiveSupport::Duration.months(self)
|
||||
end
|
||||
alias :month :months
|
||||
|
||||
# Returns a Duration instance matching the number of years provided.
|
||||
#
|
||||
# 2.years # => 2 years
|
||||
def years
|
||||
ActiveSupport::Duration.years(self)
|
||||
end
|
||||
alias :year :years
|
||||
end
|
||||
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/kernel/agnostics"
|
||||
require "active_support/core_ext/kernel/concern"
|
||||
require "active_support/core_ext/kernel/reporting"
|
||||
require "active_support/core_ext/kernel/singleton_class"
|
||||
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Object
|
||||
# Makes backticks behave (somewhat more) similarly on all platforms.
|
||||
# On win32 `nonexistent_command` raises Errno::ENOENT; on Unix, the
|
||||
# spawned shell prints a message to stderr and sets $?. We emulate
|
||||
# Unix on the former but not the latter.
|
||||
def `(command) #:nodoc:
|
||||
super
|
||||
rescue Errno::ENOENT => e
|
||||
STDERR.puts "#$0: #{e}"
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/module/concerning"
|
||||
|
||||
module Kernel
|
||||
module_function
|
||||
|
||||
# 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
|
||||
@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Kernel
|
||||
module_function
|
||||
|
||||
# Sets $VERBOSE to +nil+ for the duration of the block and back to its original
|
||||
# value afterwards.
|
||||
#
|
||||
# silence_warnings do
|
||||
# value = noisy_call # no warning voiced
|
||||
# end
|
||||
#
|
||||
# noisy_call # warning voiced
|
||||
def silence_warnings
|
||||
with_warnings(nil) { yield }
|
||||
end
|
||||
|
||||
# Sets $VERBOSE to +true+ for the duration of the block and back to its
|
||||
# original value afterwards.
|
||||
def enable_warnings
|
||||
with_warnings(true) { yield }
|
||||
end
|
||||
|
||||
# Sets $VERBOSE for the duration of the block and back to its original
|
||||
# value afterwards.
|
||||
def with_warnings(flag)
|
||||
old_verbose, $VERBOSE = $VERBOSE, flag
|
||||
yield
|
||||
ensure
|
||||
$VERBOSE = old_verbose
|
||||
end
|
||||
|
||||
# Blocks and ignores any exception passed as argument if raised within the block.
|
||||
#
|
||||
# suppress(ZeroDivisionError) do
|
||||
# 1/0
|
||||
# puts 'This code is NOT reached'
|
||||
# end
|
||||
#
|
||||
# puts 'This code gets executed and nothing related to ZeroDivisionError was seen'
|
||||
def suppress(*exception_classes)
|
||||
yield
|
||||
rescue *exception_classes
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Kernel
|
||||
# class_eval on an object acts like singleton_class.class_eval.
|
||||
def class_eval(*args, &block)
|
||||
singleton_class.class_eval(*args, &block)
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class LoadError
|
||||
# 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$/, "".freeze) == path.sub(/\.rb$/, "".freeze)
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActiveSupport
|
||||
module MarshalWithAutoloading # :nodoc:
|
||||
def load(source, proc = nil)
|
||||
super(source, proc)
|
||||
rescue ArgumentError, NameError => exc
|
||||
if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|)
|
||||
# try loading the class/module
|
||||
loaded = $1.constantize
|
||||
|
||||
raise unless $1 == loaded.name
|
||||
|
||||
# if it is an IO we need to go back to read the object
|
||||
source.rewind if source.respond_to?(:rewind)
|
||||
retry
|
||||
else
|
||||
raise exc
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Marshal.singleton_class.prepend(ActiveSupport::MarshalWithAutoloading)
|
||||
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/module/aliasing"
|
||||
require "active_support/core_ext/module/introspection"
|
||||
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/attribute_accessors_per_thread"
|
||||
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/redefine_method"
|
||||
require "active_support/core_ext/module/remove_method"
|
||||
@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Module
|
||||
# Allows you to make aliases for attributes, which includes
|
||||
# getter, setter, and a predicate.
|
||||
#
|
||||
# class Content < ActiveRecord::Base
|
||||
# # has a title attribute
|
||||
# end
|
||||
#
|
||||
# class Email < Content
|
||||
# alias_attribute :subject, :title
|
||||
# end
|
||||
#
|
||||
# e = Email.find(1)
|
||||
# e.title # => "Superstars"
|
||||
# e.subject # => "Superstars"
|
||||
# e.subject? # => true
|
||||
# e.subject = "Megastars"
|
||||
# e.title # => "Megastars"
|
||||
def alias_attribute(new_name, old_name)
|
||||
# The following reader methods use an explicit `self` receiver in order to
|
||||
# support aliases that start with an uppercase letter. Otherwise, they would
|
||||
# be resolved as constants instead.
|
||||
module_eval <<-STR, __FILE__, __LINE__ + 1
|
||||
def #{new_name}; self.#{old_name}; end # def subject; self.title; end
|
||||
def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end
|
||||
def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end
|
||||
STR
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Module
|
||||
# A module may or may not have a name.
|
||||
#
|
||||
# module M; end
|
||||
# M.name # => "M"
|
||||
#
|
||||
# m = Module.new
|
||||
# m.name # => nil
|
||||
#
|
||||
# +anonymous?+ method returns true if module does not have a name, false otherwise:
|
||||
#
|
||||
# Module.new.anonymous? # => true
|
||||
#
|
||||
# module M; end
|
||||
# M.anonymous? # => false
|
||||
#
|
||||
# A module gets a name when it is first assigned to a constant. Either
|
||||
# via the +module+ or +class+ keyword or by an explicit assignment:
|
||||
#
|
||||
# m = Module.new # creates an anonymous module
|
||||
# m.anonymous? # => true
|
||||
# M = m # m gets a name here as a side-effect
|
||||
# m.name # => "M"
|
||||
# m.anonymous? # => false
|
||||
def anonymous?
|
||||
name.nil?
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Module
|
||||
# Declares an attribute reader backed by an internally-named instance variable.
|
||||
def attr_internal_reader(*attrs)
|
||||
attrs.each { |attr_name| attr_internal_define(attr_name, :reader) }
|
||||
end
|
||||
|
||||
# Declares an attribute writer backed by an internally-named instance variable.
|
||||
def attr_internal_writer(*attrs)
|
||||
attrs.each { |attr_name| attr_internal_define(attr_name, :writer) }
|
||||
end
|
||||
|
||||
# Declares an attribute reader and writer backed by an internally-named instance
|
||||
# variable.
|
||||
def attr_internal_accessor(*attrs)
|
||||
attr_internal_reader(*attrs)
|
||||
attr_internal_writer(*attrs)
|
||||
end
|
||||
alias_method :attr_internal, :attr_internal_accessor
|
||||
|
||||
class << self; attr_accessor :attr_internal_naming_format end
|
||||
self.attr_internal_naming_format = "@_%s"
|
||||
|
||||
private
|
||||
def attr_internal_ivar_name(attr)
|
||||
Module.attr_internal_naming_format % attr
|
||||
end
|
||||
|
||||
def attr_internal_define(attr_name, type)
|
||||
internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, "")
|
||||
# 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
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,215 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/array/extract_options"
|
||||
require "active_support/core_ext/regexp"
|
||||
|
||||
# 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 class variable is set to +nil+, if it is not previously
|
||||
# defined. All class and instance methods created will be public, even if
|
||||
# this method is called with a private or protected access modifier.
|
||||
#
|
||||
# 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: 1_Badname
|
||||
#
|
||||
# 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_reader :hair_colors, instance_reader: false
|
||||
# end
|
||||
#
|
||||
# class Person
|
||||
# include HairColors
|
||||
# end
|
||||
#
|
||||
# Person.new.hair_colors # => NoMethodError
|
||||
#
|
||||
# You can set a default value for the attribute.
|
||||
#
|
||||
# module HairColors
|
||||
# mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
|
||||
# end
|
||||
#
|
||||
# class Person
|
||||
# include HairColors
|
||||
# end
|
||||
#
|
||||
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
|
||||
def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil)
|
||||
syms.each do |sym|
|
||||
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
|
||||
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||
@@#{sym} = nil unless defined? @@#{sym}
|
||||
|
||||
def self.#{sym}
|
||||
@@#{sym}
|
||||
end
|
||||
EOS
|
||||
|
||||
if instance_reader && instance_accessor
|
||||
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||
def #{sym}
|
||||
@@#{sym}
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
sym_default_value = (block_given? && default.nil?) ? yield : default
|
||||
class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil?
|
||||
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. All class and instance methods created
|
||||
# will be public, even if this method is called with a private or protected
|
||||
# access modifier.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# You can set a default value for the attribute.
|
||||
#
|
||||
# module HairColors
|
||||
# mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red]
|
||||
# end
|
||||
#
|
||||
# class Person
|
||||
# include HairColors
|
||||
# end
|
||||
#
|
||||
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
|
||||
def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil)
|
||||
syms.each do |sym|
|
||||
raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
|
||||
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||
@@#{sym} = nil unless defined? @@#{sym}
|
||||
|
||||
def self.#{sym}=(obj)
|
||||
@@#{sym} = obj
|
||||
end
|
||||
EOS
|
||||
|
||||
if instance_writer && instance_accessor
|
||||
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||
def #{sym}=(obj)
|
||||
@@#{sym} = obj
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
sym_default_value = (block_given? && default.nil?) ? yield : default
|
||||
send("#{sym}=", sym_default_value) unless sym_default_value.nil?
|
||||
end
|
||||
end
|
||||
alias :cattr_writer :mattr_writer
|
||||
|
||||
# Defines both class and instance accessors for class attributes.
|
||||
# All class and instance methods created will be public, even if
|
||||
# this method is called with a private or protected access modifier.
|
||||
#
|
||||
# module HairColors
|
||||
# mattr_accessor :hair_colors
|
||||
# end
|
||||
#
|
||||
# class Person
|
||||
# include HairColors
|
||||
# end
|
||||
#
|
||||
# HairColors.hair_colors = [:brown, :black, :blonde, :red]
|
||||
# HairColors.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.new.hair_colors << :blue
|
||||
# Person.new.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>.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# You can set a default value for the attribute.
|
||||
#
|
||||
# module HairColors
|
||||
# mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red]
|
||||
# end
|
||||
#
|
||||
# class Person
|
||||
# include HairColors
|
||||
# end
|
||||
#
|
||||
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
|
||||
def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
|
||||
mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk)
|
||||
mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default)
|
||||
end
|
||||
alias :cattr_accessor :mattr_accessor
|
||||
end
|
||||
@ -0,0 +1,150 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/array/extract_options"
|
||||
require "active_support/core_ext/regexp"
|
||||
|
||||
# Extends the module object with class/module and instance accessors for
|
||||
# class/module attributes, just like the native attr* accessors for instance
|
||||
# attributes, but does so on a per-thread basis.
|
||||
#
|
||||
# So the values are scoped within the Thread.current space under the class name
|
||||
# of the module.
|
||||
class Module
|
||||
# Defines a per-thread class attribute and creates class and instance reader methods.
|
||||
# The underlying per-thread class variable is set to +nil+, if it is not previously defined.
|
||||
#
|
||||
# module Current
|
||||
# thread_mattr_reader :user
|
||||
# end
|
||||
#
|
||||
# Current.user # => nil
|
||||
# Thread.current[:attr_Current_user] = "DHH"
|
||||
# Current.user # => "DHH"
|
||||
#
|
||||
# The attribute name must be a valid method name in Ruby.
|
||||
#
|
||||
# module Foo
|
||||
# thread_mattr_reader :"1_Badname"
|
||||
# end
|
||||
# # => NameError: invalid attribute name: 1_Badname
|
||||
#
|
||||
# If you want to opt out of the creation of the instance reader method, pass
|
||||
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
|
||||
#
|
||||
# class Current
|
||||
# thread_mattr_reader :user, instance_reader: false
|
||||
# end
|
||||
#
|
||||
# Current.new.user # => NoMethodError
|
||||
def thread_mattr_reader(*syms) # :nodoc:
|
||||
options = syms.extract_options!
|
||||
|
||||
syms.each do |sym|
|
||||
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
|
||||
|
||||
# The following generated method concatenates `name` because we want it
|
||||
# to work with inheritance via polymorphism.
|
||||
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||
def self.#{sym}
|
||||
Thread.current["attr_" + name + "_#{sym}"]
|
||||
end
|
||||
EOS
|
||||
|
||||
unless options[:instance_reader] == false || options[:instance_accessor] == false
|
||||
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||
def #{sym}
|
||||
self.class.#{sym}
|
||||
end
|
||||
EOS
|
||||
end
|
||||
end
|
||||
end
|
||||
alias :thread_cattr_reader :thread_mattr_reader
|
||||
|
||||
# Defines a per-thread class attribute and creates a class and instance writer methods to
|
||||
# allow assignment to the attribute.
|
||||
#
|
||||
# module Current
|
||||
# thread_mattr_writer :user
|
||||
# end
|
||||
#
|
||||
# Current.user = "DHH"
|
||||
# Thread.current[:attr_Current_user] # => "DHH"
|
||||
#
|
||||
# If you want to opt out of the creation of the instance writer method, pass
|
||||
# <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
|
||||
#
|
||||
# class Current
|
||||
# thread_mattr_writer :user, instance_writer: false
|
||||
# end
|
||||
#
|
||||
# Current.new.user = "DHH" # => NoMethodError
|
||||
def thread_mattr_writer(*syms) # :nodoc:
|
||||
options = syms.extract_options!
|
||||
syms.each do |sym|
|
||||
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
|
||||
|
||||
# The following generated method concatenates `name` because we want it
|
||||
# to work with inheritance via polymorphism.
|
||||
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||
def self.#{sym}=(obj)
|
||||
Thread.current["attr_" + name + "_#{sym}"] = obj
|
||||
end
|
||||
EOS
|
||||
|
||||
unless options[:instance_writer] == false || options[:instance_accessor] == false
|
||||
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
||||
def #{sym}=(obj)
|
||||
self.class.#{sym} = obj
|
||||
end
|
||||
EOS
|
||||
end
|
||||
end
|
||||
end
|
||||
alias :thread_cattr_writer :thread_mattr_writer
|
||||
|
||||
# Defines both class and instance accessors for class attributes.
|
||||
#
|
||||
# class Account
|
||||
# thread_mattr_accessor :user
|
||||
# end
|
||||
#
|
||||
# Account.user = "DHH"
|
||||
# Account.user # => "DHH"
|
||||
# Account.new.user # => "DHH"
|
||||
#
|
||||
# If a subclass changes the value, the parent class' value is not changed.
|
||||
# Similarly, if the parent class changes the value, the value of subclasses
|
||||
# is not changed.
|
||||
#
|
||||
# class Customer < Account
|
||||
# end
|
||||
#
|
||||
# Customer.user = "Rafael"
|
||||
# Customer.user # => "Rafael"
|
||||
# Account.user # => "DHH"
|
||||
#
|
||||
# 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 Current
|
||||
# thread_mattr_accessor :user, instance_writer: false, instance_reader: false
|
||||
# end
|
||||
#
|
||||
# Current.new.user = "DHH" # => NoMethodError
|
||||
# Current.new.user # => NoMethodError
|
||||
#
|
||||
# Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
|
||||
#
|
||||
# class Current
|
||||
# mattr_accessor :user, instance_accessor: false
|
||||
# end
|
||||
#
|
||||
# Current.new.user = "DHH" # => NoMethodError
|
||||
# Current.new.user # => NoMethodError
|
||||
def thread_mattr_accessor(*syms)
|
||||
thread_mattr_reader(*syms)
|
||||
thread_mattr_writer(*syms)
|
||||
end
|
||||
alias :thread_cattr_accessor :thread_mattr_accessor
|
||||
end
|
||||
@ -0,0 +1,134 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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 < ApplicationRecord
|
||||
# # Other todo implementation
|
||||
# # ...
|
||||
#
|
||||
# ## Event tracking
|
||||
# has_many :events
|
||||
#
|
||||
# before_create :track_creation
|
||||
#
|
||||
# private
|
||||
# def track_creation
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# == With an inline module:
|
||||
#
|
||||
# Noisy syntax.
|
||||
#
|
||||
# class Todo < ApplicationRecord
|
||||
# # Other todo implementation
|
||||
# # ...
|
||||
#
|
||||
# module EventTracking
|
||||
# extend ActiveSupport::Concern
|
||||
#
|
||||
# included do
|
||||
# has_many :events
|
||||
# before_create :track_creation
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def track_creation
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
# include EventTracking
|
||||
# end
|
||||
#
|
||||
# == Mix-in noise exiled to its own file:
|
||||
#
|
||||
# Once our chunk of behavior starts pushing the scroll-to-understand-it
|
||||
# boundary, we give in and move it to a separate file. At this size, the
|
||||
# increased overhead can be a reasonable tradeoff even if it reduces our
|
||||
# at-a-glance perception of how things work.
|
||||
#
|
||||
# class Todo < ApplicationRecord
|
||||
# # 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 < ApplicationRecord
|
||||
# # Other todo implementation
|
||||
# # ...
|
||||
#
|
||||
# concerning :EventTracking do
|
||||
# included do
|
||||
# has_many :events
|
||||
# before_create :track_creation
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def track_creation
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Todo.ancestors
|
||||
# # => [Todo, Todo::EventTracking, ApplicationRecord, 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
|
||||
@ -0,0 +1,287 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "set"
|
||||
require "active_support/core_ext/regexp"
|
||||
|
||||
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_KEYWORDS = %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)
|
||||
DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
|
||||
DELEGATION_RESERVED_METHOD_NAMES = Set.new(
|
||||
RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
|
||||
).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 +Module::DelegationError+
|
||||
# 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
|
||||
# (also a symbol or string).
|
||||
#
|
||||
# Delegation is particularly useful with Active Record associations:
|
||||
#
|
||||
# class Greeter < ActiveRecord::Base
|
||||
# def hello
|
||||
# 'hello'
|
||||
# end
|
||||
#
|
||||
# def goodbye
|
||||
# 'goodbye'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class Foo < ActiveRecord::Base
|
||||
# belongs_to :greeter
|
||||
# delegate :hello, to: :greeter
|
||||
# end
|
||||
#
|
||||
# Foo.new.hello # => "hello"
|
||||
# Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
|
||||
#
|
||||
# Multiple delegates to the same target are allowed:
|
||||
#
|
||||
# class Foo < ActiveRecord::Base
|
||||
# belongs_to :greeter
|
||||
# delegate :hello, :goodbye, to: :greeter
|
||||
# end
|
||||
#
|
||||
# Foo.new.goodbye # => "goodbye"
|
||||
#
|
||||
# Methods can be delegated to instance variables, class variables, or constants
|
||||
# by providing them as a symbols:
|
||||
#
|
||||
# class Foo
|
||||
# CONSTANT_ARRAY = [0,1,2,3]
|
||||
# @@class_array = [4,5,6,7]
|
||||
#
|
||||
# def initialize
|
||||
# @instance_array = [8,9,10,11]
|
||||
# end
|
||||
# delegate :sum, to: :CONSTANT_ARRAY
|
||||
# delegate :min, to: :@@class_array
|
||||
# delegate :max, to: :@instance_array
|
||||
# end
|
||||
#
|
||||
# Foo.new.sum # => 6
|
||||
# Foo.new.min # => 4
|
||||
# Foo.new.max # => 11
|
||||
#
|
||||
# It's also possible to delegate a method to the class by using +:class+:
|
||||
#
|
||||
# class Foo
|
||||
# def self.hello
|
||||
# "world"
|
||||
# end
|
||||
#
|
||||
# delegate :hello, to: :class
|
||||
# end
|
||||
#
|
||||
# Foo.new.hello # => "world"
|
||||
#
|
||||
# Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
|
||||
# is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
|
||||
# delegated to.
|
||||
#
|
||||
# Person = Struct.new(:name, :address)
|
||||
#
|
||||
# class Invoice < Struct.new(:client)
|
||||
# delegate :name, :address, to: :client, prefix: true
|
||||
# end
|
||||
#
|
||||
# john_doe = Person.new('John Doe', 'Vimmersvej 13')
|
||||
# invoice = Invoice.new(john_doe)
|
||||
# invoice.client_name # => "John Doe"
|
||||
# invoice.client_address # => "Vimmersvej 13"
|
||||
#
|
||||
# It is also possible to supply a custom prefix.
|
||||
#
|
||||
# class Invoice < Struct.new(:client)
|
||||
# delegate :name, :address, to: :client, prefix: :customer
|
||||
# end
|
||||
#
|
||||
# invoice = Invoice.new(john_doe)
|
||||
# invoice.customer_name # => 'John Doe'
|
||||
# invoice.customer_address # => 'Vimmersvej 13'
|
||||
#
|
||||
# If the target is +nil+ and does not respond to the delegated method a
|
||||
# +Module::DelegationError+ is raised. If you wish to instead return +nil+,
|
||||
# use the <tt>:allow_nil</tt> option.
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# has_one :profile
|
||||
# delegate :age, to: :profile
|
||||
# end
|
||||
#
|
||||
# User.new.age
|
||||
# # => Module::DelegationError: User#age delegated to profile.age, but profile is nil
|
||||
#
|
||||
# But if not having a profile yet is fine and should not be an error
|
||||
# condition:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# has_one :profile
|
||||
# delegate :age, to: :profile, allow_nil: true
|
||||
# end
|
||||
#
|
||||
# User.new.age # nil
|
||||
#
|
||||
# Note that if the target is not +nil+ then the call is attempted regardless of the
|
||||
# <tt>:allow_nil</tt> option, and thus an exception is still raised if said object
|
||||
# does not respond to the method:
|
||||
#
|
||||
# class Foo
|
||||
# def initialize(bar)
|
||||
# @bar = bar
|
||||
# end
|
||||
#
|
||||
# delegate :name, to: :@bar, allow_nil: true
|
||||
# end
|
||||
#
|
||||
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
|
||||
#
|
||||
# The target method must be public, otherwise it will raise +NoMethodError+.
|
||||
def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
|
||||
unless to
|
||||
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)."
|
||||
end
|
||||
|
||||
if prefix == true && /^[^a-z_]/.match?(to)
|
||||
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
|
||||
end
|
||||
|
||||
method_prefix = \
|
||||
if prefix
|
||||
"#{prefix == true ? to : prefix}_"
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
location = caller_locations(1, 1).first
|
||||
file, line = location.path, location.lineno
|
||||
|
||||
to = to.to_s
|
||||
to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
|
||||
|
||||
methods.map do |method|
|
||||
# Attribute writer methods only accept one argument. Makes sure []=
|
||||
# methods still accept two arguments.
|
||||
definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block"
|
||||
|
||||
# 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.
|
||||
# On the other hand it could be that the target has side-effects,
|
||||
# whereas conceptually, from the user point of view, the delegator should
|
||||
# be doing one call.
|
||||
if allow_nil
|
||||
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}")
|
||||
|
||||
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
|
||||
|
||||
# When building decorators, a common pattern may emerge:
|
||||
#
|
||||
# class Partition
|
||||
# def initialize(event)
|
||||
# @event = event
|
||||
# end
|
||||
#
|
||||
# def person
|
||||
# @event.detail.person || @event.creator
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def respond_to_missing?(name, include_private = false)
|
||||
# @event.respond_to?(name, include_private)
|
||||
# end
|
||||
#
|
||||
# def method_missing(method, *args, &block)
|
||||
# @event.send(method, *args, &block)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# With <tt>Module#delegate_missing_to</tt>, the above is condensed to:
|
||||
#
|
||||
# class Partition
|
||||
# delegate_missing_to :@event
|
||||
#
|
||||
# def initialize(event)
|
||||
# @event = event
|
||||
# end
|
||||
#
|
||||
# def person
|
||||
# @event.detail.person || @event.creator
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The target can be anything callable within the object, e.g. instance
|
||||
# variables, methods, constants, etc.
|
||||
#
|
||||
# The delegated method must be public on the target, otherwise it will
|
||||
# raise +NoMethodError+.
|
||||
def delegate_missing_to(target)
|
||||
target = target.to_s
|
||||
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
|
||||
|
||||
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||
def respond_to_missing?(name, include_private = false)
|
||||
# It may look like an oversight, but we deliberately do not pass
|
||||
# +include_private+, because they do not get delegated.
|
||||
|
||||
#{target}.respond_to?(name) || super
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &block)
|
||||
if #{target}.respond_to?(method)
|
||||
#{target}.public_send(method, *args, &block)
|
||||
else
|
||||
begin
|
||||
super
|
||||
rescue NoMethodError
|
||||
if #{target}.nil?
|
||||
raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Module
|
||||
# deprecate :foo
|
||||
# deprecate bar: 'message'
|
||||
# deprecate :foo, :bar, baz: 'warning!', qux: 'gone!'
|
||||
#
|
||||
# You can also use custom deprecator instance:
|
||||
#
|
||||
# deprecate :foo, deprecator: MyLib::Deprecator.new
|
||||
# deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new
|
||||
#
|
||||
# \Custom deprecators must respond to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt>
|
||||
# method where you can implement your custom warning behavior.
|
||||
#
|
||||
# class MyLib::Deprecator
|
||||
# def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
|
||||
# message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
|
||||
# Kernel.warn message
|
||||
# end
|
||||
# end
|
||||
def deprecate(*method_names)
|
||||
ActiveSupport::Deprecation.deprecate_methods(self, *method_names)
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/inflector"
|
||||
|
||||
class Module
|
||||
# Returns the name of the module containing this one.
|
||||
#
|
||||
# M::N.parent_name # => "M"
|
||||
def parent_name
|
||||
if defined?(@parent_name)
|
||||
@parent_name
|
||||
else
|
||||
parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
|
||||
@parent_name = parent_name unless frozen?
|
||||
parent_name
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the module which contains this one according to its name.
|
||||
#
|
||||
# module M
|
||||
# module N
|
||||
# end
|
||||
# end
|
||||
# X = M::N
|
||||
#
|
||||
# M::N.parent # => M
|
||||
# X.parent # => M
|
||||
#
|
||||
# The parent of top-level and anonymous modules is Object.
|
||||
#
|
||||
# M.parent # => Object
|
||||
# Module.new.parent # => Object
|
||||
def parent
|
||||
parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object
|
||||
end
|
||||
|
||||
# Returns all the parents of this module according to its name, ordered from
|
||||
# nested outwards. The receiver is not contained within the result.
|
||||
#
|
||||
# module M
|
||||
# module N
|
||||
# end
|
||||
# end
|
||||
# X = M::N
|
||||
#
|
||||
# M.parents # => [Object]
|
||||
# M::N.parents # => [M, Object]
|
||||
# X.parents # => [M, Object]
|
||||
def parents
|
||||
parents = []
|
||||
if parent_name
|
||||
parts = parent_name.split("::")
|
||||
until parts.empty?
|
||||
parents << ActiveSupport::Inflector.constantize(parts * "::")
|
||||
parts.pop
|
||||
end
|
||||
end
|
||||
parents << Object unless parents.include? Object
|
||||
parents
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/module/anonymous"
|
||||
require "active_support/core_ext/string/inflections"
|
||||
|
||||
class Module
|
||||
def reachable? #:nodoc:
|
||||
!anonymous? && name.safe_constantize.equal?(self)
|
||||
end
|
||||
deprecate :reachable?
|
||||
end
|
||||
@ -0,0 +1,49 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Module
|
||||
if RUBY_VERSION >= "2.3"
|
||||
# Marks the named method as intended to be redefined, if it exists.
|
||||
# Suppresses the Ruby method redefinition warning. Prefer
|
||||
# #redefine_method where possible.
|
||||
def silence_redefinition_of_method(method)
|
||||
if method_defined?(method) || private_method_defined?(method)
|
||||
# This suppresses the "method redefined" warning; the self-alias
|
||||
# looks odd, but means we don't need to generate a unique name
|
||||
alias_method method, method
|
||||
end
|
||||
end
|
||||
else
|
||||
def silence_redefinition_of_method(method)
|
||||
if method_defined?(method) || private_method_defined?(method)
|
||||
alias_method :__rails_redefine, method
|
||||
remove_method :__rails_redefine
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Replaces the existing method definition, if there is one, with the passed
|
||||
# block as its body.
|
||||
def redefine_method(method, &block)
|
||||
visibility = method_visibility(method)
|
||||
silence_redefinition_of_method(method)
|
||||
define_method(method, &block)
|
||||
send(visibility, method)
|
||||
end
|
||||
|
||||
# Replaces the existing singleton method definition, if there is one, with
|
||||
# the passed block as its body.
|
||||
def redefine_singleton_method(method, &block)
|
||||
singleton_class.redefine_method(method, &block)
|
||||
end
|
||||
|
||||
def method_visibility(method) # :nodoc:
|
||||
case
|
||||
when private_method_defined?(method)
|
||||
:private
|
||||
when protected_method_defined?(method)
|
||||
:protected
|
||||
else
|
||||
:public
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/module/redefine_method"
|
||||
|
||||
class Module
|
||||
# Removes the named method, if it exists.
|
||||
def remove_possible_method(method)
|
||||
if method_defined?(method) || private_method_defined?(method)
|
||||
undef_method(method)
|
||||
end
|
||||
end
|
||||
|
||||
# Removes the named singleton method, if it exists.
|
||||
def remove_possible_singleton_method(method)
|
||||
singleton_class.remove_possible_method(method)
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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
|
||||
# Since ruby v2.3.0 `did_you_mean` gem is loaded by default.
|
||||
# It extends NameError#message with spell corrections which are SLOW.
|
||||
# We should use original_message message instead.
|
||||
message = respond_to?(:original_message) ? original_message : self.message
|
||||
|
||||
if /undefined local variable or method/ !~ message
|
||||
$1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message
|
||||
end
|
||||
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
|
||||
self.name == name
|
||||
else
|
||||
missing_name == name.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/numeric/bytes"
|
||||
require "active_support/core_ext/numeric/time"
|
||||
require "active_support/core_ext/numeric/inquiry"
|
||||
require "active_support/core_ext/numeric/conversions"
|
||||
@ -0,0 +1,66 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Numeric
|
||||
KILOBYTE = 1024
|
||||
MEGABYTE = KILOBYTE * 1024
|
||||
GIGABYTE = MEGABYTE * 1024
|
||||
TERABYTE = GIGABYTE * 1024
|
||||
PETABYTE = TERABYTE * 1024
|
||||
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
|
||||
alias :exabyte :exabytes
|
||||
end
|
||||
@ -0,0 +1,140 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/big_decimal/conversions"
|
||||
require "active_support/number_helper"
|
||||
require "active_support/core_ext/module/deprecation"
|
||||
|
||||
module ActiveSupport::NumericWithFormat
|
||||
# Provides options for converting numbers into formatted strings.
|
||||
# Options are provided for phone numbers, currency, percentage,
|
||||
# precision, positional notation, file size and pretty printing.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# For details on which formats use which options, see ActiveSupport::NumberHelper
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# Phone Numbers:
|
||||
# 5551234.to_s(:phone) # => "555-1234"
|
||||
# 1235551234.to_s(:phone) # => "123-555-1234"
|
||||
# 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234"
|
||||
# 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234"
|
||||
# 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
|
||||
# 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234"
|
||||
# 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
|
||||
# # => "+1.123.555.1234 x 1343"
|
||||
#
|
||||
# Currency:
|
||||
# 1234567890.50.to_s(:currency) # => "$1,234,567,890.50"
|
||||
# 1234567890.506.to_s(:currency) # => "$1,234,567,890.51"
|
||||
# 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506"
|
||||
# 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €"
|
||||
# -1234567890.50.to_s(:currency, negative_format: '(%u%n)')
|
||||
# # => "($1,234,567,890.50)"
|
||||
# 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '')
|
||||
# # => "£1234567890,50"
|
||||
# 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '', format: '%n %u')
|
||||
# # => "1234567890,50 £"
|
||||
#
|
||||
# Percentage:
|
||||
# 100.to_s(:percentage) # => "100.000%"
|
||||
# 100.to_s(:percentage, precision: 0) # => "100%"
|
||||
# 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%"
|
||||
# 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%"
|
||||
# 1000.to_s(:percentage, locale: :fr) # => "1 000,000%"
|
||||
# 100.to_s(:percentage, format: '%n %') # => "100.000 %"
|
||||
#
|
||||
# Delimited:
|
||||
# 12345678.to_s(:delimited) # => "12,345,678"
|
||||
# 12345678.05.to_s(:delimited) # => "12,345,678.05"
|
||||
# 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678"
|
||||
# 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678"
|
||||
# 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05"
|
||||
# 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05"
|
||||
# 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
|
||||
# # => "98 765 432,98"
|
||||
#
|
||||
# Rounded:
|
||||
# 111.2345.to_s(:rounded) # => "111.235"
|
||||
# 111.2345.to_s(:rounded, precision: 2) # => "111.23"
|
||||
# 13.to_s(:rounded, precision: 5) # => "13.00000"
|
||||
# 389.32314.to_s(:rounded, precision: 0) # => "389"
|
||||
# 111.2345.to_s(:rounded, significant: true) # => "111"
|
||||
# 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100"
|
||||
# 13.to_s(:rounded, precision: 5, significant: true) # => "13.000"
|
||||
# 111.234.to_s(:rounded, locale: :fr) # => "111,234"
|
||||
# 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
|
||||
# # => "13"
|
||||
# 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3"
|
||||
# 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
|
||||
# # => "1.111,23"
|
||||
#
|
||||
# Human-friendly size in Bytes:
|
||||
# 123.to_s(:human_size) # => "123 Bytes"
|
||||
# 1234.to_s(:human_size) # => "1.21 KB"
|
||||
# 12345.to_s(:human_size) # => "12.1 KB"
|
||||
# 1234567.to_s(:human_size) # => "1.18 MB"
|
||||
# 1234567890.to_s(:human_size) # => "1.15 GB"
|
||||
# 1234567890123.to_s(:human_size) # => "1.12 TB"
|
||||
# 1234567890123456.to_s(:human_size) # => "1.1 PB"
|
||||
# 1234567890123456789.to_s(:human_size) # => "1.07 EB"
|
||||
# 1234567.to_s(:human_size, precision: 2) # => "1.2 MB"
|
||||
# 483989.to_s(:human_size, precision: 2) # => "470 KB"
|
||||
# 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB"
|
||||
# 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
|
||||
# 524288000.to_s(:human_size, precision: 5) # => "500 MB"
|
||||
#
|
||||
# Human-friendly format:
|
||||
# 123.to_s(:human) # => "123"
|
||||
# 1234.to_s(:human) # => "1.23 Thousand"
|
||||
# 12345.to_s(:human) # => "12.3 Thousand"
|
||||
# 1234567.to_s(:human) # => "1.23 Million"
|
||||
# 1234567890.to_s(:human) # => "1.23 Billion"
|
||||
# 1234567890123.to_s(:human) # => "1.23 Trillion"
|
||||
# 1234567890123456.to_s(:human) # => "1.23 Quadrillion"
|
||||
# 1234567890123456789.to_s(:human) # => "1230 Quadrillion"
|
||||
# 489939.to_s(:human, precision: 2) # => "490 Thousand"
|
||||
# 489939.to_s(:human, precision: 4) # => "489.9 Thousand"
|
||||
# 1234567.to_s(:human, precision: 4,
|
||||
# significant: false) # => "1.2346 Million"
|
||||
# 1234567.to_s(:human, precision: 1,
|
||||
# separator: ',',
|
||||
# significant: false) # => "1,2 Million"
|
||||
def to_s(format = nil, options = nil)
|
||||
case format
|
||||
when nil
|
||||
super()
|
||||
when Integer, String
|
||||
super(format)
|
||||
when :phone
|
||||
ActiveSupport::NumberHelper.number_to_phone(self, options || {})
|
||||
when :currency
|
||||
ActiveSupport::NumberHelper.number_to_currency(self, options || {})
|
||||
when :percentage
|
||||
ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
|
||||
when :delimited
|
||||
ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
|
||||
when :rounded
|
||||
ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
|
||||
when :human
|
||||
ActiveSupport::NumberHelper.number_to_human(self, options || {})
|
||||
when :human_size
|
||||
ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
|
||||
when Symbol
|
||||
super()
|
||||
else
|
||||
super(format)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ruby 2.4+ unifies Fixnum & Bignum into Integer.
|
||||
if 0.class == Integer
|
||||
Integer.prepend ActiveSupport::NumericWithFormat
|
||||
else
|
||||
Fixnum.prepend ActiveSupport::NumericWithFormat
|
||||
Bignum.prepend ActiveSupport::NumericWithFormat
|
||||
end
|
||||
Float.prepend ActiveSupport::NumericWithFormat
|
||||
BigDecimal.prepend ActiveSupport::NumericWithFormat
|
||||
@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
unless 1.respond_to?(:positive?) # TODO: Remove this file when we drop support to ruby < 2.3
|
||||
class Numeric
|
||||
# Returns true if the number is positive.
|
||||
#
|
||||
# 1.positive? # => true
|
||||
# 0.positive? # => false
|
||||
# -1.positive? # => false
|
||||
def positive?
|
||||
self > 0
|
||||
end
|
||||
|
||||
# Returns true if the number is negative.
|
||||
#
|
||||
# -1.negative? # => true
|
||||
# 0.negative? # => false
|
||||
# 1.negative? # => false
|
||||
def negative?
|
||||
self < 0
|
||||
end
|
||||
end
|
||||
|
||||
class Complex
|
||||
undef :positive?
|
||||
undef :negative?
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,66 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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
|
||||
# Returns a Duration instance matching the number of seconds provided.
|
||||
#
|
||||
# 2.seconds # => 2 seconds
|
||||
def seconds
|
||||
ActiveSupport::Duration.seconds(self)
|
||||
end
|
||||
alias :second :seconds
|
||||
|
||||
# Returns a Duration instance matching the number of minutes provided.
|
||||
#
|
||||
# 2.minutes # => 2 minutes
|
||||
def minutes
|
||||
ActiveSupport::Duration.minutes(self)
|
||||
end
|
||||
alias :minute :minutes
|
||||
|
||||
# Returns a Duration instance matching the number of hours provided.
|
||||
#
|
||||
# 2.hours # => 2 hours
|
||||
def hours
|
||||
ActiveSupport::Duration.hours(self)
|
||||
end
|
||||
alias :hour :hours
|
||||
|
||||
# Returns a Duration instance matching the number of days provided.
|
||||
#
|
||||
# 2.days # => 2 days
|
||||
def days
|
||||
ActiveSupport::Duration.days(self)
|
||||
end
|
||||
alias :day :days
|
||||
|
||||
# Returns a Duration instance matching the number of weeks provided.
|
||||
#
|
||||
# 2.weeks # => 2 weeks
|
||||
def weeks
|
||||
ActiveSupport::Duration.weeks(self)
|
||||
end
|
||||
alias :week :weeks
|
||||
|
||||
# Returns a Duration instance matching the number of fortnights provided.
|
||||
#
|
||||
# 2.fortnights # => 4 weeks
|
||||
def fortnights
|
||||
ActiveSupport::Duration.weeks(self * 2)
|
||||
end
|
||||
alias :fortnight :fortnights
|
||||
|
||||
# Returns the number of milliseconds equivalent to the seconds provided.
|
||||
# Used with the standard time durations.
|
||||
#
|
||||
# 2.in_milliseconds # => 2000
|
||||
# 1.hour.in_milliseconds # => 3600000
|
||||
def in_milliseconds
|
||||
self * 1000
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/object/acts_like"
|
||||
require "active_support/core_ext/object/blank"
|
||||
require "active_support/core_ext/object/duplicable"
|
||||
require "active_support/core_ext/object/deep_dup"
|
||||
require "active_support/core_ext/object/try"
|
||||
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/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"
|
||||
@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Object
|
||||
# A duck-type assistant method. For example, Active Support extends Date
|
||||
# to define an <tt>acts_like_date?</tt> method, and extends Time to define
|
||||
# <tt>acts_like_time?</tt>. As a result, we can do <tt>x.acts_like?(:time)</tt> and
|
||||
# <tt>x.acts_like?(:date)</tt> to do duck-type-safe comparisons, since classes that
|
||||
# we want to act like Time simply need to define an <tt>acts_like_time?</tt> method.
|
||||
def acts_like?(duck)
|
||||
case duck
|
||||
when :time
|
||||
respond_to? :acts_like_time?
|
||||
when :date
|
||||
respond_to? :acts_like_date?
|
||||
when :string
|
||||
respond_to? :acts_like_string?
|
||||
else
|
||||
respond_to? :"acts_like_#{duck}?"
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/object/to_param"
|
||||
require "active_support/core_ext/object/to_query"
|
||||
require "active_support/core_ext/array/conversions"
|
||||
require "active_support/core_ext/hash/conversions"
|
||||
@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/object/duplicable"
|
||||
|
||||
class Object
|
||||
# Returns a deep copy of object if it's duplicable. If it's
|
||||
# not duplicable, returns +self+.
|
||||
#
|
||||
# object = Object.new
|
||||
# dup = object.deep_dup
|
||||
# dup.instance_variable_set(:@a, 1)
|
||||
#
|
||||
# object.instance_variable_defined?(:@a) # => false
|
||||
# dup.instance_variable_defined?(:@a) # => true
|
||||
def deep_dup
|
||||
duplicable? ? dup : self
|
||||
end
|
||||
end
|
||||
|
||||
class Array
|
||||
# Returns a deep copy of array.
|
||||
#
|
||||
# array = [1, [2, 3]]
|
||||
# dup = array.deep_dup
|
||||
# dup[1][2] = 4
|
||||
#
|
||||
# array[1][2] # => nil
|
||||
# dup[1][2] # => 4
|
||||
def deep_dup
|
||||
map(&:deep_dup)
|
||||
end
|
||||
end
|
||||
|
||||
class Hash
|
||||
# Returns a deep copy of hash.
|
||||
#
|
||||
# hash = { a: { b: 'b' } }
|
||||
# dup = hash.deep_dup
|
||||
# dup[:a][:c] = 'c'
|
||||
#
|
||||
# hash[:a][:c] # => nil
|
||||
# dup[:a][:c] # => "c"
|
||||
def deep_dup
|
||||
hash = dup
|
||||
each_pair do |key, value|
|
||||
if key.frozen? && ::String === key
|
||||
hash[key] = value.deep_dup
|
||||
else
|
||||
hash.delete(key)
|
||||
hash[key.deep_dup] = value.deep_dup
|
||||
end
|
||||
end
|
||||
hash
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,156 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#--
|
||||
# Most objects are cloneable, but not all. For example you can't dup methods:
|
||||
#
|
||||
# method(:puts).dup # => TypeError: allocator undefined for Method
|
||||
#
|
||||
# Classes may signal their instances are not duplicable removing +dup+/+clone+
|
||||
# or raising exceptions from them. So, to dup an arbitrary object you normally
|
||||
# use an optimistic approach and are ready to catch an exception, say:
|
||||
#
|
||||
# arbitrary_object.dup rescue object
|
||||
#
|
||||
# Rails dups objects in a few critical spots where they are not that arbitrary.
|
||||
# That rescue is very expensive (like 40 times slower than a predicate), and it
|
||||
# is often triggered.
|
||||
#
|
||||
# That's why we hardcode the following cases and check duplicable? instead of
|
||||
# using that rescue idiom.
|
||||
#++
|
||||
class Object
|
||||
# Can you safely dup this object?
|
||||
#
|
||||
# False for method objects;
|
||||
# true otherwise.
|
||||
def duplicable?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class NilClass
|
||||
begin
|
||||
nil.dup
|
||||
rescue TypeError
|
||||
|
||||
# +nil+ is not duplicable:
|
||||
#
|
||||
# nil.duplicable? # => false
|
||||
# nil.dup # => TypeError: can't dup NilClass
|
||||
def duplicable?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FalseClass
|
||||
begin
|
||||
false.dup
|
||||
rescue TypeError
|
||||
|
||||
# +false+ is not duplicable:
|
||||
#
|
||||
# false.duplicable? # => false
|
||||
# false.dup # => TypeError: can't dup FalseClass
|
||||
def duplicable?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TrueClass
|
||||
begin
|
||||
true.dup
|
||||
rescue TypeError
|
||||
|
||||
# +true+ is not duplicable:
|
||||
#
|
||||
# true.duplicable? # => false
|
||||
# true.dup # => TypeError: can't dup TrueClass
|
||||
def duplicable?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Symbol
|
||||
begin
|
||||
:symbol.dup # Ruby 2.4.x.
|
||||
"symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
|
||||
rescue TypeError
|
||||
|
||||
# Symbols are not duplicable:
|
||||
#
|
||||
# :my_symbol.duplicable? # => false
|
||||
# :my_symbol.dup # => TypeError: can't dup Symbol
|
||||
def duplicable?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Numeric
|
||||
begin
|
||||
1.dup
|
||||
rescue TypeError
|
||||
|
||||
# Numbers are not duplicable:
|
||||
#
|
||||
# 3.duplicable? # => false
|
||||
# 3.dup # => TypeError: can't dup Integer
|
||||
def duplicable?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require "bigdecimal"
|
||||
class BigDecimal
|
||||
# BigDecimals are duplicable:
|
||||
#
|
||||
# BigDecimal("1.2").duplicable? # => true
|
||||
# BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
|
||||
def duplicable?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class Method
|
||||
# Methods are not duplicable:
|
||||
#
|
||||
# method(:puts).duplicable? # => false
|
||||
# method(:puts).dup # => TypeError: allocator undefined for Method
|
||||
def duplicable?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class Complex
|
||||
begin
|
||||
Complex(1).dup
|
||||
rescue TypeError
|
||||
|
||||
# Complexes are not duplicable:
|
||||
#
|
||||
# Complex(1).duplicable? # => false
|
||||
# Complex(1).dup # => TypeError: can't copy Complex
|
||||
def duplicable?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Rational
|
||||
begin
|
||||
Rational(1).dup
|
||||
rescue TypeError
|
||||
|
||||
# Rationals are not duplicable:
|
||||
#
|
||||
# Rational(1).duplicable? # => false
|
||||
# Rational(1).dup # => TypeError: can't copy Rational
|
||||
def duplicable?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Object
|
||||
# Returns true if this object is included in the argument. Argument must be
|
||||
# any object which responds to +#include?+. Usage:
|
||||
#
|
||||
# characters = ["Konata", "Kagami", "Tsukasa"]
|
||||
# "Konata".in?(characters) # => true
|
||||
#
|
||||
# This will throw an +ArgumentError+ if the argument doesn't respond
|
||||
# to +#include?+.
|
||||
def in?(another_object)
|
||||
another_object.include?(self)
|
||||
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)
|
||||
in?(another_object) ? self : nil
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Object
|
||||
# Returns a hash with string keys that maps instance variable names without "@" to their
|
||||
# corresponding values.
|
||||
#
|
||||
# class C
|
||||
# def initialize(x, y)
|
||||
# @x, @y = x, y
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
|
||||
def instance_values
|
||||
Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
|
||||
end
|
||||
|
||||
# Returns an array of instance variable names as strings including "@".
|
||||
#
|
||||
# class C
|
||||
# def initialize(x, y)
|
||||
# @x, @y = x, y
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
|
||||
def instance_variable_names
|
||||
instance_variables.map(&:to_s)
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,227 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Hack to load json gem first so we can overwrite its to_json.
|
||||
require "json"
|
||||
require "bigdecimal"
|
||||
require "uri/generic"
|
||||
require "pathname"
|
||||
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"
|
||||
|
||||
# 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.
|
||||
|
||||
module ActiveSupport
|
||||
module ToJsonWithActiveSupportEncoder # :nodoc:
|
||||
def to_json(options = nil)
|
||||
if options.is_a?(::JSON::State)
|
||||
# Called from JSON.{generate,dump}, forward it to JSON gem's to_json
|
||||
super(options)
|
||||
else
|
||||
# to_json is being invoked directly, use ActiveSupport's encoder
|
||||
ActiveSupport::JSON.encode(self, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].reverse_each do |klass|
|
||||
klass.prepend(ActiveSupport::ToJsonWithActiveSupportEncoder)
|
||||
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 IO
|
||||
def as_json(options = nil) #:nodoc:
|
||||
to_s
|
||||
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 URI::Generic #:nodoc:
|
||||
def as_json(options = nil)
|
||||
to_s
|
||||
end
|
||||
end
|
||||
|
||||
class Pathname #:nodoc:
|
||||
def as_json(options = nil)
|
||||
to_s
|
||||
end
|
||||
end
|
||||
|
||||
class Process::Status #:nodoc:
|
||||
def as_json(options = nil)
|
||||
{ exitstatus: exitstatus, pid: pid }
|
||||
end
|
||||
end
|
||||
|
||||
class Exception
|
||||
def as_json(options = nil)
|
||||
to_s
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/object/to_query"
|
||||
@ -0,0 +1,89 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cgi"
|
||||
|
||||
class Object
|
||||
# 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)
|
||||
"#{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}[]"
|
||||
|
||||
if empty?
|
||||
nil.to_query(prefix)
|
||||
else
|
||||
collect { |value| value.to_query(prefix) }.join "&"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Hash
|
||||
# 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)
|
||||
query = 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
|
||||
|
||||
query.sort! unless namespace.to_s.include?("[]")
|
||||
query.join("&")
|
||||
end
|
||||
|
||||
alias_method :to_param, :to_query
|
||||
end
|
||||
@ -0,0 +1,148 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "delegate"
|
||||
|
||||
module ActiveSupport
|
||||
module Tryable #:nodoc:
|
||||
def try(*a, &b)
|
||||
try!(*a, &b) if a.empty? || respond_to?(a.first)
|
||||
end
|
||||
|
||||
def try!(*a, &b)
|
||||
if a.empty? && block_given?
|
||||
if b.arity == 0
|
||||
instance_eval(&b)
|
||||
else
|
||||
yield self
|
||||
end
|
||||
else
|
||||
public_send(*a, &b)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
include ActiveSupport::Tryable
|
||||
|
||||
##
|
||||
# :method: try
|
||||
#
|
||||
# :call-seq:
|
||||
# try(*a, &b)
|
||||
#
|
||||
# Invokes the public method whose name goes as first argument just like
|
||||
# +public_send+ does, except that if the receiver does not respond to it the
|
||||
# call returns +nil+ rather than raising an exception.
|
||||
#
|
||||
# This method is defined to be able to write
|
||||
#
|
||||
# @person.try(:name)
|
||||
#
|
||||
# instead of
|
||||
#
|
||||
# @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:
|
||||
#
|
||||
# nil.try(:to_i) # => nil, rather than 0
|
||||
#
|
||||
# Arguments and blocks are forwarded to the method if invoked:
|
||||
#
|
||||
# @posts.try(:each_slice, 2) do |a, b|
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
# 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
|
||||
# in case of argument mismatch.
|
||||
#
|
||||
# If +try+ is called without arguments it yields the receiver to a given
|
||||
# block unless it is +nil+:
|
||||
#
|
||||
# @person.try do |p|
|
||||
# ...
|
||||
# end
|
||||
#
|
||||
# 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+.
|
||||
|
||||
##
|
||||
# :method: try!
|
||||
#
|
||||
# :call-seq:
|
||||
# try!(*a, &b)
|
||||
#
|
||||
# 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:Integer
|
||||
end
|
||||
|
||||
class Delegator
|
||||
include ActiveSupport::Tryable
|
||||
|
||||
##
|
||||
# :method: try
|
||||
#
|
||||
# :call-seq:
|
||||
# try(a*, &b)
|
||||
#
|
||||
# See Object#try
|
||||
|
||||
##
|
||||
# :method: try!
|
||||
#
|
||||
# :call-seq:
|
||||
# try!(a*, &b)
|
||||
#
|
||||
# See Object#try!
|
||||
end
|
||||
|
||||
class NilClass
|
||||
# Calling +try+ on +nil+ always returns +nil+.
|
||||
# It becomes especially helpful when navigating through associations that may return +nil+.
|
||||
#
|
||||
# nil.try(:name) # => nil
|
||||
#
|
||||
# Without +try+
|
||||
# @person && @person.children.any? && @person.children.first.name
|
||||
#
|
||||
# With +try+
|
||||
# @person.try(:children).try(:first).try(:name)
|
||||
def try(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
# Calling +try!+ on +nil+ always returns +nil+.
|
||||
#
|
||||
# nil.try!(:name) # => nil
|
||||
def try!(*args)
|
||||
nil
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,82 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/option_merger"
|
||||
|
||||
class Object
|
||||
# An elegant way to factor duplication out of options passed to a series of
|
||||
# method calls. Each method called in the block, with the block variable as
|
||||
# the receiver, will have its options merged with the default +options+ hash
|
||||
# provided. Each method called on the block variable must take an options
|
||||
# hash as its final argument.
|
||||
#
|
||||
# Without <tt>with_options</tt>, this code contains duplication:
|
||||
#
|
||||
# class Account < ActiveRecord::Base
|
||||
# has_many :customers, dependent: :destroy
|
||||
# has_many :products, dependent: :destroy
|
||||
# has_many :invoices, dependent: :destroy
|
||||
# has_many :expenses, dependent: :destroy
|
||||
# end
|
||||
#
|
||||
# Using <tt>with_options</tt>, we can remove the duplication:
|
||||
#
|
||||
# class Account < ActiveRecord::Base
|
||||
# with_options dependent: :destroy do |assoc|
|
||||
# assoc.has_many :customers
|
||||
# assoc.has_many :products
|
||||
# assoc.has_many :invoices
|
||||
# assoc.has_many :expenses
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# It can also be used with an explicit receiver:
|
||||
#
|
||||
# I18n.with_options locale: user.locale, scope: 'newsletter' do |i18n|
|
||||
# subject i18n.t :subject
|
||||
# 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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# NOTE: You cannot call class methods implicitly inside of with_options.
|
||||
# You can access these methods using the class name instead:
|
||||
#
|
||||
# class Phone < ActiveRecord::Base
|
||||
# enum phone_number_type: [home: 0, office: 1, mobile: 2]
|
||||
#
|
||||
# with_options presence: true do
|
||||
# validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
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
|
||||
@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/range/conversions"
|
||||
require "active_support/core_ext/range/include_range"
|
||||
require "active_support/core_ext/range/include_time_with_zone"
|
||||
require "active_support/core_ext/range/overlaps"
|
||||
require "active_support/core_ext/range/each"
|
||||
@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActiveSupport::RangeWithFormat
|
||||
RANGE_FORMATS = {
|
||||
db: -> (start, stop) do
|
||||
case start
|
||||
when String then "BETWEEN '#{start}' AND '#{stop}'"
|
||||
else
|
||||
"BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'"
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
# Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
|
||||
#
|
||||
# range = (1..100) # => 1..100
|
||||
#
|
||||
# range.to_s # => "1..100"
|
||||
# range.to_s(:db) # => "BETWEEN '1' AND '100'"
|
||||
#
|
||||
# == Adding your own range formats to to_s
|
||||
# You can add your own formats to the Range::RANGE_FORMATS hash.
|
||||
# Use the format name as the hash key and a Proc instance.
|
||||
#
|
||||
# # config/initializers/range_formats.rb
|
||||
# Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
|
||||
def to_s(format = :default)
|
||||
if formatter = RANGE_FORMATS[format]
|
||||
formatter.call(first, last)
|
||||
else
|
||||
super()
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :to_default_s, :to_s
|
||||
alias_method :to_formatted_s, :to_s
|
||||
end
|
||||
|
||||
Range.prepend(ActiveSupport::RangeWithFormat)
|
||||
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/time_with_zone"
|
||||
|
||||
module ActiveSupport
|
||||
module EachTimeWithZone #:nodoc:
|
||||
def each(&block)
|
||||
ensure_iteration_allowed
|
||||
super
|
||||
end
|
||||
|
||||
def step(n = 1, &block)
|
||||
ensure_iteration_allowed
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_iteration_allowed
|
||||
raise TypeError, "can't iterate from #{first.class}" if first.is_a?(TimeWithZone)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Range.prepend(ActiveSupport::EachTimeWithZone)
|
||||
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActiveSupport
|
||||
module IncludeWithRange #:nodoc:
|
||||
# Extends the default Range#include? to support range comparisons.
|
||||
# (1..5).include?(1..5) # => true
|
||||
# (1..5).include?(2..3) # => true
|
||||
# (1..5).include?(2..6) # => false
|
||||
#
|
||||
# The native Range#include? behavior is untouched.
|
||||
# ('a'..'f').include?('c') # => true
|
||||
# (5..9).include?(11) # => false
|
||||
def include?(value)
|
||||
if value.is_a?(::Range)
|
||||
# 1...10 includes 1..9 but it does not include 1..10.
|
||||
operator = exclude_end? && !value.exclude_end? ? :< : :<=
|
||||
super(value.first) && value.last.send(operator, last)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Range.prepend(ActiveSupport::IncludeWithRange)
|
||||
@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/time_with_zone"
|
||||
|
||||
module ActiveSupport
|
||||
module IncludeTimeWithZone #:nodoc:
|
||||
# Extends the default Range#include? to support ActiveSupport::TimeWithZone.
|
||||
#
|
||||
# (1.hour.ago..1.hour.from_now).include?(Time.current) # => true
|
||||
#
|
||||
def include?(value)
|
||||
if first.is_a?(TimeWithZone)
|
||||
cover?(value)
|
||||
elsif last.is_a?(TimeWithZone)
|
||||
cover?(value)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Range.prepend(ActiveSupport::IncludeTimeWithZone)
|
||||
@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Range
|
||||
# Compare two ranges and see if they overlap each other
|
||||
# (1..5).overlaps?(4..6) # => true
|
||||
# (1..5).overlaps?(7..9) # => false
|
||||
def overlaps?(other)
|
||||
cover?(other.first) || other.cover?(first)
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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
|
||||
@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/string/conversions"
|
||||
require "active_support/core_ext/string/filters"
|
||||
require "active_support/core_ext/string/multibyte"
|
||||
require "active_support/core_ext/string/starts_ends_with"
|
||||
require "active_support/core_ext/string/inflections"
|
||||
require "active_support/core_ext/string/access"
|
||||
require "active_support/core_ext/string/behavior"
|
||||
require "active_support/core_ext/string/output_safety"
|
||||
require "active_support/core_ext/string/exclude"
|
||||
require "active_support/core_ext/string/strip"
|
||||
require "active_support/core_ext/string/inquiry"
|
||||
require "active_support/core_ext/string/indent"
|
||||
require "active_support/core_ext/string/zones"
|
||||
@ -0,0 +1,106 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class String
|
||||
# If you pass a single integer, returns a substring of one character at that
|
||||
# position. The first character of the string is at position 0, the next at
|
||||
# position 1, and so on. If a range is supplied, a substring containing
|
||||
# characters at offsets given by the range is returned. In both cases, if an
|
||||
# offset is negative, it is counted from the end of the string. Returns +nil+
|
||||
# if the initial offset falls outside the string. Returns an empty string if
|
||||
# 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) # => ""
|
||||
#
|
||||
# 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
|
||||
def at(position)
|
||||
self[position]
|
||||
end
|
||||
|
||||
# Returns a substring from the given position to the end of the 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"
|
||||
#
|
||||
# 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"
|
||||
def from(position)
|
||||
self[position..-1]
|
||||
end
|
||||
|
||||
# Returns a substring from the beginning of the string to the given position.
|
||||
# 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"
|
||||
#
|
||||
# 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"
|
||||
def to(position)
|
||||
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 a copy of self.
|
||||
#
|
||||
# str = "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
|
||||
dup
|
||||
else
|
||||
to(limit - 1)
|
||||
end
|
||||
end
|
||||
|
||||
# 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 a copy of self.
|
||||
#
|
||||
# str = "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
|
||||
dup
|
||||
else
|
||||
from(-limit)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class String
|
||||
# Enables more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>.
|
||||
def acts_like_string?
|
||||
true
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,59 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "date"
|
||||
require "active_support/core_ext/time/calculations"
|
||||
|
||||
class String
|
||||
# Converts a string to a Time value.
|
||||
# The +form+ can be either :utc or :local (default :local).
|
||||
#
|
||||
# The time is parsed using Time.parse method.
|
||||
# If +form+ is :local, then the time is in the system timezone.
|
||||
# If the date part is missing then the current date is used and if
|
||||
# the time part is missing then it is assumed to be 00:00:00.
|
||||
#
|
||||
# "13-12-2012".to_time # => 2012-12-13 00:00:00 +0100
|
||||
# "06:12".to_time # => 2012-12-13 06:12:00 +0100
|
||||
# "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100
|
||||
# "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100
|
||||
# "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 06:12:00 UTC
|
||||
# "12/13/2012".to_time # => ArgumentError: argument out of range
|
||||
def to_time(form = :local)
|
||||
parts = Date._parse(self, false)
|
||||
used_keys = %i(year mon mday hour min sec sec_fraction offset)
|
||||
return if (parts.keys & used_keys).empty?
|
||||
|
||||
now = Time.now
|
||||
time = Time.new(
|
||||
parts.fetch(:year, now.year),
|
||||
parts.fetch(:mon, now.month),
|
||||
parts.fetch(:mday, now.day),
|
||||
parts.fetch(:hour, 0),
|
||||
parts.fetch(:min, 0),
|
||||
parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
|
||||
parts.fetch(:offset, form == :utc ? 0 : nil)
|
||||
)
|
||||
|
||||
form == :utc ? time.utc : time.to_time
|
||||
end
|
||||
|
||||
# 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
|
||||
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
|
||||
def to_datetime
|
||||
::DateTime.parse(self, false) unless blank?
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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
|
||||
def exclude?(string)
|
||||
!include?(string)
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user