Remove unused parts of activesupport
This commit is contained in:
parent
3b92bb9da5
commit
6935b84584
@ -1,48 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
# Actionable errors let's you define actions to resolve an error.
|
|
||||||
#
|
|
||||||
# To make an error actionable, include the <tt>ActiveSupport::ActionableError</tt>
|
|
||||||
# module and invoke the +action+ class macro to define the action. An action
|
|
||||||
# needs a name and a block to execute.
|
|
||||||
module ActionableError
|
|
||||||
extend Concern
|
|
||||||
|
|
||||||
class NonActionable < StandardError; end
|
|
||||||
|
|
||||||
included do
|
|
||||||
class_attribute :_actions, default: {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.actions(error) # :nodoc:
|
|
||||||
case error
|
|
||||||
when ActionableError, -> it { Class === it && it < ActionableError }
|
|
||||||
error._actions
|
|
||||||
else
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.dispatch(error, name) # :nodoc:
|
|
||||||
actions(error).fetch(name).call
|
|
||||||
rescue KeyError
|
|
||||||
raise NonActionable, "Cannot find action \"#{name}\""
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
# Defines an action that can resolve the error.
|
|
||||||
#
|
|
||||||
# class PendingMigrationError < MigrationError
|
|
||||||
# include ActiveSupport::ActionableError
|
|
||||||
#
|
|
||||||
# action "Run pending migrations" do
|
|
||||||
# ActiveRecord::Tasks::DatabaseTasks.migrate
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
def action(name, &block)
|
|
||||||
_actions[name] = block
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/symbol/starts_ends_with"
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
# Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check
|
|
||||||
# its string-like contents:
|
|
||||||
#
|
|
||||||
# variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
|
|
||||||
#
|
|
||||||
# variants.phone? # => true
|
|
||||||
# variants.tablet? # => true
|
|
||||||
# variants.desktop? # => false
|
|
||||||
class ArrayInquirer < Array
|
|
||||||
# Passes each element of +candidates+ collection to ArrayInquirer collection.
|
|
||||||
# The method returns true if any element from the ArrayInquirer collection
|
|
||||||
# is equal to the stringified or symbolized form of any element in the +candidates+ collection.
|
|
||||||
#
|
|
||||||
# If +candidates+ collection is not given, method returns true.
|
|
||||||
#
|
|
||||||
# variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
|
|
||||||
#
|
|
||||||
# variants.any? # => true
|
|
||||||
# variants.any?(:phone, :tablet) # => true
|
|
||||||
# variants.any?('phone', 'desktop') # => true
|
|
||||||
# variants.any?(:desktop, :watch) # => false
|
|
||||||
def any?(*candidates)
|
|
||||||
if candidates.none?
|
|
||||||
super
|
|
||||||
else
|
|
||||||
candidates.any? do |candidate|
|
|
||||||
include?(candidate.to_sym) || include?(candidate.to_s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def respond_to_missing?(name, include_private = false)
|
|
||||||
name.end_with?("?") || super
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_missing(name, *args)
|
|
||||||
if name.end_with?("?")
|
|
||||||
any?(name[0..-2])
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
# Backtraces often include many lines that are not relevant for the context
|
|
||||||
# under review. This makes it hard to find the signal amongst the backtrace
|
|
||||||
# noise, and adds debugging time. With a BacktraceCleaner, filters and
|
|
||||||
# silencers are used to remove the noisy lines, so that only the most relevant
|
|
||||||
# lines remain.
|
|
||||||
#
|
|
||||||
# Filters are used to modify lines of data, while silencers are used to remove
|
|
||||||
# lines entirely. The typical filter use case is to remove lengthy path
|
|
||||||
# information from the start of each line, and view file paths relevant to the
|
|
||||||
# app directory instead of the file system root. The typical silencer use case
|
|
||||||
# is to exclude the output of a noisy library from the backtrace, so that you
|
|
||||||
# can focus on the rest.
|
|
||||||
#
|
|
||||||
# bc = ActiveSupport::BacktraceCleaner.new
|
|
||||||
# bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
|
|
||||||
# bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
|
|
||||||
# bc.clean(exception.backtrace) # perform the cleanup
|
|
||||||
#
|
|
||||||
# To reconfigure an existing BacktraceCleaner (like the default one in Rails)
|
|
||||||
# and show as much data as possible, you can always call
|
|
||||||
# <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
|
|
||||||
# backtrace to a pristine state. If you need to reconfigure an existing
|
|
||||||
# BacktraceCleaner so that it does not filter or modify the paths of any lines
|
|
||||||
# of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
|
|
||||||
# These two methods will give you a completely untouched backtrace.
|
|
||||||
#
|
|
||||||
# Inspired by the Quiet Backtrace gem by thoughtbot.
|
|
||||||
class BacktraceCleaner
|
|
||||||
def initialize
|
|
||||||
@filters, @silencers = [], []
|
|
||||||
add_gem_filter
|
|
||||||
add_gem_silencer
|
|
||||||
add_stdlib_silencer
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the backtrace after all filters and silencers have been run
|
|
||||||
# against it. Filters run first, then silencers.
|
|
||||||
def clean(backtrace, kind = :silent)
|
|
||||||
filtered = filter_backtrace(backtrace)
|
|
||||||
|
|
||||||
case kind
|
|
||||||
when :silent
|
|
||||||
silence(filtered)
|
|
||||||
when :noise
|
|
||||||
noise(filtered)
|
|
||||||
else
|
|
||||||
filtered
|
|
||||||
end
|
|
||||||
end
|
|
||||||
alias :filter :clean
|
|
||||||
|
|
||||||
# Adds a filter from the block provided. Each line in the backtrace will be
|
|
||||||
# mapped against this filter.
|
|
||||||
#
|
|
||||||
# # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb"
|
|
||||||
# backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') }
|
|
||||||
def add_filter(&block)
|
|
||||||
@filters << block
|
|
||||||
end
|
|
||||||
|
|
||||||
# Adds a silencer from the block provided. If the silencer returns +true+
|
|
||||||
# for a given line, it will be excluded from the clean backtrace.
|
|
||||||
#
|
|
||||||
# # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb"
|
|
||||||
# backtrace_cleaner.add_silencer { |line| /puma/.match?(line) }
|
|
||||||
def add_silencer(&block)
|
|
||||||
@silencers << block
|
|
||||||
end
|
|
||||||
|
|
||||||
# Removes all silencers, but leaves in the filters. Useful if your
|
|
||||||
# context of debugging suddenly expands as you suspect a bug in one of
|
|
||||||
# the libraries you use.
|
|
||||||
def remove_silencers!
|
|
||||||
@silencers = []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Removes all filters, but leaves in the silencers. Useful if you suddenly
|
|
||||||
# need to see entire filepaths in the backtrace that you had already
|
|
||||||
# filtered out.
|
|
||||||
def remove_filters!
|
|
||||||
@filters = []
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) /
|
|
||||||
|
|
||||||
def add_gem_filter
|
|
||||||
gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
|
|
||||||
return if gems_paths.empty?
|
|
||||||
|
|
||||||
gems_regexp = %r{\A(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)}
|
|
||||||
gems_result = '\3 (\4) \5'
|
|
||||||
add_filter { |line| line.sub(gems_regexp, gems_result) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_gem_silencer
|
|
||||||
add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_stdlib_silencer
|
|
||||||
add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def filter_backtrace(backtrace)
|
|
||||||
@filters.each do |f|
|
|
||||||
backtrace = backtrace.map { |line| f.call(line) }
|
|
||||||
end
|
|
||||||
|
|
||||||
backtrace
|
|
||||||
end
|
|
||||||
|
|
||||||
def silence(backtrace)
|
|
||||||
@silencers.each do |s|
|
|
||||||
backtrace = backtrace.reject { |line| s.call(line) }
|
|
||||||
end
|
|
||||||
|
|
||||||
backtrace
|
|
||||||
end
|
|
||||||
|
|
||||||
def noise(backtrace)
|
|
||||||
backtrace.select do |line|
|
|
||||||
@silencers.any? do |s|
|
|
||||||
s.call(line)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/benchmark"
|
|
||||||
require "active_support/core_ext/hash/keys"
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
module Benchmarkable
|
|
||||||
# Allows you to measure the execution time of a block in a template and
|
|
||||||
# records the result to the log. Wrap this block around expensive operations
|
|
||||||
# or possible bottlenecks to get a time reading for the operation. For
|
|
||||||
# example, let's say you thought your file processing method was taking too
|
|
||||||
# long; you could wrap it in a benchmark block.
|
|
||||||
#
|
|
||||||
# <% benchmark 'Process data files' do %>
|
|
||||||
# <%= expensive_files_operation %>
|
|
||||||
# <% end %>
|
|
||||||
#
|
|
||||||
# That would add something like "Process data files (345.2ms)" to the log,
|
|
||||||
# which you can then use to compare timings when optimizing your code.
|
|
||||||
#
|
|
||||||
# You may give an optional logger level (<tt>:debug</tt>, <tt>:info</tt>,
|
|
||||||
# <tt>:warn</tt>, <tt>:error</tt>) as the <tt>:level</tt> option. The
|
|
||||||
# default logger level value is <tt>:info</tt>.
|
|
||||||
#
|
|
||||||
# <% benchmark 'Low-level files', level: :debug do %>
|
|
||||||
# <%= lowlevel_files_operation %>
|
|
||||||
# <% end %>
|
|
||||||
#
|
|
||||||
# Finally, you can pass true as the third argument to silence all log
|
|
||||||
# activity (other than the timing information) from inside the block. This
|
|
||||||
# is great for boiling down a noisy block to just a single statement that
|
|
||||||
# produces one log line:
|
|
||||||
#
|
|
||||||
# <% benchmark 'Process data files', level: :info, silence: true do %>
|
|
||||||
# <%= expensive_and_chatty_files_operation %>
|
|
||||||
# <% end %>
|
|
||||||
def benchmark(message = "Benchmarking", options = {})
|
|
||||||
if logger
|
|
||||||
options.assert_valid_keys(:level, :silence)
|
|
||||||
options[:level] ||= :info
|
|
||||||
|
|
||||||
result = nil
|
|
||||||
ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
|
|
||||||
logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ])
|
|
||||||
result
|
|
||||||
else
|
|
||||||
yield
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
begin
|
|
||||||
require "builder"
|
|
||||||
rescue LoadError => e
|
|
||||||
$stderr.puts "You don't have builder installed in your application. Please add it to your Gemfile and run bundle install"
|
|
||||||
raise e
|
|
||||||
end
|
|
||||||
@ -1,862 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/concern"
|
|
||||||
require "active_support/descendants_tracker"
|
|
||||||
require "active_support/core_ext/array/extract_options"
|
|
||||||
require "active_support/core_ext/class/attribute"
|
|
||||||
require "active_support/core_ext/string/filters"
|
|
||||||
require "thread"
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
# Callbacks are code hooks that are run at key points in an object's life cycle.
|
|
||||||
# The typical use case is to have a base class define a set of callbacks
|
|
||||||
# relevant to the other functionality it supplies, so that subclasses can
|
|
||||||
# install callbacks that enhance or modify the base functionality without
|
|
||||||
# needing to override or redefine methods of the base class.
|
|
||||||
#
|
|
||||||
# Mixing in this module allows you to define the events in the object's
|
|
||||||
# life cycle that will support callbacks (via +ClassMethods.define_callbacks+),
|
|
||||||
# set the instance methods, procs, or callback objects to be called (via
|
|
||||||
# +ClassMethods.set_callback+), and run the installed callbacks at the
|
|
||||||
# appropriate times (via +run_callbacks+).
|
|
||||||
#
|
|
||||||
# By default callbacks are halted by throwing +:abort+.
|
|
||||||
# See +ClassMethods.define_callbacks+ for details.
|
|
||||||
#
|
|
||||||
# Three kinds of callbacks are supported: before callbacks, run before a
|
|
||||||
# certain event; after callbacks, run after the event; and around callbacks,
|
|
||||||
# blocks that surround the event, triggering it when they yield. Callback code
|
|
||||||
# can be contained in instance methods, procs or lambdas, or callback objects
|
|
||||||
# that respond to certain predetermined methods. See +ClassMethods.set_callback+
|
|
||||||
# for details.
|
|
||||||
#
|
|
||||||
# class Record
|
|
||||||
# include ActiveSupport::Callbacks
|
|
||||||
# define_callbacks :save
|
|
||||||
#
|
|
||||||
# def save
|
|
||||||
# run_callbacks :save do
|
|
||||||
# puts "- save"
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class PersonRecord < Record
|
|
||||||
# set_callback :save, :before, :saving_message
|
|
||||||
# def saving_message
|
|
||||||
# puts "saving..."
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# set_callback :save, :after do |object|
|
|
||||||
# puts "saved"
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# person = PersonRecord.new
|
|
||||||
# person.save
|
|
||||||
#
|
|
||||||
# Output:
|
|
||||||
# saving...
|
|
||||||
# - save
|
|
||||||
# saved
|
|
||||||
module Callbacks
|
|
||||||
extend Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
extend ActiveSupport::DescendantsTracker
|
|
||||||
class_attribute :__callbacks, instance_writer: false, default: {}
|
|
||||||
end
|
|
||||||
|
|
||||||
CALLBACK_FILTER_TYPES = [:before, :after, :around]
|
|
||||||
|
|
||||||
# Runs the callbacks for the given event.
|
|
||||||
#
|
|
||||||
# Calls the before and around callbacks in the order they were set, yields
|
|
||||||
# the block (if given one), and then runs the after callbacks in reverse
|
|
||||||
# order.
|
|
||||||
#
|
|
||||||
# If the callback chain was halted, returns +false+. Otherwise returns the
|
|
||||||
# result of the block, +nil+ if no callbacks have been set, or +true+
|
|
||||||
# if callbacks have been set but no block is given.
|
|
||||||
#
|
|
||||||
# run_callbacks :save do
|
|
||||||
# save
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
#
|
|
||||||
# As this method is used in many places, and often wraps large portions of
|
|
||||||
# user code, it has an additional design goal of minimizing its impact on
|
|
||||||
# the visible call stack. An exception from inside a :before or :after
|
|
||||||
# callback can be as noisy as it likes -- but when control has passed
|
|
||||||
# smoothly through and into the supplied block, we want as little evidence
|
|
||||||
# as possible that we were here.
|
|
||||||
def run_callbacks(kind)
|
|
||||||
callbacks = __callbacks[kind.to_sym]
|
|
||||||
|
|
||||||
if callbacks.empty?
|
|
||||||
yield if block_given?
|
|
||||||
else
|
|
||||||
env = Filters::Environment.new(self, false, nil)
|
|
||||||
next_sequence = callbacks.compile
|
|
||||||
|
|
||||||
# Common case: no 'around' callbacks defined
|
|
||||||
if next_sequence.final?
|
|
||||||
next_sequence.invoke_before(env)
|
|
||||||
env.value = !env.halted && (!block_given? || yield)
|
|
||||||
next_sequence.invoke_after(env)
|
|
||||||
env.value
|
|
||||||
else
|
|
||||||
invoke_sequence = Proc.new do
|
|
||||||
skipped = nil
|
|
||||||
|
|
||||||
while true
|
|
||||||
current = next_sequence
|
|
||||||
current.invoke_before(env)
|
|
||||||
if current.final?
|
|
||||||
env.value = !env.halted && (!block_given? || yield)
|
|
||||||
elsif current.skip?(env)
|
|
||||||
(skipped ||= []) << current
|
|
||||||
next_sequence = next_sequence.nested
|
|
||||||
next
|
|
||||||
else
|
|
||||||
next_sequence = next_sequence.nested
|
|
||||||
begin
|
|
||||||
target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
|
|
||||||
target.send(method, *arguments, &block)
|
|
||||||
ensure
|
|
||||||
next_sequence = current
|
|
||||||
end
|
|
||||||
end
|
|
||||||
current.invoke_after(env)
|
|
||||||
skipped.pop.invoke_after(env) while skipped&.first
|
|
||||||
break env.value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
invoke_sequence.call
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
# A hook invoked every time a before callback is halted.
|
|
||||||
# This can be overridden in ActiveSupport::Callbacks implementors in order
|
|
||||||
# to provide better debugging/logging.
|
|
||||||
def halted_callback_hook(filter, name)
|
|
||||||
end
|
|
||||||
|
|
||||||
module Conditionals # :nodoc:
|
|
||||||
class Value
|
|
||||||
def initialize(&block)
|
|
||||||
@block = block
|
|
||||||
end
|
|
||||||
def call(target, value); @block.call(value); end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module Filters
|
|
||||||
Environment = Struct.new(:target, :halted, :value)
|
|
||||||
|
|
||||||
class Before
|
|
||||||
def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter, name)
|
|
||||||
halted_lambda = chain_config[:terminator]
|
|
||||||
|
|
||||||
if user_conditions.any?
|
|
||||||
halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name)
|
|
||||||
else
|
|
||||||
halting(callback_sequence, user_callback, halted_lambda, filter, name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter, name)
|
|
||||||
callback_sequence.before do |env|
|
|
||||||
target = env.target
|
|
||||||
value = env.value
|
|
||||||
halted = env.halted
|
|
||||||
|
|
||||||
if !halted && user_conditions.all? { |c| c.call(target, value) }
|
|
||||||
result_lambda = -> { user_callback.call target, value }
|
|
||||||
env.halted = halted_lambda.call(target, result_lambda)
|
|
||||||
if env.halted
|
|
||||||
target.send :halted_callback_hook, filter, name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
env
|
|
||||||
end
|
|
||||||
end
|
|
||||||
private_class_method :halting_and_conditional
|
|
||||||
|
|
||||||
def self.halting(callback_sequence, user_callback, halted_lambda, filter, name)
|
|
||||||
callback_sequence.before do |env|
|
|
||||||
target = env.target
|
|
||||||
value = env.value
|
|
||||||
halted = env.halted
|
|
||||||
|
|
||||||
unless halted
|
|
||||||
result_lambda = -> { user_callback.call target, value }
|
|
||||||
env.halted = halted_lambda.call(target, result_lambda)
|
|
||||||
if env.halted
|
|
||||||
target.send :halted_callback_hook, filter, name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
env
|
|
||||||
end
|
|
||||||
end
|
|
||||||
private_class_method :halting
|
|
||||||
end
|
|
||||||
|
|
||||||
class After
|
|
||||||
def self.build(callback_sequence, user_callback, user_conditions, chain_config)
|
|
||||||
if chain_config[:skip_after_callbacks_if_terminated]
|
|
||||||
if user_conditions.any?
|
|
||||||
halting_and_conditional(callback_sequence, user_callback, user_conditions)
|
|
||||||
else
|
|
||||||
halting(callback_sequence, user_callback)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if user_conditions.any?
|
|
||||||
conditional callback_sequence, user_callback, user_conditions
|
|
||||||
else
|
|
||||||
simple callback_sequence, user_callback
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
|
|
||||||
callback_sequence.after do |env|
|
|
||||||
target = env.target
|
|
||||||
value = env.value
|
|
||||||
halted = env.halted
|
|
||||||
|
|
||||||
if !halted && user_conditions.all? { |c| c.call(target, value) }
|
|
||||||
user_callback.call target, value
|
|
||||||
end
|
|
||||||
|
|
||||||
env
|
|
||||||
end
|
|
||||||
end
|
|
||||||
private_class_method :halting_and_conditional
|
|
||||||
|
|
||||||
def self.halting(callback_sequence, user_callback)
|
|
||||||
callback_sequence.after do |env|
|
|
||||||
unless env.halted
|
|
||||||
user_callback.call env.target, env.value
|
|
||||||
end
|
|
||||||
|
|
||||||
env
|
|
||||||
end
|
|
||||||
end
|
|
||||||
private_class_method :halting
|
|
||||||
|
|
||||||
def self.conditional(callback_sequence, user_callback, user_conditions)
|
|
||||||
callback_sequence.after do |env|
|
|
||||||
target = env.target
|
|
||||||
value = env.value
|
|
||||||
|
|
||||||
if user_conditions.all? { |c| c.call(target, value) }
|
|
||||||
user_callback.call target, value
|
|
||||||
end
|
|
||||||
|
|
||||||
env
|
|
||||||
end
|
|
||||||
end
|
|
||||||
private_class_method :conditional
|
|
||||||
|
|
||||||
def self.simple(callback_sequence, user_callback)
|
|
||||||
callback_sequence.after do |env|
|
|
||||||
user_callback.call env.target, env.value
|
|
||||||
|
|
||||||
env
|
|
||||||
end
|
|
||||||
end
|
|
||||||
private_class_method :simple
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Callback #:nodoc:#
|
|
||||||
def self.build(chain, filter, kind, options)
|
|
||||||
if filter.is_a?(String)
|
|
||||||
raise ArgumentError, <<-MSG.squish
|
|
||||||
Passing string to define a callback is not supported. See the `.set_callback`
|
|
||||||
documentation to see supported values.
|
|
||||||
MSG
|
|
||||||
end
|
|
||||||
|
|
||||||
new chain.name, filter, kind, options, chain.config
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_accessor :kind, :name
|
|
||||||
attr_reader :chain_config
|
|
||||||
|
|
||||||
def initialize(name, filter, kind, options, chain_config)
|
|
||||||
@chain_config = chain_config
|
|
||||||
@name = name
|
|
||||||
@kind = kind
|
|
||||||
@filter = filter
|
|
||||||
@key = compute_identifier filter
|
|
||||||
@if = check_conditionals(options[:if])
|
|
||||||
@unless = check_conditionals(options[:unless])
|
|
||||||
end
|
|
||||||
|
|
||||||
def filter; @key; end
|
|
||||||
def raw_filter; @filter; end
|
|
||||||
|
|
||||||
def merge_conditional_options(chain, if_option:, unless_option:)
|
|
||||||
options = {
|
|
||||||
if: @if.dup,
|
|
||||||
unless: @unless.dup
|
|
||||||
}
|
|
||||||
|
|
||||||
options[:if].concat Array(unless_option)
|
|
||||||
options[:unless].concat Array(if_option)
|
|
||||||
|
|
||||||
self.class.build chain, @filter, @kind, options
|
|
||||||
end
|
|
||||||
|
|
||||||
def matches?(_kind, _filter)
|
|
||||||
@kind == _kind && filter == _filter
|
|
||||||
end
|
|
||||||
|
|
||||||
def duplicates?(other)
|
|
||||||
case @filter
|
|
||||||
when Symbol
|
|
||||||
matches?(other.kind, other.filter)
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Wraps code with filter
|
|
||||||
def apply(callback_sequence)
|
|
||||||
user_conditions = conditions_lambdas
|
|
||||||
user_callback = CallTemplate.build(@filter, self)
|
|
||||||
|
|
||||||
case kind
|
|
||||||
when :before
|
|
||||||
Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter, name)
|
|
||||||
when :after
|
|
||||||
Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config)
|
|
||||||
when :around
|
|
||||||
callback_sequence.around(user_callback, user_conditions)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_scopes
|
|
||||||
Array(chain_config[:scope]).map { |s| public_send(s) }
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
EMPTY_ARRAY = [].freeze
|
|
||||||
private_constant :EMPTY_ARRAY
|
|
||||||
|
|
||||||
def check_conditionals(conditionals)
|
|
||||||
return EMPTY_ARRAY if conditionals.blank?
|
|
||||||
|
|
||||||
conditionals = Array(conditionals)
|
|
||||||
if conditionals.any? { |c| c.is_a?(String) }
|
|
||||||
raise ArgumentError, <<-MSG.squish
|
|
||||||
Passing string to be evaluated in :if and :unless conditional
|
|
||||||
options is not supported. Pass a symbol for an instance method,
|
|
||||||
or a lambda, proc or block, instead.
|
|
||||||
MSG
|
|
||||||
end
|
|
||||||
|
|
||||||
conditionals.freeze
|
|
||||||
end
|
|
||||||
|
|
||||||
def compute_identifier(filter)
|
|
||||||
case filter
|
|
||||||
when ::Proc
|
|
||||||
filter.object_id
|
|
||||||
else
|
|
||||||
filter
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def conditions_lambdas
|
|
||||||
@if.map { |c| CallTemplate.build(c, self).make_lambda } +
|
|
||||||
@unless.map { |c| CallTemplate.build(c, self).inverted_lambda }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# A future invocation of user-supplied code (either as a callback,
|
|
||||||
# or a condition filter).
|
|
||||||
class CallTemplate # :nodoc:
|
|
||||||
def initialize(target, method, arguments, block)
|
|
||||||
@override_target = target
|
|
||||||
@method_name = method
|
|
||||||
@arguments = arguments
|
|
||||||
@override_block = block
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the parts needed to make this call, with the given
|
|
||||||
# input values.
|
|
||||||
#
|
|
||||||
# Returns an array of the form:
|
|
||||||
#
|
|
||||||
# [target, block, method, *arguments]
|
|
||||||
#
|
|
||||||
# This array can be used as such:
|
|
||||||
#
|
|
||||||
# target.send(method, *arguments, &block)
|
|
||||||
#
|
|
||||||
# The actual invocation is left up to the caller to minimize
|
|
||||||
# call stack pollution.
|
|
||||||
def expand(target, value, block)
|
|
||||||
expanded = [@override_target || target, @override_block || block, @method_name]
|
|
||||||
|
|
||||||
@arguments.each do |arg|
|
|
||||||
case arg
|
|
||||||
when :value then expanded << value
|
|
||||||
when :target then expanded << target
|
|
||||||
when :block then expanded << (block || raise(ArgumentError))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
expanded
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return a lambda that will make this call when given the input
|
|
||||||
# values.
|
|
||||||
def make_lambda
|
|
||||||
lambda do |target, value, &block|
|
|
||||||
target, block, method, *arguments = expand(target, value, block)
|
|
||||||
target.send(method, *arguments, &block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return a lambda that will make this call when given the input
|
|
||||||
# values, but then return the boolean inverse of that result.
|
|
||||||
def inverted_lambda
|
|
||||||
lambda do |target, value, &block|
|
|
||||||
target, block, method, *arguments = expand(target, value, block)
|
|
||||||
! target.send(method, *arguments, &block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Filters support:
|
|
||||||
#
|
|
||||||
# Symbols:: A method to call.
|
|
||||||
# Procs:: A proc to call with the object.
|
|
||||||
# Objects:: An object with a <tt>before_foo</tt> method on it to call.
|
|
||||||
#
|
|
||||||
# All of these objects are converted into a CallTemplate and handled
|
|
||||||
# the same after this point.
|
|
||||||
def self.build(filter, callback)
|
|
||||||
case filter
|
|
||||||
when Symbol
|
|
||||||
new(nil, filter, [], nil)
|
|
||||||
when Conditionals::Value
|
|
||||||
new(filter, :call, [:target, :value], nil)
|
|
||||||
when ::Proc
|
|
||||||
if filter.arity > 1
|
|
||||||
new(nil, :instance_exec, [:target, :block], filter)
|
|
||||||
elsif filter.arity > 0
|
|
||||||
new(nil, :instance_exec, [:target], filter)
|
|
||||||
else
|
|
||||||
new(nil, :instance_exec, [], filter)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
method_to_call = callback.current_scopes.join("_")
|
|
||||||
|
|
||||||
new(filter, method_to_call, [:target], nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Execute before and after filters in a sequence instead of
|
|
||||||
# chaining them with nested lambda calls, see:
|
|
||||||
# https://github.com/rails/rails/issues/18011
|
|
||||||
class CallbackSequence # :nodoc:
|
|
||||||
def initialize(nested = nil, call_template = nil, user_conditions = nil)
|
|
||||||
@nested = nested
|
|
||||||
@call_template = call_template
|
|
||||||
@user_conditions = user_conditions
|
|
||||||
|
|
||||||
@before = []
|
|
||||||
@after = []
|
|
||||||
end
|
|
||||||
|
|
||||||
def before(&before)
|
|
||||||
@before.unshift(before)
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def after(&after)
|
|
||||||
@after.push(after)
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def around(call_template, user_conditions)
|
|
||||||
CallbackSequence.new(self, call_template, user_conditions)
|
|
||||||
end
|
|
||||||
|
|
||||||
def skip?(arg)
|
|
||||||
arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) }
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :nested
|
|
||||||
|
|
||||||
def final?
|
|
||||||
!@call_template
|
|
||||||
end
|
|
||||||
|
|
||||||
def expand_call_template(arg, block)
|
|
||||||
@call_template.expand(arg.target, arg.value, block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def invoke_before(arg)
|
|
||||||
@before.each { |b| b.call(arg) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def invoke_after(arg)
|
|
||||||
@after.each { |a| a.call(arg) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class CallbackChain #:nodoc:#
|
|
||||||
include Enumerable
|
|
||||||
|
|
||||||
attr_reader :name, :config
|
|
||||||
|
|
||||||
def initialize(name, config)
|
|
||||||
@name = name
|
|
||||||
@config = {
|
|
||||||
scope: [:kind],
|
|
||||||
terminator: default_terminator
|
|
||||||
}.merge!(config)
|
|
||||||
@chain = []
|
|
||||||
@callbacks = nil
|
|
||||||
@mutex = Mutex.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def each(&block); @chain.each(&block); end
|
|
||||||
def index(o); @chain.index(o); end
|
|
||||||
def empty?; @chain.empty?; end
|
|
||||||
|
|
||||||
def insert(index, o)
|
|
||||||
@callbacks = nil
|
|
||||||
@chain.insert(index, o)
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(o)
|
|
||||||
@callbacks = nil
|
|
||||||
@chain.delete(o)
|
|
||||||
end
|
|
||||||
|
|
||||||
def clear
|
|
||||||
@callbacks = nil
|
|
||||||
@chain.clear
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize_copy(other)
|
|
||||||
@callbacks = nil
|
|
||||||
@chain = other.chain.dup
|
|
||||||
@mutex = Mutex.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def compile
|
|
||||||
@callbacks || @mutex.synchronize do
|
|
||||||
final_sequence = CallbackSequence.new
|
|
||||||
@callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
|
|
||||||
callback.apply callback_sequence
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def append(*callbacks)
|
|
||||||
callbacks.each { |c| append_one(c) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def prepend(*callbacks)
|
|
||||||
callbacks.each { |c| prepend_one(c) }
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
attr_reader :chain
|
|
||||||
|
|
||||||
private
|
|
||||||
def append_one(callback)
|
|
||||||
@callbacks = nil
|
|
||||||
remove_duplicates(callback)
|
|
||||||
@chain.push(callback)
|
|
||||||
end
|
|
||||||
|
|
||||||
def prepend_one(callback)
|
|
||||||
@callbacks = nil
|
|
||||||
remove_duplicates(callback)
|
|
||||||
@chain.unshift(callback)
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_duplicates(callback)
|
|
||||||
@callbacks = nil
|
|
||||||
@chain.delete_if { |c| callback.duplicates?(c) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_terminator
|
|
||||||
Proc.new do |target, result_lambda|
|
|
||||||
terminate = true
|
|
||||||
catch(:abort) do
|
|
||||||
result_lambda.call
|
|
||||||
terminate = false
|
|
||||||
end
|
|
||||||
terminate
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
def normalize_callback_params(filters, block) # :nodoc:
|
|
||||||
type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
|
|
||||||
options = filters.extract_options!
|
|
||||||
filters.unshift(block) if block
|
|
||||||
[type, filters, options.dup]
|
|
||||||
end
|
|
||||||
|
|
||||||
# This is used internally to append, prepend and skip callbacks to the
|
|
||||||
# CallbackChain.
|
|
||||||
def __update_callbacks(name) #:nodoc:
|
|
||||||
([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
|
|
||||||
chain = target.get_callbacks name
|
|
||||||
yield target, chain.dup
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Install a callback for the given event.
|
|
||||||
#
|
|
||||||
# set_callback :save, :before, :before_method
|
|
||||||
# set_callback :save, :after, :after_method, if: :condition
|
|
||||||
# set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
|
|
||||||
#
|
|
||||||
# The second argument indicates whether the callback is to be run +:before+,
|
|
||||||
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
|
|
||||||
# means the first example above can also be written as:
|
|
||||||
#
|
|
||||||
# set_callback :save, :before_method
|
|
||||||
#
|
|
||||||
# The callback can be specified as a symbol naming an instance method; as a
|
|
||||||
# proc, lambda, or block; or as an object that responds to a certain method
|
|
||||||
# determined by the <tt>:scope</tt> argument to +define_callbacks+.
|
|
||||||
#
|
|
||||||
# If a proc, lambda, or block is given, its body is evaluated in the context
|
|
||||||
# of the current object. It can also optionally accept the current object as
|
|
||||||
# an argument.
|
|
||||||
#
|
|
||||||
# Before and around callbacks are called in the order that they are set;
|
|
||||||
# after callbacks are called in the reverse order.
|
|
||||||
#
|
|
||||||
# Around callbacks can access the return value from the event, if it
|
|
||||||
# wasn't halted, from the +yield+ call.
|
|
||||||
#
|
|
||||||
# ===== Options
|
|
||||||
#
|
|
||||||
# * <tt>:if</tt> - A symbol or an array of symbols, each naming an instance
|
|
||||||
# method or a proc; the callback will be called only when they all return
|
|
||||||
# a true value.
|
|
||||||
#
|
|
||||||
# If a proc is given, its body is evaluated in the context of the
|
|
||||||
# current object. It can also optionally accept the current object as
|
|
||||||
# an argument.
|
|
||||||
# * <tt>:unless</tt> - A symbol or an array of symbols, each naming an
|
|
||||||
# instance method or a proc; the callback will be called only when they
|
|
||||||
# all return a false value.
|
|
||||||
#
|
|
||||||
# If a proc is given, its body is evaluated in the context of the
|
|
||||||
# current object. It can also optionally accept the current object as
|
|
||||||
# an argument.
|
|
||||||
# * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
|
|
||||||
# existing chain rather than appended.
|
|
||||||
def set_callback(name, *filter_list, &block)
|
|
||||||
type, filters, options = normalize_callback_params(filter_list, block)
|
|
||||||
|
|
||||||
self_chain = get_callbacks name
|
|
||||||
mapped = filters.map do |filter|
|
|
||||||
Callback.build(self_chain, filter, type, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
__update_callbacks(name) do |target, chain|
|
|
||||||
options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
|
|
||||||
target.set_callbacks name, chain
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or
|
|
||||||
# <tt>:unless</tt> options may be passed in order to control when the
|
|
||||||
# callback is skipped.
|
|
||||||
#
|
|
||||||
# class Writer < Person
|
|
||||||
# skip_callback :validate, :before, :check_membership, if: -> { age > 18 }
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# An <tt>ArgumentError</tt> will be raised if the callback has not
|
|
||||||
# already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>).
|
|
||||||
def skip_callback(name, *filter_list, &block)
|
|
||||||
type, filters, options = normalize_callback_params(filter_list, block)
|
|
||||||
|
|
||||||
options[:raise] = true unless options.key?(:raise)
|
|
||||||
|
|
||||||
__update_callbacks(name) do |target, chain|
|
|
||||||
filters.each do |filter|
|
|
||||||
callback = chain.find { |c| c.matches?(type, filter) }
|
|
||||||
|
|
||||||
if !callback && options[:raise]
|
|
||||||
raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined"
|
|
||||||
end
|
|
||||||
|
|
||||||
if callback && (options.key?(:if) || options.key?(:unless))
|
|
||||||
new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless])
|
|
||||||
chain.insert(chain.index(callback), new_callback)
|
|
||||||
end
|
|
||||||
|
|
||||||
chain.delete(callback)
|
|
||||||
end
|
|
||||||
target.set_callbacks name, chain
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Remove all set callbacks for the given event.
|
|
||||||
def reset_callbacks(name)
|
|
||||||
callbacks = get_callbacks name
|
|
||||||
|
|
||||||
ActiveSupport::DescendantsTracker.descendants(self).each do |target|
|
|
||||||
chain = target.get_callbacks(name).dup
|
|
||||||
callbacks.each { |c| chain.delete(c) }
|
|
||||||
target.set_callbacks name, chain
|
|
||||||
end
|
|
||||||
|
|
||||||
set_callbacks(name, callbacks.dup.clear)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Define sets of events in the object life cycle that support callbacks.
|
|
||||||
#
|
|
||||||
# define_callbacks :validate
|
|
||||||
# define_callbacks :initialize, :save, :destroy
|
|
||||||
#
|
|
||||||
# ===== Options
|
|
||||||
#
|
|
||||||
# * <tt>:terminator</tt> - Determines when a before filter will halt the
|
|
||||||
# callback chain, preventing following before and around callbacks from
|
|
||||||
# being called and the event from being triggered.
|
|
||||||
# This should be a lambda to be executed.
|
|
||||||
# The current object and the result lambda of the callback will be provided
|
|
||||||
# to the terminator lambda.
|
|
||||||
#
|
|
||||||
# define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
|
|
||||||
#
|
|
||||||
# In this example, if any before validate callbacks returns +false+,
|
|
||||||
# any successive before and around callback is not executed.
|
|
||||||
#
|
|
||||||
# The default terminator halts the chain when a callback throws +:abort+.
|
|
||||||
#
|
|
||||||
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
|
|
||||||
# callbacks should be terminated by the <tt>:terminator</tt> option. By
|
|
||||||
# default after callbacks are executed no matter if callback chain was
|
|
||||||
# terminated or not. This option has no effect if <tt>:terminator</tt>
|
|
||||||
# option is set to +nil+.
|
|
||||||
#
|
|
||||||
# * <tt>:scope</tt> - Indicates which methods should be executed when an
|
|
||||||
# object is used as a callback.
|
|
||||||
#
|
|
||||||
# class Audit
|
|
||||||
# def before(caller)
|
|
||||||
# puts 'Audit: before is called'
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def before_save(caller)
|
|
||||||
# puts 'Audit: before_save is called'
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class Account
|
|
||||||
# include ActiveSupport::Callbacks
|
|
||||||
#
|
|
||||||
# define_callbacks :save
|
|
||||||
# set_callback :save, :before, Audit.new
|
|
||||||
#
|
|
||||||
# def save
|
|
||||||
# run_callbacks :save do
|
|
||||||
# puts 'save in main'
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# In the above case whenever you save an account the method
|
|
||||||
# <tt>Audit#before</tt> will be called. On the other hand
|
|
||||||
#
|
|
||||||
# define_callbacks :save, scope: [:kind, :name]
|
|
||||||
#
|
|
||||||
# would trigger <tt>Audit#before_save</tt> instead. That's constructed
|
|
||||||
# by calling <tt>#{kind}_#{name}</tt> on the given instance. In this
|
|
||||||
# case "kind" is "before" and "name" is "save". In this context +:kind+
|
|
||||||
# and +:name+ have special meanings: +:kind+ refers to the kind of
|
|
||||||
# callback (before/after/around) and +:name+ refers to the method on
|
|
||||||
# which callbacks are being defined.
|
|
||||||
#
|
|
||||||
# A declaration like
|
|
||||||
#
|
|
||||||
# define_callbacks :save, scope: [:name]
|
|
||||||
#
|
|
||||||
# would call <tt>Audit#save</tt>.
|
|
||||||
#
|
|
||||||
# ===== Notes
|
|
||||||
#
|
|
||||||
# +names+ passed to +define_callbacks+ must not end with
|
|
||||||
# <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
|
|
||||||
#
|
|
||||||
# Calling +define_callbacks+ multiple times with the same +names+ will
|
|
||||||
# overwrite previous callbacks registered with +set_callback+.
|
|
||||||
def define_callbacks(*names)
|
|
||||||
options = names.extract_options!
|
|
||||||
|
|
||||||
names.each do |name|
|
|
||||||
name = name.to_sym
|
|
||||||
|
|
||||||
([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |target|
|
|
||||||
target.set_callbacks name, CallbackChain.new(name, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
||||||
def _run_#{name}_callbacks(&block)
|
|
||||||
run_callbacks #{name.inspect}, &block
|
|
||||||
end
|
|
||||||
|
|
||||||
def self._#{name}_callbacks
|
|
||||||
get_callbacks(#{name.inspect})
|
|
||||||
end
|
|
||||||
|
|
||||||
def self._#{name}_callbacks=(value)
|
|
||||||
set_callbacks(#{name.inspect}, value)
|
|
||||||
end
|
|
||||||
|
|
||||||
def _#{name}_callbacks
|
|
||||||
__callbacks[#{name.inspect}]
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
def get_callbacks(name) # :nodoc:
|
|
||||||
__callbacks[name.to_sym]
|
|
||||||
end
|
|
||||||
|
|
||||||
if Module.instance_method(:method_defined?).arity == 1 # Ruby 2.5 and older
|
|
||||||
def set_callbacks(name, callbacks) # :nodoc:
|
|
||||||
self.__callbacks = __callbacks.merge(name.to_sym => callbacks)
|
|
||||||
end
|
|
||||||
else # Ruby 2.6 and newer
|
|
||||||
def set_callbacks(name, callbacks) # :nodoc:
|
|
||||||
unless singleton_class.method_defined?(:__callbacks, false)
|
|
||||||
self.__callbacks = __callbacks.dup
|
|
||||||
end
|
|
||||||
self.__callbacks[name.to_sym] = callbacks
|
|
||||||
self.__callbacks
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,215 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
# A typical module looks like this:
|
|
||||||
#
|
|
||||||
# module M
|
|
||||||
# def self.included(base)
|
|
||||||
# base.extend ClassMethods
|
|
||||||
# base.class_eval do
|
|
||||||
# scope :disabled, -> { where(disabled: true) }
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# module ClassMethods
|
|
||||||
# ...
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# By using <tt>ActiveSupport::Concern</tt> the above module could instead be
|
|
||||||
# written as:
|
|
||||||
#
|
|
||||||
# require "active_support/concern"
|
|
||||||
#
|
|
||||||
# module M
|
|
||||||
# extend ActiveSupport::Concern
|
|
||||||
#
|
|
||||||
# included do
|
|
||||||
# scope :disabled, -> { where(disabled: true) }
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class_methods do
|
|
||||||
# ...
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# Moreover, it gracefully handles module dependencies. Given a +Foo+ module
|
|
||||||
# and a +Bar+ module which depends on the former, we would typically write the
|
|
||||||
# following:
|
|
||||||
#
|
|
||||||
# module Foo
|
|
||||||
# def self.included(base)
|
|
||||||
# base.class_eval do
|
|
||||||
# def self.method_injected_by_foo
|
|
||||||
# ...
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# module Bar
|
|
||||||
# def self.included(base)
|
|
||||||
# base.method_injected_by_foo
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class Host
|
|
||||||
# include Foo # We need to include this dependency for Bar
|
|
||||||
# include Bar # Bar is the module that Host really needs
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
|
|
||||||
# could try to hide these from +Host+ directly including +Foo+ in +Bar+:
|
|
||||||
#
|
|
||||||
# module Bar
|
|
||||||
# include Foo
|
|
||||||
# def self.included(base)
|
|
||||||
# base.method_injected_by_foo
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class Host
|
|
||||||
# include Bar
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
|
|
||||||
# is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
|
|
||||||
# module dependencies are properly resolved:
|
|
||||||
#
|
|
||||||
# require "active_support/concern"
|
|
||||||
#
|
|
||||||
# module Foo
|
|
||||||
# extend ActiveSupport::Concern
|
|
||||||
# included do
|
|
||||||
# def self.method_injected_by_foo
|
|
||||||
# ...
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# module Bar
|
|
||||||
# extend ActiveSupport::Concern
|
|
||||||
# include Foo
|
|
||||||
#
|
|
||||||
# included do
|
|
||||||
# self.method_injected_by_foo
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class Host
|
|
||||||
# include Bar # It works, now Bar takes care of its dependencies
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# === Prepending concerns
|
|
||||||
#
|
|
||||||
# Just like <tt>include</tt>, concerns also support <tt>prepend</tt> with a corresponding
|
|
||||||
# <tt>prepended do</tt> callback. <tt>module ClassMethods</tt> or <tt>class_methods do</tt> are
|
|
||||||
# prepended as well.
|
|
||||||
#
|
|
||||||
# <tt>prepend</tt> is also used for any dependencies.
|
|
||||||
module Concern
|
|
||||||
class MultipleIncludedBlocks < StandardError #:nodoc:
|
|
||||||
def initialize
|
|
||||||
super "Cannot define multiple 'included' blocks for a Concern"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class MultiplePrependBlocks < StandardError #:nodoc:
|
|
||||||
def initialize
|
|
||||||
super "Cannot define multiple 'prepended' blocks for a Concern"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.extended(base) #:nodoc:
|
|
||||||
base.instance_variable_set(:@_dependencies, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
def append_features(base) #:nodoc:
|
|
||||||
if base.instance_variable_defined?(:@_dependencies)
|
|
||||||
base.instance_variable_get(:@_dependencies) << self
|
|
||||||
false
|
|
||||||
else
|
|
||||||
return false if base < self
|
|
||||||
@_dependencies.each { |dep| base.include(dep) }
|
|
||||||
super
|
|
||||||
base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
|
|
||||||
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def prepend_features(base) #:nodoc:
|
|
||||||
if base.instance_variable_defined?(:@_dependencies)
|
|
||||||
base.instance_variable_get(:@_dependencies).unshift self
|
|
||||||
false
|
|
||||||
else
|
|
||||||
return false if base < self
|
|
||||||
@_dependencies.each { |dep| base.prepend(dep) }
|
|
||||||
super
|
|
||||||
base.singleton_class.prepend const_get(:ClassMethods) if const_defined?(:ClassMethods)
|
|
||||||
base.class_eval(&@_prepended_block) if instance_variable_defined?(:@_prepended_block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Evaluate given block in context of base class,
|
|
||||||
# so that you can write class macros here.
|
|
||||||
# When you define more than one +included+ block, it raises an exception.
|
|
||||||
def included(base = nil, &block)
|
|
||||||
if base.nil?
|
|
||||||
if instance_variable_defined?(:@_included_block)
|
|
||||||
if @_included_block.source_location != block.source_location
|
|
||||||
raise MultipleIncludedBlocks
|
|
||||||
end
|
|
||||||
else
|
|
||||||
@_included_block = block
|
|
||||||
end
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Evaluate given block in context of base class,
|
|
||||||
# so that you can write class macros here.
|
|
||||||
# When you define more than one +prepended+ block, it raises an exception.
|
|
||||||
def prepended(base = nil, &block)
|
|
||||||
if base.nil?
|
|
||||||
if instance_variable_defined?(:@_prepended_block)
|
|
||||||
if @_prepended_block.source_location != block.source_location
|
|
||||||
raise MultiplePrependBlocks
|
|
||||||
end
|
|
||||||
else
|
|
||||||
@_prepended_block = block
|
|
||||||
end
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Define class methods from given block.
|
|
||||||
# You can define private class methods as well.
|
|
||||||
#
|
|
||||||
# module Example
|
|
||||||
# extend ActiveSupport::Concern
|
|
||||||
#
|
|
||||||
# class_methods do
|
|
||||||
# def foo; puts 'foo'; end
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
# def bar; puts 'bar'; end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class Buzz
|
|
||||||
# include Example
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# Buzz.foo # => "foo"
|
|
||||||
# Buzz.bar # => private method 'bar' called for Buzz:Class(NoMethodError)
|
|
||||||
def class_methods(&class_methods_module_definition)
|
|
||||||
mod = const_defined?(:ClassMethods, false) ?
|
|
||||||
const_get(:ClassMethods) :
|
|
||||||
const_set(:ClassMethods, Module.new)
|
|
||||||
|
|
||||||
mod.module_eval(&class_methods_module_definition)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,146 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/concern"
|
|
||||||
require "active_support/ordered_options"
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
# Configurable provides a <tt>config</tt> method to store and retrieve
|
|
||||||
# configuration options as an <tt>OrderedOptions</tt>.
|
|
||||||
module Configurable
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
class Configuration < ActiveSupport::InheritableOptions
|
|
||||||
def compile_methods!
|
|
||||||
self.class.compile_methods!(keys)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Compiles reader methods so we don't have to go through method_missing.
|
|
||||||
def self.compile_methods!(keys)
|
|
||||||
keys.reject { |m| method_defined?(m) }.each do |key|
|
|
||||||
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
||||||
def #{key}; _get(#{key.inspect}); end
|
|
||||||
RUBY
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
def config
|
|
||||||
@_config ||= if respond_to?(:superclass) && superclass.respond_to?(:config)
|
|
||||||
superclass.config.inheritable_copy
|
|
||||||
else
|
|
||||||
# create a new "anonymous" class that will host the compiled reader methods
|
|
||||||
Class.new(Configuration).new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def configure
|
|
||||||
yield config
|
|
||||||
end
|
|
||||||
|
|
||||||
# Allows you to add shortcut so that you don't have to refer to attribute
|
|
||||||
# through config. Also look at the example for config to contrast.
|
|
||||||
#
|
|
||||||
# Defines both class and instance config accessors.
|
|
||||||
#
|
|
||||||
# class User
|
|
||||||
# include ActiveSupport::Configurable
|
|
||||||
# config_accessor :allowed_access
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# User.allowed_access # => nil
|
|
||||||
# User.allowed_access = false
|
|
||||||
# User.allowed_access # => false
|
|
||||||
#
|
|
||||||
# user = User.new
|
|
||||||
# user.allowed_access # => false
|
|
||||||
# user.allowed_access = true
|
|
||||||
# user.allowed_access # => true
|
|
||||||
#
|
|
||||||
# User.allowed_access # => false
|
|
||||||
#
|
|
||||||
# The attribute name must be a valid method name in Ruby.
|
|
||||||
#
|
|
||||||
# class User
|
|
||||||
# include ActiveSupport::Configurable
|
|
||||||
# config_accessor :"1_Badname"
|
|
||||||
# end
|
|
||||||
# # => NameError: invalid config attribute name
|
|
||||||
#
|
|
||||||
# To omit the instance writer method, pass <tt>instance_writer: false</tt>.
|
|
||||||
# To omit the instance reader method, pass <tt>instance_reader: false</tt>.
|
|
||||||
#
|
|
||||||
# class User
|
|
||||||
# include ActiveSupport::Configurable
|
|
||||||
# config_accessor :allowed_access, instance_reader: false, instance_writer: false
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# User.allowed_access = false
|
|
||||||
# User.allowed_access # => false
|
|
||||||
#
|
|
||||||
# User.new.allowed_access = true # => NoMethodError
|
|
||||||
# User.new.allowed_access # => NoMethodError
|
|
||||||
#
|
|
||||||
# Or pass <tt>instance_accessor: false</tt>, to omit both instance methods.
|
|
||||||
#
|
|
||||||
# class User
|
|
||||||
# include ActiveSupport::Configurable
|
|
||||||
# config_accessor :allowed_access, instance_accessor: false
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# User.allowed_access = false
|
|
||||||
# User.allowed_access # => false
|
|
||||||
#
|
|
||||||
# User.new.allowed_access = true # => NoMethodError
|
|
||||||
# User.new.allowed_access # => NoMethodError
|
|
||||||
#
|
|
||||||
# Also you can pass a block to set up the attribute with a default value.
|
|
||||||
#
|
|
||||||
# class User
|
|
||||||
# include ActiveSupport::Configurable
|
|
||||||
# config_accessor :hair_colors do
|
|
||||||
# [:brown, :black, :blonde, :red]
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# User.hair_colors # => [:brown, :black, :blonde, :red]
|
|
||||||
def config_accessor(*names, instance_reader: true, instance_writer: true, instance_accessor: true) # :doc:
|
|
||||||
names.each do |name|
|
|
||||||
raise NameError.new("invalid config attribute name") unless /\A[_A-Za-z]\w*\z/.match?(name)
|
|
||||||
|
|
||||||
reader, reader_line = "def #{name}; config.#{name}; end", __LINE__
|
|
||||||
writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__
|
|
||||||
|
|
||||||
singleton_class.class_eval reader, __FILE__, reader_line
|
|
||||||
singleton_class.class_eval writer, __FILE__, writer_line
|
|
||||||
|
|
||||||
if instance_accessor
|
|
||||||
class_eval reader, __FILE__, reader_line if instance_reader
|
|
||||||
class_eval writer, __FILE__, writer_line if instance_writer
|
|
||||||
end
|
|
||||||
send("#{name}=", yield) if block_given?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
private :config_accessor
|
|
||||||
end
|
|
||||||
|
|
||||||
# Reads and writes attributes from a configuration <tt>OrderedOptions</tt>.
|
|
||||||
#
|
|
||||||
# require "active_support/configurable"
|
|
||||||
#
|
|
||||||
# class User
|
|
||||||
# include ActiveSupport::Configurable
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# user = User.new
|
|
||||||
#
|
|
||||||
# user.config.allowed_access = true
|
|
||||||
# user.config.level = 1
|
|
||||||
#
|
|
||||||
# user.config.allowed_access # => true
|
|
||||||
# user.config.level # => 1
|
|
||||||
def config
|
|
||||||
@_config ||= self.class.config.inheritable_copy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
# Reads a YAML configuration file, evaluating any ERB, then
|
|
||||||
# parsing the resulting YAML.
|
|
||||||
#
|
|
||||||
# Warns in case of YAML confusing characters, like invisible
|
|
||||||
# non-breaking spaces.
|
|
||||||
class ConfigurationFile # :nodoc:
|
|
||||||
class FormatError < StandardError; end
|
|
||||||
|
|
||||||
def initialize(content_path)
|
|
||||||
@content_path = content_path.to_s
|
|
||||||
@content = read content_path
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.parse(content_path, **options)
|
|
||||||
new(content_path).parse(**options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse(context: nil, **options)
|
|
||||||
source = render(context)
|
|
||||||
if YAML.respond_to?(:unsafe_load)
|
|
||||||
YAML.unsafe_load(source, **options) || {}
|
|
||||||
else
|
|
||||||
YAML.load(source, **options) || {}
|
|
||||||
end
|
|
||||||
rescue Psych::SyntaxError => error
|
|
||||||
raise "YAML syntax error occurred while parsing #{@content_path}. " \
|
|
||||||
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
|
|
||||||
"Error: #{error.message}"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def read(content_path)
|
|
||||||
require "yaml"
|
|
||||||
require "erb"
|
|
||||||
|
|
||||||
File.read(content_path).tap do |content|
|
|
||||||
if content.include?("\u00A0")
|
|
||||||
warn "File contains invisible non-breaking spaces, you may want to remove those"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(context)
|
|
||||||
erb = ERB.new(@content).tap { |e| e.filename = @content_path }
|
|
||||||
context ? erb.result(context) : erb.result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).sort.each do |path|
|
|
||||||
require path
|
|
||||||
end
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# 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"
|
|
||||||
require "active_support/core_ext/array/extract_options"
|
|
||||||
require "active_support/core_ext/array/grouping"
|
|
||||||
require "active_support/core_ext/array/inquiry"
|
|
||||||
@ -1,213 +0,0 @@
|
|||||||
# 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::XmlMarkup)
|
|
||||||
|
|
||||||
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
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Array
|
|
||||||
# Removes and returns the elements for which the block returns a true value.
|
|
||||||
# If no block is given, an Enumerator is returned instead.
|
|
||||||
#
|
|
||||||
# numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
||||||
# odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
|
|
||||||
# numbers # => [0, 2, 4, 6, 8]
|
|
||||||
def extract!
|
|
||||||
return to_enum(:extract!) { size } unless block_given?
|
|
||||||
|
|
||||||
extracted_elements = []
|
|
||||||
|
|
||||||
reject! do |element|
|
|
||||||
extracted_elements << element if yield(element)
|
|
||||||
end
|
|
||||||
|
|
||||||
extracted_elements
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# 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(&block)
|
|
||||||
1000 * realtime(&block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/big_decimal/conversions"
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/class/attribute"
|
|
||||||
require "active_support/core_ext/class/subclasses"
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/module/redefine_method"
|
|
||||||
|
|
||||||
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, instance_accessor: true,
|
|
||||||
instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil)
|
|
||||||
|
|
||||||
class_methods, methods = [], []
|
|
||||||
attrs.each do |name|
|
|
||||||
unless name.is_a?(Symbol) || name.is_a?(String)
|
|
||||||
raise TypeError, "#{name.inspect} is not a symbol nor a string"
|
|
||||||
end
|
|
||||||
|
|
||||||
class_methods << <<~RUBY # In case the method exists and is not public
|
|
||||||
silence_redefinition_of_method def #{name}
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
|
|
||||||
methods << <<~RUBY if instance_reader
|
|
||||||
silence_redefinition_of_method def #{name}
|
|
||||||
defined?(@#{name}) ? @#{name} : self.class.#{name}
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
|
|
||||||
class_methods << <<~RUBY
|
|
||||||
silence_redefinition_of_method def #{name}=(value)
|
|
||||||
redefine_method(:#{name}) { value } if singleton_class?
|
|
||||||
redefine_singleton_method(:#{name}) { value }
|
|
||||||
value
|
|
||||||
end
|
|
||||||
RUBY
|
|
||||||
|
|
||||||
methods << <<~RUBY if instance_writer
|
|
||||||
silence_redefinition_of_method(:#{name}=)
|
|
||||||
attr_writer :#{name}
|
|
||||||
RUBY
|
|
||||||
|
|
||||||
if instance_predicate
|
|
||||||
class_methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end"
|
|
||||||
if instance_reader
|
|
||||||
methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
location = caller_locations(1, 1).first
|
|
||||||
class_eval(["class << self", *class_methods, "end", *methods].join(";").tr("\n", ";"), location.path, location.lineno)
|
|
||||||
|
|
||||||
attrs.each { |name| public_send("#{name}=", default) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class 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
|
|
||||||
ObjectSpace.each_object(singleton_class).reject do |k|
|
|
||||||
k.singleton_class? || k == self
|
|
||||||
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
|
|
||||||
descendants.select { |descendant| descendant.superclass == self }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "date"
|
|
||||||
|
|
||||||
class Date #:nodoc:
|
|
||||||
# No Date is blank:
|
|
||||||
#
|
|
||||||
# Date.today.blank? # => false
|
|
||||||
#
|
|
||||||
# @return [false]
|
|
||||||
def blank?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,146 +0,0 @@
|
|||||||
# 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)
|
|
||||||
d = self
|
|
||||||
|
|
||||||
d = d >> options[:years] * 12 if options[:years]
|
|
||||||
d = d >> options[:months] if options[:months]
|
|
||||||
d = d + options[:weeks] * 7 if options[:weeks]
|
|
||||||
d = d + options[: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
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
# 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",
|
|
||||||
inspect: "%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.public_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
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "date"
|
|
||||||
require "active_support/core_ext/date_and_time/zones"
|
|
||||||
|
|
||||||
class Date
|
|
||||||
include DateAndTime::Zones
|
|
||||||
end
|
|
||||||
@ -1,364 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/object/try"
|
|
||||||
require "active_support/core_ext/date_time/conversions"
|
|
||||||
|
|
||||||
module DateAndTime
|
|
||||||
module Calculations
|
|
||||||
DAYS_INTO_WEEK = {
|
|
||||||
sunday: 0,
|
|
||||||
monday: 1,
|
|
||||||
tuesday: 2,
|
|
||||||
wednesday: 3,
|
|
||||||
thursday: 4,
|
|
||||||
friday: 5,
|
|
||||||
saturday: 6
|
|
||||||
}
|
|
||||||
WEEKEND_DAYS = [ 6, 0 ]
|
|
||||||
|
|
||||||
# Returns a new date/time representing yesterday.
|
|
||||||
def yesterday
|
|
||||||
advance(days: -1)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a new date/time representing tomorrow.
|
|
||||||
def tomorrow
|
|
||||||
advance(days: 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns true if the date/time is today.
|
|
||||||
def today?
|
|
||||||
to_date == ::Date.current
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns true if the date/time is tomorrow.
|
|
||||||
def tomorrow?
|
|
||||||
to_date == ::Date.current.tomorrow
|
|
||||||
end
|
|
||||||
alias :next_day? :tomorrow?
|
|
||||||
|
|
||||||
# Returns true if the date/time is yesterday.
|
|
||||||
def yesterday?
|
|
||||||
to_date == ::Date.current.yesterday
|
|
||||||
end
|
|
||||||
alias :prev_day? :yesterday?
|
|
||||||
|
|
||||||
# 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 true if the date/time falls before <tt>date_or_time</tt>.
|
|
||||||
def before?(date_or_time)
|
|
||||||
self < date_or_time
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns true if the date/time falls after <tt>date_or_time</tt>.
|
|
||||||
def after?(date_or_time)
|
|
||||||
self > date_or_time
|
|
||||||
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 = month - (2 + month) % 3
|
|
||||||
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 = month + (12 - month) % 3
|
|
||||||
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
|
|
||||||
|
|
||||||
# Short-hand for months_since(3)
|
|
||||||
def next_quarter
|
|
||||||
months_since(3)
|
|
||||||
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
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# 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.fetch(start_day)
|
|
||||||
(wday - 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)
|
|
||||||
from_now = DAYS_INTO_WEEK.fetch(day_of_week) - wday
|
|
||||||
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)
|
|
||||||
ago = wday - 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.fetch(day) - DAYS_INTO_WEEK.fetch(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
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
# Change the output of <tt>ActiveSupport::TimeZone.utc_to_local</tt>.
|
|
||||||
#
|
|
||||||
# When `true`, it returns local times with an UTC offset, with `false` local
|
|
||||||
# times are returned as UTC.
|
|
||||||
#
|
|
||||||
# # Given this zone:
|
|
||||||
# zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
|
|
||||||
#
|
|
||||||
# # With `utc_to_local_returns_utc_offset_times = false`, local time is converted to UTC:
|
|
||||||
# zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 UTC
|
|
||||||
#
|
|
||||||
# # With `utc_to_local_returns_utc_offset_times = true`, local time is returned with UTC offset:
|
|
||||||
# zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 -0500
|
|
||||||
mattr_accessor :utc_to_local_returns_utc_offset_times, instance_writer: false, default: false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,211 +0,0 @@
|
|||||||
# 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, 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
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/digest/uuid"
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "securerandom"
|
|
||||||
require "digest"
|
|
||||||
|
|
||||||
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
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/file/atomic"
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/hash/conversions"
|
|
||||||
require "active_support/core_ext/hash/deep_merge"
|
|
||||||
require "active_support/core_ext/hash/deep_transform_values"
|
|
||||||
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"
|
|
||||||
@ -1,263 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/xml_mini"
|
|
||||||
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/object/try"
|
|
||||||
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::XmlMarkup)
|
|
||||||
|
|
||||||
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 = value.transform_values { |v| 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
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
# 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 == 0 : self % number == 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/kernel/concern"
|
|
||||||
require "active_support/core_ext/kernel/reporting"
|
|
||||||
require "active_support/core_ext/kernel/singleton_class"
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# 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.delete_suffix(".rb") == path.to_s.delete_suffix(".rb")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/string/inflections"
|
|
||||||
|
|
||||||
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)
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
# 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/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"
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
# 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
|
|
||||||
return unless message.start_with?("uninitialized constant ")
|
|
||||||
|
|
||||||
receiver = begin
|
|
||||||
self.receiver
|
|
||||||
rescue ArgumentError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
if receiver == Object
|
|
||||||
name.to_s
|
|
||||||
elsif receiver
|
|
||||||
"#{real_mod_name(receiver)}::#{self.name}"
|
|
||||||
else
|
|
||||||
if match = message.match(/((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/)
|
|
||||||
match[1]
|
|
||||||
end
|
|
||||||
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
|
|
||||||
|
|
||||||
private
|
|
||||||
UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
|
|
||||||
private_constant :UNBOUND_METHOD_MODULE_NAME
|
|
||||||
|
|
||||||
if UnboundMethod.method_defined?(:bind_call)
|
|
||||||
def real_mod_name(mod)
|
|
||||||
UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
def real_mod_name(mod)
|
|
||||||
UNBOUND_METHOD_MODULE_NAME.bind(mod).call
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/numeric/bytes"
|
|
||||||
require "active_support/core_ext/numeric/time"
|
|
||||||
require "active_support/core_ext/numeric/conversions"
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,239 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Hack to load json gem first so we can overwrite its to_json.
|
|
||||||
require "json"
|
|
||||||
require "bigdecimal"
|
|
||||||
require "ipaddr"
|
|
||||||
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
|
|
||||||
|
|
||||||
[Enumerable, Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].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
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
subset.each do |k, v|
|
|
||||||
result[k.to_s] = options ? v.as_json(options.dup) : v.as_json
|
|
||||||
end
|
|
||||||
result
|
|
||||||
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 IPAddr # :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
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/object/to_query"
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/range/conversions"
|
|
||||||
require "active_support/core_ext/range/compare_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"
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
module CompareWithRange
|
|
||||||
# Extends the default Range#=== to support range comparisons.
|
|
||||||
# (1..5) === (1..5) # => true
|
|
||||||
# (1..5) === (2..3) # => true
|
|
||||||
# (1..5) === (1...6) # => true
|
|
||||||
# (1..5) === (2..6) # => false
|
|
||||||
#
|
|
||||||
# The native Range#=== behavior is untouched.
|
|
||||||
# ('a'..'f') === ('c') # => true
|
|
||||||
# (5..9) === (11) # => false
|
|
||||||
#
|
|
||||||
# The given range must be fully bounded, with both start and end.
|
|
||||||
def ===(value)
|
|
||||||
if value.is_a?(::Range)
|
|
||||||
is_backwards_op = value.exclude_end? ? :>= : :>
|
|
||||||
return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end)
|
|
||||||
# 1...10 includes 1..9 but it does not include 1..10.
|
|
||||||
# 1..10 includes 1...11 but it does not include 1...12.
|
|
||||||
operator = exclude_end? && !value.exclude_end? ? :< : :<=
|
|
||||||
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
|
|
||||||
super(value.first) && (self.end.nil? || value_max.public_send(operator, last))
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Extends the default Range#include? to support range comparisons.
|
|
||||||
# (1..5).include?(1..5) # => true
|
|
||||||
# (1..5).include?(2..3) # => true
|
|
||||||
# (1..5).include?(1...6) # => true
|
|
||||||
# (1..5).include?(2..6) # => false
|
|
||||||
#
|
|
||||||
# The native Range#include? behavior is untouched.
|
|
||||||
# ('a'..'f').include?('c') # => true
|
|
||||||
# (5..9).include?(11) # => false
|
|
||||||
#
|
|
||||||
# The given range must be fully bounded, with both start and end.
|
|
||||||
def include?(value)
|
|
||||||
if value.is_a?(::Range)
|
|
||||||
is_backwards_op = value.exclude_end? ? :>= : :>
|
|
||||||
return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end)
|
|
||||||
# 1...10 includes 1..9 but it does not include 1..10.
|
|
||||||
# 1..10 includes 1...11 but it does not include 1...12.
|
|
||||||
operator = exclude_end? && !value.exclude_end? ? :< : :<=
|
|
||||||
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
|
|
||||||
super(value.first) && (self.end.nil? || value_max.public_send(operator, last))
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Extends the default Range#cover? to support range comparisons.
|
|
||||||
# (1..5).cover?(1..5) # => true
|
|
||||||
# (1..5).cover?(2..3) # => true
|
|
||||||
# (1..5).cover?(1...6) # => true
|
|
||||||
# (1..5).cover?(2..6) # => false
|
|
||||||
#
|
|
||||||
# The native Range#cover? behavior is untouched.
|
|
||||||
# ('a'..'f').cover?('c') # => true
|
|
||||||
# (5..9).cover?(11) # => false
|
|
||||||
#
|
|
||||||
# The given range must be fully bounded, with both start and end.
|
|
||||||
def cover?(value)
|
|
||||||
if value.is_a?(::Range)
|
|
||||||
is_backwards_op = value.exclude_end? ? :>= : :>
|
|
||||||
return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end)
|
|
||||||
# 1...10 covers 1..9 but it does not cover 1..10.
|
|
||||||
# 1..10 covers 1...11 but it does not cover 1...12.
|
|
||||||
operator = exclude_end? && !value.exclude_end? ? :< : :<=
|
|
||||||
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
|
|
||||||
super(value.first) && (self.end.nil? || value_max.public_send(operator, last))
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Range.prepend(ActiveSupport::CompareWithRange)
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
module 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
|
|
||||||
end
|
|
||||||
|
|
||||||
Range.prepend(ActiveSupport::RangeWithFormat)
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
# 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)
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/time_with_zone"
|
|
||||||
require "active_support/deprecation"
|
|
||||||
|
|
||||||
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 self.begin.is_a?(TimeWithZone) || self.end.is_a?(TimeWithZone)
|
|
||||||
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
|
||||||
Using `Range#include?` to check the inclusion of a value in
|
|
||||||
a date time range is deprecated.
|
|
||||||
It is recommended to use `Range#cover?` instead of `Range#include?` to
|
|
||||||
check the inclusion of a value in a date time range.
|
|
||||||
MSG
|
|
||||||
cover?(value)
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Range.prepend(ActiveSupport::IncludeTimeWithZone)
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Regexp
|
|
||||||
# Returns +true+ if the regexp has the multiline flag set.
|
|
||||||
#
|
|
||||||
# (/./).multiline? # => false
|
|
||||||
# (/./m).multiline? # => true
|
|
||||||
#
|
|
||||||
# Regexp.new(".").multiline? # => false
|
|
||||||
# Regexp.new(".", Regexp::MULTILINE).multiline? # => true
|
|
||||||
def multiline?
|
|
||||||
options & MULTILINE == MULTILINE
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
# 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"]
|
|
||||||
BASE36_ALPHABET = ("0".."9").to_a + ("a".."z").to_a
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# SecureRandom.base36 generates a random base36 string in lowercase.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
# This method can be used over +base58+ if a deterministic case key is necessary.
|
|
||||||
#
|
|
||||||
# The result will contain alphanumeric characters in lowercase.
|
|
||||||
#
|
|
||||||
# p SecureRandom.base36 # => "4kugl2pdqmscqtje"
|
|
||||||
# p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
|
|
||||||
def self.base36(n = 16)
|
|
||||||
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
|
|
||||||
idx = byte % 64
|
|
||||||
idx = SecureRandom.random_number(36) if idx >= 36
|
|
||||||
BASE36_ALPHABET[idx]
|
|
||||||
end.join
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
# 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"
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
# 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, length]
|
|
||||||
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)
|
|
||||||
position += size if position < 0
|
|
||||||
self[0, position + 1] || +""
|
|
||||||
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)
|
|
||||||
self[0, limit] || raise(ArgumentError, "negative limit")
|
|
||||||
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)
|
|
||||||
self[[length - limit, 0].max, limit] || raise(ArgumentError, "negative limit")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
# 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
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
# 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
|
|
||||||
# "1604326192".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
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/string_inquirer"
|
|
||||||
require "active_support/environment_inquirer"
|
|
||||||
|
|
||||||
class String
|
|
||||||
# Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class,
|
|
||||||
# which gives you a prettier way to test for equality.
|
|
||||||
#
|
|
||||||
# env = 'production'.inquiry
|
|
||||||
# env.production? # => true
|
|
||||||
# env.development? # => false
|
|
||||||
def inquiry
|
|
||||||
ActiveSupport::StringInquirer.new(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,347 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "erb"
|
|
||||||
require "active_support/core_ext/module/redefine_method"
|
|
||||||
require "active_support/multibyte/unicode"
|
|
||||||
|
|
||||||
class ERB
|
|
||||||
module Util
|
|
||||||
HTML_ESCAPE = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" }
|
|
||||||
JSON_ESCAPE = { "&" => '\u0026', ">" => '\u003e', "<" => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
|
|
||||||
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
|
|
||||||
JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
|
|
||||||
|
|
||||||
# Following XML requirements: https://www.w3.org/TR/REC-xml/#NT-Name
|
|
||||||
TAG_NAME_START_REGEXP_SET = "@:A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}" \
|
|
||||||
"\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}" \
|
|
||||||
"\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}"
|
|
||||||
TAG_NAME_START_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}]/
|
|
||||||
TAG_NAME_FOLLOWING_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}\-.0-9\u{B7}\u{0300}-\u{036F}\u{203F}-\u{2040}]/
|
|
||||||
TAG_NAME_REPLACEMENT_CHAR = "_"
|
|
||||||
|
|
||||||
# A utility method for escaping HTML tag characters.
|
|
||||||
# This method is also aliased as <tt>h</tt>.
|
|
||||||
#
|
|
||||||
# puts html_escape('is a > 0 & a < 10?')
|
|
||||||
# # => is a > 0 & a < 10?
|
|
||||||
def html_escape(s)
|
|
||||||
unwrapped_html_escape(s).html_safe
|
|
||||||
end
|
|
||||||
|
|
||||||
silence_redefinition_of_method :h
|
|
||||||
alias h html_escape
|
|
||||||
|
|
||||||
module_function :h
|
|
||||||
|
|
||||||
singleton_class.silence_redefinition_of_method :html_escape
|
|
||||||
module_function :html_escape
|
|
||||||
|
|
||||||
# HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
|
|
||||||
# This method is not for public consumption! Seriously!
|
|
||||||
def unwrapped_html_escape(s) # :nodoc:
|
|
||||||
s = s.to_s
|
|
||||||
if s.html_safe?
|
|
||||||
s
|
|
||||||
else
|
|
||||||
CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
module_function :unwrapped_html_escape
|
|
||||||
|
|
||||||
# A utility method for escaping HTML without affecting existing escaped entities.
|
|
||||||
#
|
|
||||||
# html_escape_once('1 < 2 & 3')
|
|
||||||
# # => "1 < 2 & 3"
|
|
||||||
#
|
|
||||||
# html_escape_once('<< Accept & Checkout')
|
|
||||||
# # => "<< Accept & Checkout"
|
|
||||||
def html_escape_once(s)
|
|
||||||
result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
|
|
||||||
s.html_safe? ? result.html_safe : result
|
|
||||||
end
|
|
||||||
|
|
||||||
module_function :html_escape_once
|
|
||||||
|
|
||||||
# A utility method for escaping HTML entities in JSON strings. Specifically, the
|
|
||||||
# &, > and < characters are replaced with their equivalent unicode escaped form -
|
|
||||||
# \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
|
|
||||||
# escaped as they are treated as newline characters in some JavaScript engines.
|
|
||||||
# These sequences have identical meaning as the original characters inside the
|
|
||||||
# context of a JSON string, so assuming the input is a valid and well-formed
|
|
||||||
# JSON value, the output will have equivalent meaning when parsed:
|
|
||||||
#
|
|
||||||
# json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
|
|
||||||
# # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
|
|
||||||
#
|
|
||||||
# json_escape(json)
|
|
||||||
# # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
|
|
||||||
#
|
|
||||||
# JSON.parse(json) == JSON.parse(json_escape(json))
|
|
||||||
# # => true
|
|
||||||
#
|
|
||||||
# The intended use case for this method is to escape JSON strings before including
|
|
||||||
# them inside a script tag to avoid XSS vulnerability:
|
|
||||||
#
|
|
||||||
# <script>
|
|
||||||
# var currentUser = <%= raw json_escape(current_user.to_json) %>;
|
|
||||||
# </script>
|
|
||||||
#
|
|
||||||
# It is necessary to +raw+ the result of +json_escape+, so that quotation marks
|
|
||||||
# don't get converted to <tt>"</tt> entities. +json_escape+ doesn't
|
|
||||||
# automatically flag the result as HTML safe, since the raw value is unsafe to
|
|
||||||
# use inside HTML attributes.
|
|
||||||
#
|
|
||||||
# If your JSON is being used downstream for insertion into the DOM, be aware of
|
|
||||||
# whether or not it is being inserted via <tt>html()</tt>. Most jQuery plugins do this.
|
|
||||||
# If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
|
|
||||||
# content returned by your JSON.
|
|
||||||
#
|
|
||||||
# If you need to output JSON elsewhere in your HTML, you can just do something
|
|
||||||
# like this, as any unsafe characters (including quotation marks) will be
|
|
||||||
# automatically escaped for you:
|
|
||||||
#
|
|
||||||
# <div data-user-info="<%= current_user.to_json %>">...</div>
|
|
||||||
#
|
|
||||||
# WARNING: this helper only works with valid JSON. Using this on non-JSON values
|
|
||||||
# will open up serious XSS vulnerabilities. For example, if you replace the
|
|
||||||
# +current_user.to_json+ in the example above with user input instead, the browser
|
|
||||||
# will happily eval() that string as JavaScript.
|
|
||||||
#
|
|
||||||
# The escaping performed in this method is identical to those performed in the
|
|
||||||
# Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
|
|
||||||
# set to true. Because this transformation is idempotent, this helper can be
|
|
||||||
# applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
|
|
||||||
#
|
|
||||||
# Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
|
|
||||||
# is enabled, or if you are unsure where your JSON string originated from, it
|
|
||||||
# is recommended that you always apply this helper (other libraries, such as the
|
|
||||||
# JSON gem, do not provide this kind of protection by default; also some gems
|
|
||||||
# might override +to_json+ to bypass Active Support's encoder).
|
|
||||||
def json_escape(s)
|
|
||||||
result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
|
|
||||||
s.html_safe? ? result.html_safe : result
|
|
||||||
end
|
|
||||||
|
|
||||||
module_function :json_escape
|
|
||||||
|
|
||||||
# A utility method for escaping XML names of tags and names of attributes.
|
|
||||||
#
|
|
||||||
# xml_name_escape('1 < 2 & 3')
|
|
||||||
# # => "1___2___3"
|
|
||||||
#
|
|
||||||
# It follows the requirements of the specification: https://www.w3.org/TR/REC-xml/#NT-Name
|
|
||||||
def xml_name_escape(name)
|
|
||||||
name = name.to_s
|
|
||||||
return "" if name.blank?
|
|
||||||
|
|
||||||
starting_char = name[0].gsub(TAG_NAME_START_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
|
|
||||||
|
|
||||||
return starting_char if name.size == 1
|
|
||||||
|
|
||||||
following_chars = name[1..-1].gsub(TAG_NAME_FOLLOWING_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
|
|
||||||
|
|
||||||
starting_char + following_chars
|
|
||||||
end
|
|
||||||
module_function :xml_name_escape
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Object
|
|
||||||
def html_safe?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Numeric
|
|
||||||
def html_safe?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ActiveSupport #:nodoc:
|
|
||||||
class SafeBuffer < String
|
|
||||||
UNSAFE_STRING_METHODS = %w(
|
|
||||||
capitalize chomp chop delete delete_prefix delete_suffix
|
|
||||||
downcase lstrip next reverse rstrip scrub slice squeeze strip
|
|
||||||
succ swapcase tr tr_s unicode_normalize upcase
|
|
||||||
)
|
|
||||||
|
|
||||||
UNSAFE_STRING_METHODS_WITH_BACKREF = %w(gsub sub)
|
|
||||||
|
|
||||||
alias_method :original_concat, :concat
|
|
||||||
private :original_concat
|
|
||||||
|
|
||||||
# Raised when <tt>ActiveSupport::SafeBuffer#safe_concat</tt> is called on unsafe buffers.
|
|
||||||
class SafeConcatError < StandardError
|
|
||||||
def initialize
|
|
||||||
super "Could not concatenate to the buffer because it is not html safe."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def [](*args)
|
|
||||||
if html_safe?
|
|
||||||
new_string = super
|
|
||||||
|
|
||||||
return unless new_string
|
|
||||||
|
|
||||||
new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
|
|
||||||
new_safe_buffer.instance_variable_set :@html_safe, true
|
|
||||||
new_safe_buffer
|
|
||||||
else
|
|
||||||
to_str[*args]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def safe_concat(value)
|
|
||||||
raise SafeConcatError unless html_safe?
|
|
||||||
original_concat(value)
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(str = "")
|
|
||||||
@html_safe = true
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize_copy(other)
|
|
||||||
super
|
|
||||||
@html_safe = other.html_safe?
|
|
||||||
end
|
|
||||||
|
|
||||||
def clone_empty
|
|
||||||
self[0, 0]
|
|
||||||
end
|
|
||||||
|
|
||||||
def concat(value)
|
|
||||||
super(html_escape_interpolated_argument(value))
|
|
||||||
end
|
|
||||||
alias << concat
|
|
||||||
|
|
||||||
def bytesplice(*args, value)
|
|
||||||
super(*args, implicit_html_escape_interpolated_argument(value))
|
|
||||||
end
|
|
||||||
|
|
||||||
def insert(index, value)
|
|
||||||
super(index, html_escape_interpolated_argument(value))
|
|
||||||
end
|
|
||||||
|
|
||||||
def prepend(value)
|
|
||||||
super(html_escape_interpolated_argument(value))
|
|
||||||
end
|
|
||||||
|
|
||||||
def replace(value)
|
|
||||||
super(html_escape_interpolated_argument(value))
|
|
||||||
end
|
|
||||||
|
|
||||||
def []=(*args)
|
|
||||||
if args.length == 3
|
|
||||||
super(args[0], args[1], html_escape_interpolated_argument(args[2]))
|
|
||||||
else
|
|
||||||
super(args[0], html_escape_interpolated_argument(args[1]))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def +(other)
|
|
||||||
dup.concat(other)
|
|
||||||
end
|
|
||||||
|
|
||||||
def *(*)
|
|
||||||
new_string = super
|
|
||||||
new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
|
|
||||||
new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
|
|
||||||
new_safe_buffer
|
|
||||||
end
|
|
||||||
|
|
||||||
def %(args)
|
|
||||||
case args
|
|
||||||
when Hash
|
|
||||||
escaped_args = args.transform_values { |arg| html_escape_interpolated_argument(arg) }
|
|
||||||
else
|
|
||||||
escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) }
|
|
||||||
end
|
|
||||||
|
|
||||||
self.class.new(super(escaped_args))
|
|
||||||
end
|
|
||||||
|
|
||||||
def html_safe?
|
|
||||||
defined?(@html_safe) && @html_safe
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_param
|
|
||||||
to_str
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_with(coder)
|
|
||||||
coder.represent_object nil, to_str
|
|
||||||
end
|
|
||||||
|
|
||||||
UNSAFE_STRING_METHODS.each do |unsafe_method|
|
|
||||||
if unsafe_method.respond_to?(unsafe_method)
|
|
||||||
class_eval <<-EOT, __FILE__, __LINE__ + 1
|
|
||||||
def #{unsafe_method}(*args, &block) # def capitalize(*args, &block)
|
|
||||||
to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block)
|
|
||||||
end # end
|
|
||||||
|
|
||||||
def #{unsafe_method}!(*args) # def capitalize!(*args)
|
|
||||||
@html_safe = false # @html_safe = false
|
|
||||||
super # super
|
|
||||||
end # end
|
|
||||||
EOT
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
UNSAFE_STRING_METHODS_WITH_BACKREF.each do |unsafe_method|
|
|
||||||
if unsafe_method.respond_to?(unsafe_method)
|
|
||||||
class_eval <<-EOT, __FILE__, __LINE__ + 1
|
|
||||||
def #{unsafe_method}(*args, &block) # def gsub(*args, &block)
|
|
||||||
if block # if block
|
|
||||||
to_str.#{unsafe_method}(*args) { |*params| # to_str.gsub(*args) { |*params|
|
|
||||||
set_block_back_references(block, $~) # set_block_back_references(block, $~)
|
|
||||||
block.call(*params) # block.call(*params)
|
|
||||||
} # }
|
|
||||||
else # else
|
|
||||||
to_str.#{unsafe_method}(*args) # to_str.gsub(*args)
|
|
||||||
end # end
|
|
||||||
end # end
|
|
||||||
|
|
||||||
def #{unsafe_method}!(*args, &block) # def gsub!(*args, &block)
|
|
||||||
@html_safe = false # @html_safe = false
|
|
||||||
if block # if block
|
|
||||||
super(*args) { |*params| # super(*args) { |*params|
|
|
||||||
set_block_back_references(block, $~) # set_block_back_references(block, $~)
|
|
||||||
block.call(*params) # block.call(*params)
|
|
||||||
} # }
|
|
||||||
else # else
|
|
||||||
super # super
|
|
||||||
end # end
|
|
||||||
end # end
|
|
||||||
EOT
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def html_escape_interpolated_argument(arg)
|
|
||||||
(!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_block_back_references(block, match_data)
|
|
||||||
block.binding.eval("proc { |m| $~ = m }").call(match_data)
|
|
||||||
rescue ArgumentError
|
|
||||||
# Can't create binding from C level Proc
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class String
|
|
||||||
# Marks a string as trusted safe. It will be inserted into HTML with no
|
|
||||||
# additional escaping performed. It is your responsibility to ensure that the
|
|
||||||
# string contains no malicious content. This method is equivalent to the
|
|
||||||
# +raw+ helper in views. It is recommended that you use +sanitize+ instead of
|
|
||||||
# this method. It should never be called on user input.
|
|
||||||
def html_safe
|
|
||||||
ActiveSupport::SafeBuffer.new(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class String
|
|
||||||
alias :starts_with? :start_with?
|
|
||||||
alias :ends_with? :end_with?
|
|
||||||
end
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class String
|
|
||||||
# Strips indentation in heredocs.
|
|
||||||
#
|
|
||||||
# For example in
|
|
||||||
#
|
|
||||||
# if options[:usage]
|
|
||||||
# puts <<-USAGE.strip_heredoc
|
|
||||||
# This command does such and such.
|
|
||||||
#
|
|
||||||
# Supported options are:
|
|
||||||
# -h This message
|
|
||||||
# ...
|
|
||||||
# USAGE
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# the user would see the usage message aligned against the left margin.
|
|
||||||
#
|
|
||||||
# Technically, it looks for the least indented non-empty line
|
|
||||||
# in the whole string, and removes that amount of leading whitespace.
|
|
||||||
def strip_heredoc
|
|
||||||
gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "").tap do |stripped|
|
|
||||||
stripped.freeze if frozen?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/string/conversions"
|
|
||||||
require "active_support/core_ext/time/zones"
|
|
||||||
|
|
||||||
class String
|
|
||||||
# Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default
|
|
||||||
# is set, otherwise converts String to a Time via String#to_time
|
|
||||||
def in_time_zone(zone = ::Time.zone)
|
|
||||||
if zone
|
|
||||||
::Time.find_zone!(zone).parse(self)
|
|
||||||
else
|
|
||||||
to_time
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/symbol/starts_ends_with"
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Symbol
|
|
||||||
def start_with?(*prefixes)
|
|
||||||
to_s.start_with?(*prefixes)
|
|
||||||
end unless method_defined?(:start_with?)
|
|
||||||
|
|
||||||
def end_with?(*suffixes)
|
|
||||||
to_s.end_with?(*suffixes)
|
|
||||||
end unless method_defined?(:end_with?)
|
|
||||||
|
|
||||||
alias :starts_with? :start_with?
|
|
||||||
alias :ends_with? :end_with?
|
|
||||||
end
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/time/acts_like"
|
|
||||||
require "active_support/core_ext/time/calculations"
|
|
||||||
require "active_support/core_ext/time/compatibility"
|
|
||||||
require "active_support/core_ext/time/conversions"
|
|
||||||
require "active_support/core_ext/time/zones"
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/object/acts_like"
|
|
||||||
|
|
||||||
class Time
|
|
||||||
# Duck-types as a Time-like class. See Object#acts_like?.
|
|
||||||
def acts_like_time?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,365 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/duration"
|
|
||||||
require "active_support/core_ext/time/conversions"
|
|
||||||
require "active_support/time_with_zone"
|
|
||||||
require "active_support/core_ext/time/zones"
|
|
||||||
require "active_support/core_ext/date_and_time/calculations"
|
|
||||||
require "active_support/core_ext/date/calculations"
|
|
||||||
require "active_support/core_ext/module/remove_method"
|
|
||||||
|
|
||||||
class Time
|
|
||||||
include DateAndTime::Calculations
|
|
||||||
|
|
||||||
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
||||||
|
|
||||||
class << self
|
|
||||||
# Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances
|
|
||||||
def ===(other)
|
|
||||||
super || (self == Time && other.is_a?(ActiveSupport::TimeWithZone))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the number of days in the given month.
|
|
||||||
# If no year is specified, it will use the current year.
|
|
||||||
def days_in_month(month, year = current.year)
|
|
||||||
if month == 2 && ::Date.gregorian_leap?(year)
|
|
||||||
29
|
|
||||||
else
|
|
||||||
COMMON_YEAR_DAYS_IN_MONTH[month]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the number of days in the given year.
|
|
||||||
# If no year is specified, it will use the current year.
|
|
||||||
def days_in_year(year = current.year)
|
|
||||||
days_in_month(2, year) + 337
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns <tt>Time.zone.now</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns <tt>Time.now</tt>.
|
|
||||||
def current
|
|
||||||
::Time.zone ? ::Time.zone.now : ::Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
# Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime
|
|
||||||
# instances can be used when called with a single argument
|
|
||||||
def at_with_coercion(*args)
|
|
||||||
return at_without_coercion(*args) if args.size != 1
|
|
||||||
|
|
||||||
# Time.at can be called with a time or numerical value
|
|
||||||
time_or_number = args.first
|
|
||||||
|
|
||||||
if time_or_number.is_a?(ActiveSupport::TimeWithZone)
|
|
||||||
at_without_coercion(time_or_number.to_r).getlocal
|
|
||||||
elsif time_or_number.is_a?(DateTime)
|
|
||||||
at_without_coercion(time_or_number.to_f).getlocal
|
|
||||||
else
|
|
||||||
at_without_coercion(time_or_number)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ruby2_keywords(:at_with_coercion) if respond_to?(:ruby2_keywords, true)
|
|
||||||
alias_method :at_without_coercion, :at
|
|
||||||
alias_method :at, :at_with_coercion
|
|
||||||
|
|
||||||
# Creates a +Time+ instance from an RFC 3339 string.
|
|
||||||
#
|
|
||||||
# Time.rfc3339('1999-12-31T14:00:00-10:00') # => 2000-01-01 00:00:00 -1000
|
|
||||||
#
|
|
||||||
# If the time or offset components are missing then an +ArgumentError+ will be raised.
|
|
||||||
#
|
|
||||||
# Time.rfc3339('1999-12-31') # => ArgumentError: invalid date
|
|
||||||
def rfc3339(str)
|
|
||||||
parts = Date._rfc3339(str)
|
|
||||||
|
|
||||||
raise ArgumentError, "invalid date" if parts.empty?
|
|
||||||
|
|
||||||
Time.new(
|
|
||||||
parts.fetch(:year),
|
|
||||||
parts.fetch(:mon),
|
|
||||||
parts.fetch(:mday),
|
|
||||||
parts.fetch(:hour),
|
|
||||||
parts.fetch(:min),
|
|
||||||
parts.fetch(:sec) + parts.fetch(:sec_fraction, 0),
|
|
||||||
parts.fetch(:offset)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the number of seconds since 00:00:00.
|
|
||||||
#
|
|
||||||
# Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0.0
|
|
||||||
# Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296.0
|
|
||||||
# Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399.0
|
|
||||||
def seconds_since_midnight
|
|
||||||
to_i - change(hour: 0).to_i + (usec / 1.0e+6)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the number of seconds until 23:59:59.
|
|
||||||
#
|
|
||||||
# Time.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399
|
|
||||||
# Time.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103
|
|
||||||
# Time.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+
|
|
||||||
#
|
|
||||||
# Time.new(2012, 8, 29, 0, 0, 0.5).sec_fraction # => (1/2)
|
|
||||||
def sec_fraction
|
|
||||||
subsec
|
|
||||||
end
|
|
||||||
|
|
||||||
unless Time.method_defined?(:floor)
|
|
||||||
def floor(precision = 0)
|
|
||||||
change(nsec: 0) + subsec.floor(precision)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Restricted Ruby version due to a bug in `Time#ceil`
|
|
||||||
# See https://bugs.ruby-lang.org/issues/17025 for more details
|
|
||||||
if RUBY_VERSION <= "2.8"
|
|
||||||
remove_possible_method :ceil
|
|
||||||
def ceil(precision = 0)
|
|
||||||
change(nsec: 0) + subsec.ceil(precision)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a new Time where one or more of the elements have been changed according
|
|
||||||
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
|
|
||||||
# <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only
|
|
||||||
# the hour is passed, then minute, sec, usec and nsec is set to 0. If the hour
|
|
||||||
# and minute is passed, then sec, usec and nsec is set to 0. The +options+ parameter
|
|
||||||
# takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>,
|
|
||||||
# <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>,
|
|
||||||
# <tt>:offset</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both.
|
|
||||||
#
|
|
||||||
# Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0)
|
|
||||||
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0)
|
|
||||||
# Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0)
|
|
||||||
def change(options)
|
|
||||||
new_year = options.fetch(:year, year)
|
|
||||||
new_month = options.fetch(:month, month)
|
|
||||||
new_day = options.fetch(:day, day)
|
|
||||||
new_hour = options.fetch(:hour, hour)
|
|
||||||
new_min = options.fetch(:min, options[:hour] ? 0 : min)
|
|
||||||
new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
|
|
||||||
new_offset = options.fetch(:offset, nil)
|
|
||||||
|
|
||||||
if new_nsec = options[:nsec]
|
|
||||||
raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
|
|
||||||
new_usec = Rational(new_nsec, 1000)
|
|
||||||
else
|
|
||||||
new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
|
|
||||||
end
|
|
||||||
|
|
||||||
raise ArgumentError, "argument out of range" if new_usec >= 1000000
|
|
||||||
|
|
||||||
new_sec += Rational(new_usec, 1000000)
|
|
||||||
|
|
||||||
if new_offset
|
|
||||||
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, new_offset)
|
|
||||||
elsif utc?
|
|
||||||
::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec)
|
|
||||||
elsif zone&.respond_to?(:utc_to_local)
|
|
||||||
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, zone)
|
|
||||||
elsif zone
|
|
||||||
::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec)
|
|
||||||
else
|
|
||||||
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, utc_offset)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Uses Date to provide precise Time calculations for years, months, and days
|
|
||||||
# according to the proleptic Gregorian calendar. The +options+ parameter
|
|
||||||
# takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>,
|
|
||||||
# <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>,
|
|
||||||
# <tt>:seconds</tt>.
|
|
||||||
#
|
|
||||||
# Time.new(2015, 8, 1, 14, 35, 0).advance(seconds: 1) # => 2015-08-01 14:35:01 -0700
|
|
||||||
# Time.new(2015, 8, 1, 14, 35, 0).advance(minutes: 1) # => 2015-08-01 14:36:00 -0700
|
|
||||||
# Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700
|
|
||||||
# Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700
|
|
||||||
# Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700
|
|
||||||
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.gregorian.advance(options)
|
|
||||||
time_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?
|
|
||||||
time_advanced_by_date
|
|
||||||
else
|
|
||||||
time_advanced_by_date.since(seconds_to_advance)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
|
|
||||||
def ago(seconds)
|
|
||||||
since(-seconds)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a new Time representing the time a number of seconds since the instance time
|
|
||||||
def since(seconds)
|
|
||||||
self + seconds
|
|
||||||
rescue
|
|
||||||
to_datetime.since(seconds)
|
|
||||||
end
|
|
||||||
alias :in :since
|
|
||||||
|
|
||||||
# Returns a new Time 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 Time 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 Time representing the end of the day, 23:59:59.999999
|
|
||||||
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 Time representing the start of the hour (x:00)
|
|
||||||
def beginning_of_hour
|
|
||||||
change(min: 0)
|
|
||||||
end
|
|
||||||
alias :at_beginning_of_hour :beginning_of_hour
|
|
||||||
|
|
||||||
# Returns a new Time representing the end of the hour, x:59:59.999999
|
|
||||||
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 Time representing the start of the minute (x:xx:00)
|
|
||||||
def beginning_of_minute
|
|
||||||
change(sec: 0)
|
|
||||||
end
|
|
||||||
alias :at_beginning_of_minute :beginning_of_minute
|
|
||||||
|
|
||||||
# Returns a new Time representing the end of the minute, x:xx:59.999999
|
|
||||||
def end_of_minute
|
|
||||||
change(
|
|
||||||
sec: 59,
|
|
||||||
usec: Rational(999999999, 1000)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
alias :at_end_of_minute :end_of_minute
|
|
||||||
|
|
||||||
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
|
|
||||||
other.until(self)
|
|
||||||
else
|
|
||||||
minus_without_duration(other)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
alias_method :minus_without_duration, :-
|
|
||||||
alias_method :-, :minus_with_duration
|
|
||||||
|
|
||||||
# Time#- can also be used to determine the number of seconds between two Time instances.
|
|
||||||
# We're layering on additional behavior so that ActiveSupport::TimeWithZone instances
|
|
||||||
# are coerced into values that Time#- will recognize
|
|
||||||
def minus_with_coercion(other)
|
|
||||||
other = other.comparable_time if other.respond_to?(:comparable_time)
|
|
||||||
other.is_a?(DateTime) ? to_f - other.to_f : minus_without_coercion(other)
|
|
||||||
end
|
|
||||||
alias_method :minus_without_coercion, :-
|
|
||||||
alias_method :-, :minus_with_coercion
|
|
||||||
|
|
||||||
# Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances
|
|
||||||
# can be chronologically compared with a Time
|
|
||||||
def compare_with_coercion(other)
|
|
||||||
# we're avoiding Time#to_datetime and Time#to_time because they're expensive
|
|
||||||
if other.class == Time
|
|
||||||
compare_without_coercion(other)
|
|
||||||
elsif other.is_a?(Time)
|
|
||||||
compare_without_coercion(other.to_time)
|
|
||||||
else
|
|
||||||
to_datetime <=> other
|
|
||||||
end
|
|
||||||
end
|
|
||||||
alias_method :compare_without_coercion, :<=>
|
|
||||||
alias_method :<=>, :compare_with_coercion
|
|
||||||
|
|
||||||
# Layers additional behavior on Time#eql? so that ActiveSupport::TimeWithZone instances
|
|
||||||
# can be eql? to an equivalent Time
|
|
||||||
def eql_with_coercion(other)
|
|
||||||
# if other is an ActiveSupport::TimeWithZone, coerce a Time instance from it so we can do eql? comparison
|
|
||||||
other = other.comparable_time if other.respond_to?(:comparable_time)
|
|
||||||
eql_without_coercion(other)
|
|
||||||
end
|
|
||||||
alias_method :eql_without_coercion, :eql?
|
|
||||||
alias_method :eql?, :eql_with_coercion
|
|
||||||
|
|
||||||
# Returns a new time the specified number of days ago.
|
|
||||||
def prev_day(days = 1)
|
|
||||||
advance(days: -days)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a new time the specified number of days in the future.
|
|
||||||
def next_day(days = 1)
|
|
||||||
advance(days: days)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a new time the specified number of months ago.
|
|
||||||
def prev_month(months = 1)
|
|
||||||
advance(months: -months)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a new time the specified number of months in the future.
|
|
||||||
def next_month(months = 1)
|
|
||||||
advance(months: months)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a new time the specified number of years ago.
|
|
||||||
def prev_year(years = 1)
|
|
||||||
advance(years: -years)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a new time the specified number of years in the future.
|
|
||||||
def next_year(years = 1)
|
|
||||||
advance(years: years)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/date_and_time/compatibility"
|
|
||||||
require "active_support/core_ext/module/redefine_method"
|
|
||||||
|
|
||||||
class Time
|
|
||||||
include DateAndTime::Compatibility
|
|
||||||
|
|
||||||
silence_redefinition_of_method :to_time
|
|
||||||
|
|
||||||
# Either return +self+ or the time in the local system timezone depending
|
|
||||||
# on the setting of +ActiveSupport.to_time_preserves_timezone+.
|
|
||||||
def to_time
|
|
||||||
preserve_timezone ? self : getlocal
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "time"
|
|
||||||
require "active_support/inflector/methods"
|
|
||||||
require "active_support/values/time_zone"
|
|
||||||
|
|
||||||
class Time
|
|
||||||
DATE_FORMATS = {
|
|
||||||
db: "%Y-%m-%d %H:%M:%S",
|
|
||||||
inspect: "%Y-%m-%d %H:%M:%S.%9N %z",
|
|
||||||
number: "%Y%m%d%H%M%S",
|
|
||||||
nsec: "%Y%m%d%H%M%S%9N",
|
|
||||||
usec: "%Y%m%d%H%M%S%6N",
|
|
||||||
time: "%H:%M",
|
|
||||||
short: "%d %b %H:%M",
|
|
||||||
long: "%B %d, %Y %H:%M",
|
|
||||||
long_ordinal: lambda { |time|
|
|
||||||
day_format = ActiveSupport::Inflector.ordinalize(time.day)
|
|
||||||
time.strftime("%B #{day_format}, %Y %H:%M")
|
|
||||||
},
|
|
||||||
rfc822: lambda { |time|
|
|
||||||
offset_format = time.formatted_offset(false)
|
|
||||||
time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}")
|
|
||||||
},
|
|
||||||
iso8601: lambda { |time| time.iso8601 }
|
|
||||||
}
|
|
||||||
|
|
||||||
# Converts to a formatted string. See DATE_FORMATS for built-in formats.
|
|
||||||
#
|
|
||||||
# This method is aliased to <tt>to_s</tt>.
|
|
||||||
#
|
|
||||||
# time = Time.now # => 2007-01-18 06:10:17 -06:00
|
|
||||||
#
|
|
||||||
# time.to_formatted_s(:time) # => "06:10"
|
|
||||||
# time.to_s(:time) # => "06:10"
|
|
||||||
#
|
|
||||||
# time.to_formatted_s(:db) # => "2007-01-18 06:10:17"
|
|
||||||
# time.to_formatted_s(:number) # => "20070118061017"
|
|
||||||
# time.to_formatted_s(:short) # => "18 Jan 06:10"
|
|
||||||
# time.to_formatted_s(:long) # => "January 18, 2007 06:10"
|
|
||||||
# time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10"
|
|
||||||
# time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
|
|
||||||
# time.to_formatted_s(:iso8601) # => "2007-01-18T06:10:17-06:00"
|
|
||||||
#
|
|
||||||
# == Adding your own time formats to +to_formatted_s+
|
|
||||||
# You can add your own formats 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 argument as the value.
|
|
||||||
#
|
|
||||||
# # config/initializers/time_formats.rb
|
|
||||||
# Time::DATE_FORMATS[:month_and_year] = '%B %Y'
|
|
||||||
# Time::DATE_FORMATS[:short_ordinal] = ->(time) { time.strftime("%B #{time.day.ordinalize}") }
|
|
||||||
def to_formatted_s(format = :default)
|
|
||||||
if formatter = 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
|
|
||||||
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.
|
|
||||||
#
|
|
||||||
# Time.local(2000).formatted_offset # => "-06:00"
|
|
||||||
# Time.local(2000).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
|
|
||||||
|
|
||||||
# Aliased to +xmlschema+ for compatibility with +DateTime+
|
|
||||||
alias_method :rfc3339, :xmlschema
|
|
||||||
end
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/time_with_zone"
|
|
||||||
require "active_support/core_ext/time/acts_like"
|
|
||||||
require "active_support/core_ext/date_and_time/zones"
|
|
||||||
|
|
||||||
class Time
|
|
||||||
include DateAndTime::Zones
|
|
||||||
class << self
|
|
||||||
attr_accessor :zone_default
|
|
||||||
|
|
||||||
# Returns the TimeZone for the current request, if this has been set (via Time.zone=).
|
|
||||||
# If <tt>Time.zone</tt> has not been set for the current request, returns the TimeZone specified in <tt>config.time_zone</tt>.
|
|
||||||
def zone
|
|
||||||
Thread.current[:time_zone] || zone_default
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sets <tt>Time.zone</tt> to a TimeZone object for the current request/thread.
|
|
||||||
#
|
|
||||||
# This method accepts any of the following:
|
|
||||||
#
|
|
||||||
# * A Rails TimeZone object.
|
|
||||||
# * An identifier for a Rails TimeZone object (e.g., "Eastern Time (US & Canada)", <tt>-5.hours</tt>).
|
|
||||||
# * A TZInfo::Timezone object.
|
|
||||||
# * An identifier for a TZInfo::Timezone object (e.g., "America/New_York").
|
|
||||||
#
|
|
||||||
# Here's an example of how you might set <tt>Time.zone</tt> on a per request basis and reset it when the request is done.
|
|
||||||
# <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone:
|
|
||||||
#
|
|
||||||
# class ApplicationController < ActionController::Base
|
|
||||||
# around_action :set_time_zone
|
|
||||||
#
|
|
||||||
# def set_time_zone
|
|
||||||
# if logged_in?
|
|
||||||
# Time.use_zone(current_user.time_zone) { yield }
|
|
||||||
# else
|
|
||||||
# yield
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
def zone=(time_zone)
|
|
||||||
Thread.current[:time_zone] = find_zone!(time_zone)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Allows override of <tt>Time.zone</tt> locally inside supplied block;
|
|
||||||
# resets <tt>Time.zone</tt> to existing value when done.
|
|
||||||
#
|
|
||||||
# class ApplicationController < ActionController::Base
|
|
||||||
# around_action :set_time_zone
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
#
|
|
||||||
# def set_time_zone
|
|
||||||
# Time.use_zone(current_user.timezone) { yield }
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt>
|
|
||||||
# objects that have already been created, e.g. any model timestamp
|
|
||||||
# attributes that have been read before the block will remain in
|
|
||||||
# the application's default timezone.
|
|
||||||
def use_zone(time_zone)
|
|
||||||
new_zone = find_zone!(time_zone)
|
|
||||||
begin
|
|
||||||
old_zone, ::Time.zone = ::Time.zone, new_zone
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
::Time.zone = old_zone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a TimeZone instance matching the time zone provided.
|
|
||||||
# Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
|
|
||||||
# Raises an +ArgumentError+ for invalid time zones.
|
|
||||||
#
|
|
||||||
# Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
|
|
||||||
# Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...>
|
|
||||||
# Time.find_zone! -5.hours # => #<ActiveSupport::TimeZone @name="Bogota" ...>
|
|
||||||
# Time.find_zone! nil # => nil
|
|
||||||
# Time.find_zone! false # => false
|
|
||||||
# Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE
|
|
||||||
def find_zone!(time_zone)
|
|
||||||
if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
|
|
||||||
time_zone
|
|
||||||
else
|
|
||||||
# Look up the timezone based on the identifier (unless we've been
|
|
||||||
# passed a TZInfo::Timezone)
|
|
||||||
unless time_zone.respond_to?(:period_for_local)
|
|
||||||
time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone
|
|
||||||
if time_zone.is_a?(ActiveSupport::TimeZone)
|
|
||||||
time_zone
|
|
||||||
else
|
|
||||||
ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
rescue TZInfo::InvalidTimezoneIdentifier
|
|
||||||
raise ArgumentError, "Invalid Timezone: #{time_zone}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a TimeZone instance matching the time zone provided.
|
|
||||||
# Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
|
|
||||||
# Returns +nil+ for invalid time zones.
|
|
||||||
#
|
|
||||||
# Time.find_zone "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
|
|
||||||
# Time.find_zone "NOT-A-TIMEZONE" # => nil
|
|
||||||
def find_zone(time_zone)
|
|
||||||
find_zone!(time_zone) rescue nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "uri"
|
|
||||||
|
|
||||||
if RUBY_VERSION < "2.6.0"
|
|
||||||
require "active_support/core_ext/module/redefine_method"
|
|
||||||
URI::Parser.class_eval do
|
|
||||||
silence_redefinition_of_method :unescape
|
|
||||||
def unescape(str, escaped = /%[a-fA-F\d]{2}/)
|
|
||||||
# TODO: Are we actually sure that ASCII == UTF-8?
|
|
||||||
# YK: My initial experiments say yes, but let's be sure please
|
|
||||||
enc = str.encoding
|
|
||||||
enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
|
|
||||||
str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(escaped) { |match| [match[1, 2].hex].pack("C") }.force_encoding(enc)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module URI
|
|
||||||
class << self
|
|
||||||
def parser
|
|
||||||
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
|
||||||
URI.parser is deprecated and will be removed in Rails 7.0.
|
|
||||||
Use `URI::DEFAULT_PARSER` instead.
|
|
||||||
MSG
|
|
||||||
URI::DEFAULT_PARSER
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,210 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/callbacks"
|
|
||||||
require "active_support/core_ext/enumerable"
|
|
||||||
require "active_support/core_ext/module/delegation"
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
# Abstract super class that provides a thread-isolated attributes singleton, which resets automatically
|
|
||||||
# before and after each request. This allows you to keep all the per-request attributes easily
|
|
||||||
# available to the whole system.
|
|
||||||
#
|
|
||||||
# The following full app-like example demonstrates how to use a Current class to
|
|
||||||
# facilitate easy access to the global, per-request attributes without passing them deeply
|
|
||||||
# around everywhere:
|
|
||||||
#
|
|
||||||
# # app/models/current.rb
|
|
||||||
# class Current < ActiveSupport::CurrentAttributes
|
|
||||||
# attribute :account, :user
|
|
||||||
# attribute :request_id, :user_agent, :ip_address
|
|
||||||
#
|
|
||||||
# resets { Time.zone = nil }
|
|
||||||
#
|
|
||||||
# def user=(user)
|
|
||||||
# super
|
|
||||||
# self.account = user.account
|
|
||||||
# Time.zone = user.time_zone
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # app/controllers/concerns/authentication.rb
|
|
||||||
# module Authentication
|
|
||||||
# extend ActiveSupport::Concern
|
|
||||||
#
|
|
||||||
# included do
|
|
||||||
# before_action :authenticate
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
# def authenticate
|
|
||||||
# if authenticated_user = User.find_by(id: cookies.encrypted[:user_id])
|
|
||||||
# Current.user = authenticated_user
|
|
||||||
# else
|
|
||||||
# redirect_to new_session_url
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # app/controllers/concerns/set_current_request_details.rb
|
|
||||||
# module SetCurrentRequestDetails
|
|
||||||
# extend ActiveSupport::Concern
|
|
||||||
#
|
|
||||||
# included do
|
|
||||||
# before_action do
|
|
||||||
# Current.request_id = request.uuid
|
|
||||||
# Current.user_agent = request.user_agent
|
|
||||||
# Current.ip_address = request.ip
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class ApplicationController < ActionController::Base
|
|
||||||
# include Authentication
|
|
||||||
# include SetCurrentRequestDetails
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class MessagesController < ApplicationController
|
|
||||||
# def create
|
|
||||||
# Current.account.messages.create(message_params)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class Message < ApplicationRecord
|
|
||||||
# belongs_to :creator, default: -> { Current.user }
|
|
||||||
# after_create { |message| Event.create(record: message) }
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class Event < ApplicationRecord
|
|
||||||
# before_create do
|
|
||||||
# self.request_id = Current.request_id
|
|
||||||
# self.user_agent = Current.user_agent
|
|
||||||
# self.ip_address = Current.ip_address
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result.
|
|
||||||
# Current should only be used for a few, top-level globals, like account, user, and request details.
|
|
||||||
# The attributes stuck in Current should be used by more or less all actions on all requests. If you start
|
|
||||||
# sticking controller-specific attributes in there, you're going to create a mess.
|
|
||||||
class CurrentAttributes
|
|
||||||
include ActiveSupport::Callbacks
|
|
||||||
define_callbacks :reset
|
|
||||||
|
|
||||||
class << self
|
|
||||||
# Returns singleton instance for this class in this thread. If none exists, one is created.
|
|
||||||
def instance
|
|
||||||
current_instances[current_instances_key] ||= new
|
|
||||||
end
|
|
||||||
|
|
||||||
# Declares one or more attributes that will be given both class and instance accessor methods.
|
|
||||||
def attribute(*names)
|
|
||||||
generated_attribute_methods.module_eval do
|
|
||||||
names.each do |name|
|
|
||||||
define_method(name) do
|
|
||||||
attributes[name.to_sym]
|
|
||||||
end
|
|
||||||
|
|
||||||
define_method("#{name}=") do |attribute|
|
|
||||||
attributes[name.to_sym] = attribute
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
names.each do |name|
|
|
||||||
define_singleton_method(name) do
|
|
||||||
instance.public_send(name)
|
|
||||||
end
|
|
||||||
|
|
||||||
define_singleton_method("#{name}=") do |attribute|
|
|
||||||
instance.public_send("#{name}=", attribute)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Calls this block before #reset is called on the instance. Used for resetting external collaborators that depend on current values.
|
|
||||||
def before_reset(&block)
|
|
||||||
set_callback :reset, :before, &block
|
|
||||||
end
|
|
||||||
|
|
||||||
# Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone.
|
|
||||||
def resets(&block)
|
|
||||||
set_callback :reset, :after, &block
|
|
||||||
end
|
|
||||||
alias_method :after_reset, :resets
|
|
||||||
|
|
||||||
delegate :set, :reset, to: :instance
|
|
||||||
|
|
||||||
def reset_all # :nodoc:
|
|
||||||
current_instances.each_value(&:reset)
|
|
||||||
end
|
|
||||||
|
|
||||||
def clear_all # :nodoc:
|
|
||||||
reset_all
|
|
||||||
current_instances.clear
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def generated_attribute_methods
|
|
||||||
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_instances
|
|
||||||
Thread.current[:current_attributes_instances] ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_instances_key
|
|
||||||
@current_instances_key ||= name.to_sym
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_missing(name, *args, &block)
|
|
||||||
# Caches the method definition as a singleton method of the receiver.
|
|
||||||
#
|
|
||||||
# By letting #delegate handle it, we avoid an enclosure that'll capture args.
|
|
||||||
singleton_class.delegate name, to: :instance
|
|
||||||
|
|
||||||
send(name, *args, &block)
|
|
||||||
end
|
|
||||||
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_accessor :attributes
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@attributes = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Expose one or more attributes within a block. Old values are returned after the block concludes.
|
|
||||||
# Example demonstrating the common use of needing to set Current attributes outside the request-cycle:
|
|
||||||
#
|
|
||||||
# class Chat::PublicationJob < ApplicationJob
|
|
||||||
# def perform(attributes, room_number, creator)
|
|
||||||
# Current.set(person: creator) do
|
|
||||||
# Chat::Publisher.publish(attributes: attributes, room_number: room_number)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
def set(set_attributes)
|
|
||||||
old_attributes = compute_attributes(set_attributes.keys)
|
|
||||||
assign_attributes(set_attributes)
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
assign_attributes(old_attributes)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Reset all attributes. Should be called before and after actions, when used as a per-request singleton.
|
|
||||||
def reset
|
|
||||||
run_callbacks :reset do
|
|
||||||
self.attributes = {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def assign_attributes(new_attributes)
|
|
||||||
new_attributes.each { |key, value| public_send("#{key}=", value) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def compute_attributes(keys)
|
|
||||||
keys.index_with { |key| public_send(key) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ActiveSupport::CurrentAttributes::TestHelper # :nodoc:
|
|
||||||
def before_setup
|
|
||||||
ActiveSupport::CurrentAttributes.reset_all
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def after_teardown
|
|
||||||
super
|
|
||||||
ActiveSupport::CurrentAttributes.reset_all
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "singleton"
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
# \Deprecation specifies the API used by Rails to deprecate methods, instance
|
|
||||||
# variables, objects and constants.
|
|
||||||
class Deprecation
|
|
||||||
# active_support.rb sets an autoload for ActiveSupport::Deprecation.
|
|
||||||
#
|
|
||||||
# If these requires were at the top of the file the constant would not be
|
|
||||||
# defined by the time their files were loaded. Since some of them reopen
|
|
||||||
# ActiveSupport::Deprecation its autoload would be triggered, resulting in
|
|
||||||
# a circular require warning for active_support/deprecation.rb.
|
|
||||||
#
|
|
||||||
# So, we define the constant first, and load dependencies later.
|
|
||||||
require "active_support/deprecation/instance_delegator"
|
|
||||||
require "active_support/deprecation/behaviors"
|
|
||||||
require "active_support/deprecation/reporting"
|
|
||||||
require "active_support/deprecation/disallowed"
|
|
||||||
require "active_support/deprecation/constant_accessor"
|
|
||||||
require "active_support/deprecation/method_wrappers"
|
|
||||||
require "active_support/deprecation/proxy_wrappers"
|
|
||||||
require "active_support/core_ext/module/deprecation"
|
|
||||||
require "concurrent/atomic/thread_local_var"
|
|
||||||
|
|
||||||
include Singleton
|
|
||||||
include InstanceDelegator
|
|
||||||
include Behavior
|
|
||||||
include Reporting
|
|
||||||
include Disallowed
|
|
||||||
include MethodWrapper
|
|
||||||
|
|
||||||
# The version number in which the deprecated behavior will be removed, by default.
|
|
||||||
attr_accessor :deprecation_horizon
|
|
||||||
|
|
||||||
# It accepts two parameters on initialization. The first is a version of library
|
|
||||||
# and the second is a library name.
|
|
||||||
#
|
|
||||||
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
|
|
||||||
def initialize(deprecation_horizon = "7.0", gem_name = "Rails")
|
|
||||||
self.gem_name = gem_name
|
|
||||||
self.deprecation_horizon = deprecation_horizon
|
|
||||||
# By default, warnings are not silenced and debugging is off.
|
|
||||||
self.silenced = false
|
|
||||||
self.debug = false
|
|
||||||
@silenced_thread = Concurrent::ThreadLocalVar.new(false)
|
|
||||||
@explicitly_allowed_warnings = Concurrent::ThreadLocalVar.new(nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/notifications"
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
# Raised when <tt>ActiveSupport::Deprecation::Behavior#behavior</tt> is set with <tt>:raise</tt>.
|
|
||||||
# You would set <tt>:raise</tt>, as a behavior to raise errors and proactively report exceptions from deprecations.
|
|
||||||
class DeprecationException < StandardError
|
|
||||||
end
|
|
||||||
|
|
||||||
class Deprecation
|
|
||||||
# Default warning behaviors per Rails.env.
|
|
||||||
DEFAULT_BEHAVIORS = {
|
|
||||||
raise: ->(message, callstack, deprecation_horizon, gem_name) {
|
|
||||||
e = DeprecationException.new(message)
|
|
||||||
e.set_backtrace(callstack.map(&:to_s))
|
|
||||||
raise e
|
|
||||||
},
|
|
||||||
|
|
||||||
stderr: ->(message, callstack, deprecation_horizon, gem_name) {
|
|
||||||
$stderr.puts(message)
|
|
||||||
$stderr.puts callstack.join("\n ") if debug
|
|
||||||
},
|
|
||||||
|
|
||||||
log: ->(message, callstack, deprecation_horizon, gem_name) {
|
|
||||||
logger =
|
|
||||||
if defined?(Rails.logger) && Rails.logger
|
|
||||||
Rails.logger
|
|
||||||
else
|
|
||||||
require "active_support/logger"
|
|
||||||
ActiveSupport::Logger.new($stderr)
|
|
||||||
end
|
|
||||||
logger.warn message
|
|
||||||
logger.debug callstack.join("\n ") if debug
|
|
||||||
},
|
|
||||||
|
|
||||||
notify: ->(message, callstack, deprecation_horizon, gem_name) {
|
|
||||||
notification_name = "deprecation.#{gem_name.underscore.tr('/', '_')}"
|
|
||||||
ActiveSupport::Notifications.instrument(notification_name,
|
|
||||||
message: message,
|
|
||||||
callstack: callstack,
|
|
||||||
gem_name: gem_name,
|
|
||||||
deprecation_horizon: deprecation_horizon)
|
|
||||||
},
|
|
||||||
|
|
||||||
silence: ->(message, callstack, deprecation_horizon, gem_name) { },
|
|
||||||
}
|
|
||||||
|
|
||||||
# Behavior module allows to determine how to display deprecation messages.
|
|
||||||
# You can create a custom behavior or set any from the +DEFAULT_BEHAVIORS+
|
|
||||||
# constant. Available behaviors are:
|
|
||||||
#
|
|
||||||
# [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>.
|
|
||||||
# [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>.
|
|
||||||
# [+log+] Log all deprecation warnings to +Rails.logger+.
|
|
||||||
# [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
|
|
||||||
# [+silence+] Do nothing.
|
|
||||||
#
|
|
||||||
# Setting behaviors only affects deprecations that happen after boot time.
|
|
||||||
# For more information you can read the documentation of the +behavior=+ method.
|
|
||||||
module Behavior
|
|
||||||
# Whether to print a backtrace along with the warning.
|
|
||||||
attr_accessor :debug
|
|
||||||
|
|
||||||
# Returns the current behavior or if one isn't set, defaults to +:stderr+.
|
|
||||||
def behavior
|
|
||||||
@behavior ||= [DEFAULT_BEHAVIORS[:stderr]]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the current behavior for disallowed deprecations or if one isn't set, defaults to +:raise+.
|
|
||||||
def disallowed_behavior
|
|
||||||
@disallowed_behavior ||= [DEFAULT_BEHAVIORS[:raise]]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sets the behavior to the specified value. Can be a single value, array,
|
|
||||||
# or an object that responds to +call+.
|
|
||||||
#
|
|
||||||
# Available behaviors:
|
|
||||||
#
|
|
||||||
# [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>.
|
|
||||||
# [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>.
|
|
||||||
# [+log+] Log all deprecation warnings to +Rails.logger+.
|
|
||||||
# [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
|
|
||||||
# [+silence+] Do nothing.
|
|
||||||
#
|
|
||||||
# Setting behaviors only affects deprecations that happen after boot time.
|
|
||||||
# Deprecation warnings raised by gems are not affected by this setting
|
|
||||||
# because they happen before Rails boots up.
|
|
||||||
#
|
|
||||||
# ActiveSupport::Deprecation.behavior = :stderr
|
|
||||||
# ActiveSupport::Deprecation.behavior = [:stderr, :log]
|
|
||||||
# ActiveSupport::Deprecation.behavior = MyCustomHandler
|
|
||||||
# ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) {
|
|
||||||
# # custom stuff
|
|
||||||
# }
|
|
||||||
def behavior=(behavior)
|
|
||||||
@behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sets the behavior for disallowed deprecations (those configured by
|
|
||||||
# ActiveSupport::Deprecation.disallowed_warnings=) to the specified
|
|
||||||
# value. As with +behavior=+, this can be a single value, array, or an
|
|
||||||
# object that responds to +call+.
|
|
||||||
def disallowed_behavior=(behavior)
|
|
||||||
@disallowed_behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) }
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def arity_coerce(behavior)
|
|
||||||
unless behavior.respond_to?(:call)
|
|
||||||
raise ArgumentError, "#{behavior.inspect} is not a valid deprecation behavior."
|
|
||||||
end
|
|
||||||
|
|
||||||
if behavior.arity == 4 || behavior.arity == -1
|
|
||||||
behavior
|
|
||||||
else
|
|
||||||
-> message, callstack, _, _ { behavior.call(message, callstack) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
class Deprecation
|
|
||||||
# DeprecatedConstantAccessor transforms a constant into a deprecated one by
|
|
||||||
# hooking +const_missing+.
|
|
||||||
#
|
|
||||||
# It takes the names of an old (deprecated) constant and of a new constant
|
|
||||||
# (both in string form) and optionally a deprecator. The deprecator defaults
|
|
||||||
# to +ActiveSupport::Deprecator+ if none is specified.
|
|
||||||
#
|
|
||||||
# The deprecated constant now returns the same object as the new one rather
|
|
||||||
# than a proxy object, so it can be used transparently in +rescue+ blocks
|
|
||||||
# etc.
|
|
||||||
#
|
|
||||||
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
|
|
||||||
#
|
|
||||||
# # (In a later update, the original implementation of `PLANETS` has been removed.)
|
|
||||||
#
|
|
||||||
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
|
|
||||||
# include ActiveSupport::Deprecation::DeprecatedConstantAccessor
|
|
||||||
# deprecate_constant 'PLANETS', 'PLANETS_POST_2006'
|
|
||||||
#
|
|
||||||
# PLANETS.map { |planet| planet.capitalize }
|
|
||||||
# # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
|
|
||||||
# (Backtrace information…)
|
|
||||||
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
|
|
||||||
module DeprecatedConstantAccessor
|
|
||||||
def self.included(base)
|
|
||||||
require "active_support/inflector/methods"
|
|
||||||
|
|
||||||
extension = Module.new do
|
|
||||||
def const_missing(missing_const_name)
|
|
||||||
if class_variable_defined?(:@@_deprecated_constants)
|
|
||||||
if (replacement = class_variable_get(:@@_deprecated_constants)[missing_const_name.to_s])
|
|
||||||
replacement[:deprecator].warn(replacement[:message] || "#{name}::#{missing_const_name} is deprecated! Use #{replacement[:new]} instead.", caller_locations)
|
|
||||||
return ActiveSupport::Inflector.constantize(replacement[:new].to_s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def deprecate_constant(const_name, new_constant, message: nil, deprecator: ActiveSupport::Deprecation.instance)
|
|
||||||
class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants)
|
|
||||||
class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
base.singleton_class.prepend extension
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
class Deprecation
|
|
||||||
module Disallowed
|
|
||||||
# Sets the criteria used to identify deprecation messages which should be
|
|
||||||
# disallowed. Can be an array containing strings, symbols, or regular
|
|
||||||
# expressions. (Symbols are treated as strings). These are compared against
|
|
||||||
# the text of the generated deprecation warning.
|
|
||||||
#
|
|
||||||
# Additionally the scalar symbol +:all+ may be used to treat all
|
|
||||||
# deprecations as disallowed.
|
|
||||||
#
|
|
||||||
# Deprecations matching a substring or regular expression will be handled
|
|
||||||
# using the configured +ActiveSupport::Deprecation.disallowed_behavior+
|
|
||||||
# rather than +ActiveSupport::Deprecation.behavior+
|
|
||||||
attr_writer :disallowed_warnings
|
|
||||||
|
|
||||||
# Returns the configured criteria used to identify deprecation messages
|
|
||||||
# which should be treated as disallowed.
|
|
||||||
def disallowed_warnings
|
|
||||||
@disallowed_warnings ||= []
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def deprecation_disallowed?(message)
|
|
||||||
disallowed = ActiveSupport::Deprecation.disallowed_warnings
|
|
||||||
return false if explicitly_allowed?(message)
|
|
||||||
return true if disallowed == :all
|
|
||||||
disallowed.any? do |rule|
|
|
||||||
case rule
|
|
||||||
when String, Symbol
|
|
||||||
message.include?(rule.to_s)
|
|
||||||
when Regexp
|
|
||||||
rule.match?(message)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def explicitly_allowed?(message)
|
|
||||||
allowances = @explicitly_allowed_warnings.value
|
|
||||||
return false unless allowances
|
|
||||||
return true if allowances == :all
|
|
||||||
allowances = [allowances] unless allowances.kind_of?(Array)
|
|
||||||
allowances.any? do |rule|
|
|
||||||
case rule
|
|
||||||
when String, Symbol
|
|
||||||
message.include?(rule.to_s)
|
|
||||||
when Regexp
|
|
||||||
rule.match?(message)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "active_support/core_ext/module/delegation"
|
|
||||||
|
|
||||||
module ActiveSupport
|
|
||||||
class Deprecation
|
|
||||||
module InstanceDelegator # :nodoc:
|
|
||||||
def self.included(base)
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
base.singleton_class.prepend(OverrideDelegators)
|
|
||||||
base.public_class_method :new
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods # :nodoc:
|
|
||||||
def include(included_module)
|
|
||||||
included_module.instance_methods.each { |m| method_added(m) }
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_added(method_name)
|
|
||||||
singleton_class.delegate(method_name, to: :instance)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module OverrideDelegators # :nodoc:
|
|
||||||
def warn(message = nil, callstack = nil)
|
|
||||||
callstack ||= caller_locations(2)
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
|
|
||||||
caller_backtrace ||= caller_locations(2)
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
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