class Torque::PostgreSQL::Attributes::Builder::Period

TODO: Allow documenting by building the methods outside and importing only the raw string

Constants

CURRENT_GETTERS
DIRECT_ACCESS_REGEX
SUPPORTED_TYPES
TYPE_CASTERS

Attributes

attribute[RW]
current_getter[RW]
default[RW]
dynamic_threshold[RW]
instance_module[RW]
klass[RW]
klass_module[RW]
options[RW]
threshold[RW]
type[RW]
type_caster[RW]

Public Class Methods

new(klass, attribute, options) click to toggle source

Start a new builder of methods for period values on ActiveRecord::Base

# File lib/torque/postgresql/attributes/builder/period.rb, line 29
          def initialize(klass, attribute, options)
            @klass     = klass
            @attribute = attribute.to_s
            @options   = options
            @type      = klass.attribute_types[@attribute].type

            raise ArgumentError, <<-MSG.squish unless SUPPORTED_TYPES.include?(type)
              Period cannot be generated for #{attribute} because its type
              #{type} is not supported. Only #{SUPPORTED_TYPES.join(', ')} are supported.
            MSG

            @current_getter = CURRENT_GETTERS[type]
            @type_caster    = TYPE_CASTERS[type]

            @default        = options[:pessimistic].blank?
          end

Public Instance Methods

build() click to toggle source

Create all methods needed

# File lib/torque/postgresql/attributes/builder/period.rb, line 104
def build
  @klass_module = Module.new
  @instance_module = Module.new

  value_args      = ['value']
  left_right_args = ['left', 'right = nil']

  ## Klass methods
  build_method_helper :klass, :current_on,                 value_args            # 00
  build_method_helper :klass, :current                                           # 01
  build_method_helper :klass, :not_current                                       # 02
  build_method_helper :klass, :containing,                 value_args            # 03
  build_method_helper :klass, :not_containing,             value_args            # 04
  build_method_helper :klass, :overlapping,                left_right_args       # 05
  build_method_helper :klass, :not_overlapping,            left_right_args       # 06
  build_method_helper :klass, :starting_after,             value_args            # 07
  build_method_helper :klass, :starting_before,            value_args            # 08
  build_method_helper :klass, :finishing_after,            value_args            # 09
  build_method_helper :klass, :finishing_before,           value_args            # 10

  if threshold.present?
    build_method_helper :klass, :real_containing,          value_args            # 11
    build_method_helper :klass, :real_overlapping,         left_right_args       # 12
    build_method_helper :klass, :real_starting_after,      value_args            # 13
    build_method_helper :klass, :real_starting_before,     value_args            # 14
    build_method_helper :klass, :real_finishing_after,     value_args            # 15
    build_method_helper :klass, :real_finishing_before,    value_args            # 16
  end

  unless type.eql?(:daterange)
    build_method_helper :klass, :containing_date,          value_args            # 17
    build_method_helper :klass, :not_containing_date,      value_args            # 18
    build_method_helper :klass, :overlapping_date,         left_right_args       # 19
    build_method_helper :klass, :not_overlapping_date,     left_right_args       # 20

    if threshold.present?
      build_method_helper :klass, :real_containing_date,   value_args            # 21
      build_method_helper :klass, :real_overlapping_date,  left_right_args       # 22
    end
  end

  ## Instance methods
  build_method_helper :instance, :current?                                       # 23
  build_method_helper :instance, :current_on?,             value_args            # 24
  build_method_helper :instance, :start                                          # 25
  build_method_helper :instance, :finish                                         # 26

  if threshold.present?
    build_method_helper :instance, :real                                         # 27
    build_method_helper :instance, :real_start                                   # 28
    build_method_helper :instance, :real_finish                                  # 29
  end

  klass.extend klass_module
  klass.include instance_module
end
build_method_helper(type, key, args = []) click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 161
def build_method_helper(type, key, args = [])
  method_name = method_names[key]
  return if method_name.nil?

  method_content = send("#{type}_#{key}")
  method_content = define_string_method(method_name, method_content, args)

  source_module = send("#{type}_module")
  source_module.module_eval(method_content)
end
conflicting?() click to toggle source

Check if any of the methods that will be created get in conflict with the base class methods

# File lib/torque/postgresql/attributes/builder/period.rb, line 90
          def conflicting?
            return if options[:force] == true

            klass_method_names.values.each { |name| dangerous?(name, true) }
            instance_method_names.values.each { |name| dangerous?(name) }
          rescue Interrupt => err
            raise ArgumentError, <<-MSG.squish
              #{subtype.class.name} was not able to generate requested
              methods because the method #{err} already exists in
              #{klass.name}.
            MSG
          end
instance_method_names() click to toggle source

Get the list of methods associated withe the instances

# File lib/torque/postgresql/attributes/builder/period.rb, line 84
def instance_method_names
  @instance_method_names ||= method_names.to_a[23..29].to_h
end
klass_method_names() click to toggle source

Get the list of methods associated withe the class

# File lib/torque/postgresql/attributes/builder/period.rb, line 79
def klass_method_names
  @klass_method_names ||= method_names.to_a[0..22].to_h
end
method_names() click to toggle source

Generate all the method names

# File lib/torque/postgresql/attributes/builder/period.rb, line 74
def method_names
  @method_names ||= default_method_names.merge(options.fetch(:methods, {}))
end

Private Instance Methods

arel_attribute() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 206
def arel_attribute
  @arel_attribute ||= "arel_table[#{attribute.inspect}]"
end
arel_check_condition(type) click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 311
def arel_check_condition(type)
  checker = arel_nullif(arel_real_attribute, arel_empty_value)
  checker << ".#{type}(value.cast(#{type_caster.inspect}))"
  arel_coalesce(checker, arel_default_sql)
end
arel_coalesce(*args) click to toggle source

Create an arel version of coalesce function

# File lib/torque/postgresql/attributes/builder/period.rb, line 292
def arel_coalesce(*args)
  arel_named_function('coalesce', *args)
end
arel_convert_to_type(left, right = nil, set_type = nil) click to toggle source

Create an arel version of the type with the following values

# File lib/torque/postgresql/attributes/builder/period.rb, line 275
def arel_convert_to_type(left, right = nil, set_type = nil)
  arel_named_function(set_type || type, left, right || left)
end
arel_daterange(real = false) click to toggle source

Convert timestamp range to date range format

# File lib/torque/postgresql/attributes/builder/period.rb, line 302
def arel_daterange(real = false)
  arel_named_function(
    'daterange',
    (real ? arel_real_start_at : arel_start_at) + '.cast(:date)',
    (real ? arel_real_finish_at : arel_finish_at) + '.cast(:date)',
    '::Arel.sql("\'[]\'")',
  )
end
arel_default_sql() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 210
def arel_default_sql
  @arel_default_sql ||= arel_sql_quote(@default.inspect)
end
arel_empty_value() click to toggle source

Create an arel version of an empty value for the range

# File lib/torque/postgresql/attributes/builder/period.rb, line 297
def arel_empty_value
  arel_convert_to_type('::Arel.sql(\'NULL\')')
end
arel_finish_at() click to toggle source

Finish at version of the value

# File lib/torque/postgresql/attributes/builder/period.rb, line 242
def arel_finish_at
  @arel_finish_at ||= arel_named_function('upper', arel_attribute)
end
arel_formatting_left_right(condition, set_type = nil, cast: nil) click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 328
def arel_formatting_left_right(condition, set_type = nil, cast: nil)
  [
    arel_formatting_value(nil, 'left', cast: cast),
    '',
    'if right.present?',
    '  ' + arel_formatting_value(nil, 'right', cast: cast),
    "  value = #{arel_convert_to_type('left', 'right', set_type)}",
    'else',
    '  value = left',
    'end',
    '',
    condition,
  ].join("\n")
end
arel_formatting_value(condition = nil, value = 'value', cast: nil) click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 317
def arel_formatting_value(condition = nil, value = 'value', cast: nil)
  [
    "#{value} = arel_table[#{value}] if #{value}.is_a?(Symbol)",
    "unless #{value}.respond_to?(:cast)",
    "  #{value} = ::Arel.sql(connection.quote(#{value}))",
    ("  #{value} = #{value}.cast(#{cast.inspect})" if cast),
    'end',
    condition,
  ].compact.join("\n")
end
arel_named_function(name, *args) click to toggle source

Create an arel named function

# File lib/torque/postgresql/attributes/builder/period.rb, line 280
def arel_named_function(name, *args)
  result = +"::Arel::Nodes::NamedFunction.new(#{name.to_s.inspect}"
  result << ', [' << args.join(', ') << ']' if args.present?
  result << ')'
end
arel_nullif(*args) click to toggle source

Create an arel version of nullif function

# File lib/torque/postgresql/attributes/builder/period.rb, line 287
def arel_nullif(*args)
  arel_named_function('nullif', *args)
end
arel_real_attribute() click to toggle source

When the time has a threshold, then the real attribute is complex

# File lib/torque/postgresql/attributes/builder/period.rb, line 267
def arel_real_attribute
  return arel_attribute unless threshold.present?
  @arel_real_attribute ||= arel_named_function(
    type, arel_real_start_at, arel_real_finish_at,
  )
end
arel_real_finish_at() click to toggle source

Finish at version of the value with threshold

# File lib/torque/postgresql/attributes/builder/period.rb, line 257
def arel_real_finish_at
  return arel_finish_at unless threshold.present?
  @arel_real_finish_at ||= begin
    result = +"(#{arel_finish_at} + #{arel_threshold_value})"
    result << '.cast(:date)' if  type.eql?(:daterange)
    result
  end
end
arel_real_start_at() click to toggle source

Start at version of the value with threshold

# File lib/torque/postgresql/attributes/builder/period.rb, line 247
def arel_real_start_at
  return arel_start_at unless threshold.present?
  @arel_real_start_at ||= begin
    result = +"(#{arel_start_at} - #{arel_threshold_value})"
    result << '.cast(:date)' if  type.eql?(:daterange)
    result
  end
end
arel_sql_quote(value) click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 214
def arel_sql_quote(value)
  "::Arel.sql(connection.quote(#{value}))"
end
arel_start_at() click to toggle source

Start at version of the value

# File lib/torque/postgresql/attributes/builder/period.rb, line 237
def arel_start_at
  @arel_start_at ||= arel_named_function('lower', arel_attribute)
end
arel_threshold_value() click to toggle source

Check how to provide the threshold value

# File lib/torque/postgresql/attributes/builder/period.rb, line 219
def arel_threshold_value
  @arel_threshold_value ||= begin
    case threshold
    when Symbol, String
      "arel_attribute('#{threshold}')"
    when ActiveSupport::Duration
      value = "'#{threshold.to_i} seconds'"
      "::Arel.sql(\"#{value}\").cast(:interval)"
    when Numeric
      value = threshold.to_i.to_s
      value << type_caster.eql?(:date) ? ' days' : ' seconds'
      value = "'#{value}'"
      "::Arel.sql(\"#{value}\").cast(:interval)"
    end
  end
end
dangerous?(method_name, class_method = false) click to toggle source

Check if the method already exists in the reference class

# File lib/torque/postgresql/attributes/builder/period.rb, line 187
def dangerous?(method_name, class_method = false)
  if class_method
    if klass.dangerous_class_method?(method_name)
      raise Interrupt, method_name.to_s
    end
  else
    if klass.dangerous_attribute_method?(method_name)
      raise Interrupt, method_name.to_s
    end
  end
end
default_method_names() click to toggle source

Generates the default method names

# File lib/torque/postgresql/attributes/builder/period.rb, line 175
def default_method_names
  list = Torque::PostgreSQL.config.period.method_names.dup

  if options.fetch(:prefixed, true)
    list.transform_values { |value| format(value, attribute) }
  else
    list = list.merge(Torque::PostgreSQL.config.period.direct_method_names)
    list.transform_values { |value| value.gsub(DIRECT_ACCESS_REGEX, '') }
  end
end
define_string_method(name, body, args = []) click to toggle source

BUILDER HELPERS

# File lib/torque/postgresql/attributes/builder/period.rb, line 200
def define_string_method(name, body, args = [])
  headline = "def #{name}"
  headline += "(#{args.join(', ')})"
  [headline, body, 'end'].join("\n")
end
instance_current?() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 448
def instance_current?
  "#{method_names[:current_on?]}(#{current_getter})"
end
instance_current_on?() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 452
def instance_current_on?
  attr_value = threshold.present? ? method_names[:real] : attribute
  default_value = default.inspect
  [
    "return #{default_value} if #{attr_value}.nil?",
    "return #{default_value} if #{attr_value}.min.try(:infinite?)",
    "return #{default_value} if #{attr_value}.max.try(:infinite?)",
    "#{attr_value}.min < value && #{attr_value}.max > value",
  ].join("\n")
end
instance_finish() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 467
def instance_finish
  "#{attribute}&.max"
end
instance_real() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 471
def instance_real
  left = method_names[:real_start]
  right = method_names[:real_finish]

  [
    "left = #{left}",
    "right = #{right}",
    'return unless left || right',
    '((left || -::Float::INFINITY)..(right || ::Float::INFINITY))',
  ].join("\n")
end
instance_real_finish() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 497
def instance_real_finish
  suffix = type.eql?(:daterange) ? '.to_date' : ''
  threshold_value = threshold.is_a?(Symbol) \
    ? threshold.to_s \
    : threshold.to_i.to_s + '.seconds'

  [
    "return if #{method_names[:finish]}.nil?",
    "value = #{method_names[:finish]}",
    "value += (#{threshold_value} || 0)",
    "value#{suffix}"
  ].join("\n")
end
instance_real_start() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 483
def instance_real_start
  suffix = type.eql?(:daterange) ? '.to_date' : ''
  threshold_value = threshold.is_a?(Symbol) \
    ? threshold.to_s \
    : threshold.to_i.to_s + '.seconds'

  [
    "return if #{method_names[:start]}.nil?",
    "value = #{method_names[:start]}",
    "value -= (#{threshold_value} || 0)",
    "value#{suffix}"
  ].join("\n")
end
instance_start() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 463
def instance_start
  "#{attribute}&.min"
end
klass_containing() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 362
def klass_containing
  arel_formatting_value("where(#{arel_attribute}.contains(value))")
end
klass_containing_date() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 418
def klass_containing_date
  arel_formatting_value("where(#{arel_daterange}.contains(value))",
    cast: :date)
end
klass_current() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 348
def klass_current
  [
    "value = #{arel_sql_quote(current_getter)}",
    "where(#{arel_check_condition(:contains)})",
  ].join("\n")
end
klass_current_on() click to toggle source

METHOD BUILDERS

# File lib/torque/postgresql/attributes/builder/period.rb, line 344
def klass_current_on
  arel_formatting_value("where(#{arel_check_condition(:contains)})")
end
klass_finishing_after() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 386
def klass_finishing_after
  arel_formatting_value("where((#{arel_finish_at}).gt(value))")
end
klass_finishing_before() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 390
def klass_finishing_before
  arel_formatting_value("where((#{arel_finish_at}).lt(value))")
end
klass_not_containing() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 366
def klass_not_containing
  arel_formatting_value("where.not(#{arel_attribute}.contains(value))")
end
klass_not_containing_date() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 423
def klass_not_containing_date
  arel_formatting_value("where.not(#{arel_daterange}.contains(value))",
    cast: :date)
end
klass_not_current() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 355
def klass_not_current
  [
    "value = #{arel_sql_quote(current_getter)}",
    "where.not(#{arel_check_condition(:contains)})",
  ].join("\n")
end
klass_not_overlapping() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 374
def klass_not_overlapping
  arel_formatting_left_right("where.not(#{arel_attribute}.overlaps(value))")
end
klass_not_overlapping_date() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 433
def klass_not_overlapping_date
  arel_formatting_left_right("where.not(#{arel_daterange}.overlaps(value))",
    :daterange, cast: :date)
end
klass_overlapping() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 370
def klass_overlapping
  arel_formatting_left_right("where(#{arel_attribute}.overlaps(value))")
end
klass_overlapping_date() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 428
def klass_overlapping_date
  arel_formatting_left_right("where(#{arel_daterange}.overlaps(value))",
    :daterange, cast: :date)
end
klass_real_containing() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 394
def klass_real_containing
  arel_formatting_value("where(#{arel_real_attribute}.contains(value))")
end
klass_real_containing_date() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 438
def klass_real_containing_date
  arel_formatting_value("where(#{arel_daterange(true)}.contains(value))",
    cast: :date)
end
klass_real_finishing_after() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 410
def klass_real_finishing_after
  arel_formatting_value("where(#{arel_real_finish_at}.gt(value))")
end
klass_real_finishing_before() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 414
def klass_real_finishing_before
  arel_formatting_value("where(#{arel_real_finish_at}.lt(value))")
end
klass_real_overlapping() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 398
def klass_real_overlapping
  arel_formatting_left_right("where(#{arel_real_attribute}.overlaps(value))")
end
klass_real_overlapping_date() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 443
def klass_real_overlapping_date
  arel_formatting_left_right("where(#{arel_daterange(true)}.overlaps(value))",
    :daterange, cast: :date)
end
klass_real_starting_after() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 402
def klass_real_starting_after
  arel_formatting_value("where(#{arel_real_start_at}.gt(value))")
end
klass_real_starting_before() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 406
def klass_real_starting_before
  arel_formatting_value("where(#{arel_real_start_at}.lt(value))")
end
klass_starting_after() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 378
def klass_starting_after
  arel_formatting_value("where((#{arel_start_at}).gt(value))")
end
klass_starting_before() click to toggle source
# File lib/torque/postgresql/attributes/builder/period.rb, line 382
def klass_starting_before
  arel_formatting_value("where((#{arel_start_at}).lt(value))")
end