0

I have a model that has two columns (started_at and ended_at). I want to add a custom validator that ensures that no other record exists with dates that overlap with the record I'm validating. So far I have:

# app/models/event.rb

class Event < ActiveRecord::Base
  validates_with EventValidator
end

# app/validators/event_validator.rb

class EventValidator < ActiveModel::Validator
  attr_reader :record

  def validate(record)
    @record = record

    validate_dates
  end

  private

  def validate_dates
    started_at = record.started_at
    ended_at   = record.ended_at
    arel_table = record.class.arel_table

    # This is where I'm not quite sure what type of query I need to perform...
    constraints = arel_table[:started_at].gteq(ended_at)
      .and(arel_table[:ended_at].lteq(started_at))

    if record.persisted?
      constraints = constraints
        .and(arel_table[:id].not_eq(record.id))
    end

    if record.class.where(constraints).exists?
      record.error[:base] << "Overlaps with another event"
    end
  end 
end

I don't know exactly what query I need to ensurethat there is no overlapping. Any help is greatly appreciated

3 Answers 3

1

I don't use Arel but I think the query should be:

 constraints = arel_table[:started_at].lteq(ended_at)
      .and(arel_table[:ended_at].gteq(started_at))

Two periods overlap when

period1.start < period2.end
period1.end > period2.start
Sign up to request clarification or add additional context in comments.

Comments

0

Have a look at Validates Overlap gem

You can either use it instead your code or take condition code from it

  # Return the condition string depend on exclude_edges option.
  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

Comments

0

I would construct a query that looks something like this:

Event.exists?( 'started_at < ? AND ended_at > ?', ended_at, started_at )

If this returns true, an overlapping record exists.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.