So if I understand correctly you are looking to ensure there are no "overlaps". This entails ensuring all of the following are true
- New start_dateis not BETWEEN (inclusive) an existingstart_dateandexpiration_date
- New expiration_dateis not BETWEEN (inclusive) an existingstart_dateandexpiration_date
- New start_dateis not prior to an existingstart_dateand Newexpiration_dateis not after the corresponding existingexpiration_date
To satisfy these rules I would implement as follows:
class Post < ApplicationRecord
  validates :start_date, presence: true,comparison: { less_than: :expiration_date}
  validates :expiration_date, presence: true, comparison: { greater_than: :start_date }
  validate :non_overlapping_date_range
  def time_frame 
    start_date..expiration_date
  end 
  
  private 
    def non_overlapping_date_range 
       overlaps_post = Period
         .where(start_date: time_frame )
         .or(Post.where(expiration_date: time_frame ))
         .or(Post.where(start_date: start_date..., expiration_date: ...end_date))
         .where.not(id: id)
         .exists?
      errors.add(:base, "overlaps another Post") if overlaps_post
   end 
end
This will result in the following query
 SELECT 1 AS one 
 FROM 
  posts 
 WHERE
  ((posts.start_date BETWEEN '####-##-##' AND '####-##-##' 
  OR posts.expiration_date BETWEEN '####-##-##' AND '####-##-##')
  OR (
    posts.start_date > '####-##-##' 
    AND posts.expiration_date < '####-##-##'
  ))
  AND posts.id != # -- IS NOT NULL on create
 LIMIT 1
Using OVERLAPS (Postgres)
Postgres offers a function called OVERLAPS however the this does not fully fit the desired situation because this function treats end_date == new_start_date as continuous rather than overlapping. To counteract this we need to adjust the start_date and end_date for the purposes of the query
This can be achieved as follows:
def non_overlapping_date_range 
  overlaps_post = Post.where.not(id: id)
                 .where('(?,?) OVERLAPS (posts.start_date, posts.expiration_date)',
                        start_date - 1, end_date + 1))
                 .exists?
errors.add(:base, "overlaps another Post") if overlaps_post
end
SQL:
SELECT 1 AS one 
 FROM 
  posts 
 WHERE
  ('####-##-##','####-##-##') OVERLAPS (posts.start_date,posts.expiration_date)
  AND posts.id != # -- IS NOT NULL on create
LIMIT 1
Arel port of the same:
def non_overlapping_date_range 
  left = Arel::Nodes::Grouping.new(
    [Arel::Nodes::UnaryOperation.new(
      'DATE', 
      [Arel::Nodes.build_quoted(start_date - 1)]),
    Arel::Nodes::UnaryOperation.new(
      'DATE', 
      [Arel::Nodes.build_quoted(expiration_date + 1)])])
  right = Arel::Nodes::Grouping.new(
    [Post.arel_table[:start_date],
     Post.arel_table[:expiration_date]])
  condition = Arel::Nodes::InfixOperation.new('OVERLAPS', left, right)
  errors.add(:base, "overlaps another Post") if Post.where.not(id: id).where(condition).exists?
end