Created
April 7, 2023 17:08
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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