class OverlapValidator

Constants

BEGIN_OF_UNIX_TIME
END_OF_UNIX_TIME

Attributes

scoped_model[RW]
sql_conditions[RW]
sql_values[RW]

Public Class Methods

new(args) click to toggle source
Calls superclass method
# File lib/validates_overlap/overlap_validator.rb, line 13
def initialize(args)
  attributes_are_range(args[:attributes])

  super
end

Public Instance Methods

validate(record) click to toggle source
# File lib/validates_overlap/overlap_validator.rb, line 19
def validate(record)
  initialize_query(record, options)
  if overlapped_exists?
    if options[:load_overlapped]
      record.instance_variable_set(:@overlapped_records, get_overlapped)
    end

    if record.respond_to? attributes.first
      if options[:message_title].is_a?(Array)
        options[:message_title].each do |key|
          record.errors.add(key, options[:message_content] || :overlap)
        end
      else
        record.errors.add(options[:message_title] || attributes.first, options[:message_content] || :overlap)
      end
    else
      record.errors.add(options[:message_title] || :base, options[:message_content] || :overlap)
    end
  end
end

Protected Instance Methods

add_attribute(record, attr_name, value = nil) click to toggle source

Add attribute and his value to sql condition

# File lib/validates_overlap/overlap_validator.rb, line 159
def add_attribute(record, attr_name, value = nil)
  _value = resolve_attribute_value(record, attr_name, value)
  operator = if _value.nil?
               ' IS NULL'
             elsif _value.is_a?(Array)
               ' IN (:%s)'
             else
               ' = :%s'
  end

  self.sql_conditions += " AND #{attribute_to_sql(attr_name, record)} #{operator}" % value_attribute_name(attr_name)
  sql_values.merge!(:"#{value_attribute_name(attr_name)}" => _value)
end
add_attributes(record, attrs) click to toggle source

Add attributes and values to sql conditions. helps to use with scope options, so scope can be added as this forms :scope => “user_id” or :scope => [“user_id”, “place_id”]

# File lib/validates_overlap/overlap_validator.rb, line 146
def add_attributes(record, attrs)
  if attrs.is_a?(Array)
    attrs.each { |attr| add_attribute(record, attr) }
  elsif attrs.is_a?(Hash)
    attrs.each do |attr_name, value|
      add_attribute(record, attr_name, value)
    end
  else
    add_attribute(record, attrs)
  end
end
add_query_options(methods) click to toggle source

Allow to use scope, joins, includes methods before querying

Example:

validates_overlap :date_from, :date_to, :query_options => {:includes => “visits”}

# File lib/validates_overlap/overlap_validator.rb, line 203
def add_query_options(methods)
  methods.each do |method_name, params|
    self.scoped_model = scoped_model.send(method_name.to_sym, *params)
  end
end
attribute_to_sql(attr, record) click to toggle source

Prepare attribute name to use in sql conditions created in form 'table_name.attribute_name'

# File lib/validates_overlap/overlap_validator.rb, line 85
def attribute_to_sql(attr, record)
  if attr.to_s.include?('.')
    attr
  else
    "#{record_table_name(record)}.#{attr}"
  end
end
attributes_are_range(attributes) click to toggle source

Check if the validation of time range is defined by 2 attributes

# File lib/validates_overlap/overlap_validator.rb, line 99
def attributes_are_range(attributes)
  fail 'Validation of time range must be defined by 2 attributes' unless attributes.size == 2
end
attributes_to_sql(record) click to toggle source

Prepare attribute names to use in sql conditions return array in form ['meetings.starts_at', 'meetings.ends_at']

# File lib/validates_overlap/overlap_validator.rb, line 80
def attributes_to_sql(record)
  attributes.map { |attr| attribute_to_sql(attr, record) }
end
condition_string(starts_at_attr, ends_at_attr) click to toggle source

Return the condition string depend on exclude_edges option.

# File lib/validates_overlap/overlap_validator.rb, line 134
def condition_string(starts_at_attr, ends_at_attr)
  except_option = Array(options[:exclude_edges]).map(&:to_s)
  starts_at_sign = except_option.include?(starts_at_attr.to_s.split('.').last) ? '<' : '<='
  ends_at_sign = except_option.include?(ends_at_attr.to_s.split('.').last) ? '>' : '>='
  query = []
  query << "(#{ends_at_attr} IS NULL OR #{ends_at_attr} #{ends_at_sign} :starts_at_value)"
  query << "(#{starts_at_attr} IS NULL OR #{starts_at_attr} #{starts_at_sign} :ends_at_value)"
  query.join(' AND ')
end
generate_overlap_sql_conditions(record) click to toggle source

Generate sql condition for time range cross

# File lib/validates_overlap/overlap_validator.rb, line 112
def generate_overlap_sql_conditions(record)
  starts_at_attr, ends_at_attr = attributes_to_sql(record)
  main_condition = condition_string(starts_at_attr, ends_at_attr)
  primary_key_name = primary_key(record)
  key = primary_key_value(primary_key_name, record)
  if record.new_record?
    self.sql_conditions = main_condition
  else
    self.sql_conditions = "#{main_condition} AND #{record_table_name(record)}.#{primary_key(record)} !="
    self.sql_conditions +=   key.is_a?(String) ? "'#{key}'" : key.to_s
  end
end
generate_overlap_sql_values(record) click to toggle source

Return hash of values for overlap sql condition

# File lib/validates_overlap/overlap_validator.rb, line 126
def generate_overlap_sql_values(record)
  starts_at_value, ends_at_value = resolve_values_from_attributes(record)
  starts_at_value += options.fetch(:start_shift) { 0 } if starts_at_value && options
  ends_at_value += options.fetch(:end_shift) { 0 } if ends_at_value && options
  self.sql_values = { starts_at_value: starts_at_value || BEGIN_OF_UNIX_TIME, ends_at_value: ends_at_value || END_OF_UNIX_TIME }
end
get_assoc_value(record, attr) click to toggle source
# File lib/validates_overlap/overlap_validator.rb, line 72
def get_assoc_value(record, attr)
  assoc, attr_name = attr.to_s.split('.')
  assoc_name = assoc.singularize.to_sym
  assoc_obj = record.send(assoc_name) if record.respond_to?(assoc_name)
  (assoc_obj || record).send(attr_name.to_sym)
end
get_overlapped() click to toggle source
# File lib/validates_overlap/overlap_validator.rb, line 56
def get_overlapped
  scoped_model.where([sql_conditions, sql_values])
end
implement_enum?() click to toggle source
# File lib/validates_overlap/overlap_validator.rb, line 196
def implement_enum?
  (ActiveRecord::VERSION::MAJOR >= 5) || (ActiveRecord::VERSION::MAJOR > 4 && ActiveRecord::VERSION::MINOR > 1)
end
initialize_query(record, options = {}) click to toggle source
# File lib/validates_overlap/overlap_validator.rb, line 42
def initialize_query(record, options = {})
  scoped_model = options[:scoped_model].present? ? options[:scoped_model].constantize : record.class
  self.scoped_model = scoped_model.default_scoped
  generate_overlap_sql_values(record)
  generate_overlap_sql_conditions(record)
  add_attributes(record, options[:scope]) if options && options[:scope].present?
  add_query_options(options[:query_options]) if options && options[:query_options].present?
end
is_enum_attribute?(record, attr_name) click to toggle source
# File lib/validates_overlap/overlap_validator.rb, line 192
def is_enum_attribute?(record, attr_name)
  implement_enum? && record.class.defined_enums[attr_name.to_s].present?
end
overlapped_exists?() click to toggle source

Check if exists at least one record in DB which is overlapped with current record

# File lib/validates_overlap/overlap_validator.rb, line 52
def overlapped_exists?
  scoped_model.exists?([sql_conditions, sql_values])
end
primary_key(record) click to toggle source
# File lib/validates_overlap/overlap_validator.rb, line 103
def primary_key(record)
  record.class.primary_key
end
primary_key_value(primary_key_name, record) click to toggle source
# File lib/validates_overlap/overlap_validator.rb, line 107
def primary_key_value(primary_key_name, record)
  record.send(primary_key_name)
end
record_table_name(record) click to toggle source

Get the table name for the record

# File lib/validates_overlap/overlap_validator.rb, line 94
def record_table_name(record)
  record.class.table_name
end
resolve_attribute_value(record, attr_name, value = nil) click to toggle source
# File lib/validates_overlap/overlap_validator.rb, line 178
def resolve_attribute_value(record, attr_name, value = nil)
  if value
    value.is_a?(Proc) ? value.call(record) : value
  else
    value = record.read_attribute(attr_name)

    if is_enum_attribute?(record, attr_name)
      value = record.class.defined_enums[attr_name][value]
    end

    value
  end
end
resolve_values_from_attributes(record) click to toggle source

Resolve attributes values from record to use in sql conditions return array in form ['2011-01-10', '2011-02-20']

# File lib/validates_overlap/overlap_validator.rb, line 62
def resolve_values_from_attributes(record)
  attributes.map do |attr|
    if attr.to_s.include?('.')
      get_assoc_value(record, attr)
    else
      record.send(attr.to_sym)
    end
  end
end
value_attribute_name(attr_name) click to toggle source
# File lib/validates_overlap/overlap_validator.rb, line 173
def value_attribute_name(attr_name)
  name = attr_name.to_s.include?('.') ? attr_name.to_s.gsub('.', '_') : attr_name.to_s
  name + '_value'
end