Skip to content

Instantly share code, notes, and snippets.

@brand-it
Created April 7, 2023 17:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brand-it/27344469ae1d673c6f5df80cd04212d7 to your computer and use it in GitHub Desktop.
Save brand-it/27344469ae1d673c6f5df80cd04212d7 to your computer and use it in GitHub Desktop.
A simple tool to make it easier to test scopes on models using rspec
# frozen_string_literal: true
# A quick and easy way to test scopes sql queries by checking only the sql output
#
# is_expected.to have_scope(:user, 1).include('users.id = 1')
# is_expected.to have_scope(:user, 1).match_array([user])
# is_expected.to have_scope(:user, 1).eq([user])
module RSpec
module Matchers
class HaveScope
attr_reader :scope_name, :args, :klass, :raised_exception
def initialize(scope_name, *args)
@scope_name = scope_name.to_sym
@args = args
end
def description
"have scope #{scope_name} that is valid"
end
def include(expected)
@include = RSpec::Matchers::BuiltIn::Include.new(expected)
self
end
def eq(*expected)
@eq = RSpec::Matchers::BuiltIn::Eq.new(expected)
self
end
def match_array(*expected)
@match_array = RSpec::Matchers::BuiltIn::ContainExactly.new(expected)
self
end
def matches?(described_class)
@klass = described_class.model_name.instance_variable_get(:@klass)
has_scope? &&
active_record_relation? &&
valid_sql? &&
include? &&
eq? &&
match_array?
end
def failure_message # rubocop:disable Metrics/CyclomaticComplexity
return "expected #{klass} to respond to #{scope_name}" unless has_scope?
return "expected #{scope.inspect} to be a ActiveRecord::Relation" unless active_record_relation?
@include&.failure_message || @eq&.failure_message || @match_array&.failure_message
end
alias failure_message_when_negated failure_message
private
def match_array?
return true unless defined?(@match_array)
@match_array.matches?(scope.to_a)
end
def eq?
return true unless defined?(@eq)
@eq.matches?(scope.to_a)
end
def include?
return true unless defined?(@include)
@include.matches?(scope.to_sql)
end
def valid_sql?
scope.load
rescue StandardError => e
raise e, e.message, e.backtrace.reject { _1.include?(__FILE__) }
end
def has_scope?
klass.respond_to?(scope_name)
end
def active_record_relation?
scope.is_a?(ActiveRecord::Relation)
end
def scope
klass.public_send(scope_name, *args)
rescue StandardError => e
raise e, e.message, e.backtrace.reject { _1.include?(__FILE__) }
end
end
def have_scope(...)
HaveScope.new(...)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment