class ActsAsSpan::NoOverlapValidator
Validator that checks whether a record is overlapping with others
Takes options `:instance_scope` (optional) and `:scope` (required):
-
`instance_scope` is a proc which, when evaluated by the record, returns a boolean value. When false, the validatior will not check for overlap. When true, the validator checks normally.
-
`scope` is also a proc. This is must return an ActiveRecord Relation that determines which records' spans to compare.
Usage: Given a record with `siblings` defined, the most basic use case is: “` validates_with ActsAsSpan::NoOverlapValidator
,
scope: proc { siblings }
“` When this record is validated, every record in the ActiveRecord relation
`record.siblings` is checked for mutual overlap with `record`.
Use `instance_scope` if there is some condition where a record oughtn't be
validated for whatever reason:
“` validates_with ActsAsSpan::NoOverlapValidator
,
scope: proc { siblings }, instance_scope: proc { favorite? }
“` Now, when this record is validated, if `record.favorite?` is `true`,
`record` must pass the overlap check with its siblings. If `record.favorite?` is `false`, it is under less scrutiny.
Public Instance Methods
TODO: add back condition for start_date nil TODO: add support for multiple spans (currently only checks :default)
# File lib/acts_as_span/no_overlap_validator.rb, line 60 def temporally_overlapping_for(record) scope = record.instance_eval(&options[:scope]) start_date = record.span.start_date || Date.current end_date = record.span.end_date end_field = record.span.end_field arel_table = record.class.arel_table if end_date scope.where( arel_table[record.span.start_field].lteq(end_date) .and( arel_table[end_field].gteq(start_date) .or(arel_table[end_field].eq(nil)) ) ) else scope.where( arel_table[end_field].gteq(start_date) .or(arel_table[end_field].eq(nil)) ) end end
# File lib/acts_as_span/no_overlap_validator.rb, line 35 def validate(record) overlapping_records = temporally_overlapping_for(record) instance_scope = if options[:instance_scope].is_a? Proc record.instance_eval(&options[:instance_scope]) else true end return unless overlapping_records.any? && instance_scope error_message = options[:message] || :no_overlap record.errors.add( :base, error_message, model_name: record.class.model_name.human, model_name_plural: record.class.model_name.plural.humanize, start_date: record.span.start_date, end_date: record.span.end_date, count: overlapping_records.size, overlapping_records_s: overlapping_records.join(', ') ) end