# File lib/validates_overlap/overlap_validator.rb, line 103 def primary_key(record) record.class.primary_key end
class OverlapValidator
Constants
- BEGIN_OF_UNIX_TIME
- END_OF_UNIX_TIME
Attributes
Public Class Methods
# File lib/validates_overlap/overlap_validator.rb, line 13 def initialize(args) attributes_are_range(args[:attributes]) super end
Public Instance Methods
# 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 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 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
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
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
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
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
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 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
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
# 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
# File lib/validates_overlap/overlap_validator.rb, line 56 def get_overlapped scoped_model.where([sql_conditions, sql_values]) end
# 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
# 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
# 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
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
# File lib/validates_overlap/overlap_validator.rb, line 107 def primary_key_value(primary_key_name, record) record.send(primary_key_name) end
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
# 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 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
# 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