Skip to content

Unions sometimes fail unexpectedly when chained, and create side effects. #102

@sjjbirch

Description

@sjjbirch

Sometimes when preceding model scopes change in ways that seem like they shouldn't matter some of the methods in the gem begin to fail and produce odd side effects. Removing the chaining seems to make them work again, even if the same association that was calling in the method chain is just converted to an argument.

For example the below started happening when a change was made to the preceding scope (introducing strictest_dates scope to replace between_dates, both shown at the very bottom to reduce clutter), which suddenly made the union_except stop returning the correct new association and also has side effects on preceding associations chained with it

class Match < ApplicationRecord
#...
  scope :had_circle, -> (circle_phase) { left_joins(:circles).where('circles.phase = ?', circle_phase) }
  scope :missing_circle, -> (circle_phase) { union_except(all, had_circle(circle_phase)) } 
  scope :test_missing_circle, -> (existing_assoc, circle_phase) { union_except(existing_assoc, had_circle(circle_phase)) }
#...
end
irb(main):008> q1 = Match.strictest_dates(m_from, m_to, minor_version_start,minor_version_end)
=> [#<Match:0x00007cade8abe420
irb(main):009> q1.count
=> 4478
irb(main):010> q2 = Match.test_missing_circle(q1, missing_circle+1)
=> [#<Match:0x00007cade8a13f20
irb(main):011> [q1.count, q2.count]
=> [4478, 4478]
irb(main):017> q3 = q1.had_circle(7)
=> 
[#<Match:0x00007cae018cbcd0
irb(main):018> [q1.count, q2.count, q3.count]
=> [4478, 4478, 2638]
irb(main):019> q4 = q1.missing_circle(missing_circle+1)
=> [0, 4478, 0, 0]

The change triggered the unexpected behaviour was replacing between_dates in the calling function with strictest_dates shown below. I've had similar misbehaviour before with some other scopes using any_of but didn't investigate and just factored them out. All of the below is on PostgreSQL 14.11 (Ubuntu 14.11-0ubuntu0.22.04.1) and Rails 7.0.8:

class Match < ApplicationRecord
  scope :after_date, ->(start_date) { where(creation_time: start_date.end_of_day..) }
  scope :before_date, ->(end_date) { where(creation_time: ..end_date.beginning_of_day) }
  scope :between_dates, ->(start_date, end_date) { where(creation_time: start_date.end_of_day..end_date.beginning_of_day) }
  scope :inside_version, ->(version_number) { where(creation_time: convert_pc_version_to_exclusive_date_range(version_number) ) }
  scope :between_versions, ->(start_version, end_version) {
    where(creation_time: convert_pc_version_to_exclusive_date_range(start_version).first..convert_pc_version_to_exclusive_date_range(end_version).last )
  }
  scope :strictest_dates, ->(start_date, end_date, start_version=nil, end_version=nil) {
    if start_version && end_version
      matches = union_intersect(between_dates(start_date, end_date), between_versions(start_version, end_version))
    else
      matches = between_dates(start_date, end_date)
    end
    matches
  }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions