Skip to content

Instantly share code, notes, and snippets.

@kaspth
Created July 1, 2022 11:22
Show Gist options
  • Save kaspth/190f882646985c13c9ea389d93ec26c6 to your computer and use it in GitHub Desktop.
Save kaspth/190f882646985c13c9ea389d93ec26c6 to your computer and use it in GitHub Desktop.
`scope` extension to allow marking a class method as a scope.
# In Active Record, class method scopes have to remember to return `all` otherwise they're break the call chain.
#
# def self.some_scope = nil # Assume more complex conditions that would result in a branch that accidentally didn't return `all`.
#
# User.some_scope.first # => raises NoMethodError `first' for NilClass
#
# Note: Active Record ensures a `scope :some_scope, -> { nil }` returns `all` via `|| all` here:
# https://github.com/rails/rails/blob/c704da66de59262f4e88824589ae4eddefb6ed4a/activerecord/lib/active_record/scoping/named.rb#L181
#
# Now, this extension allows you to mark a class method as a scope, so you don't have to remember and the code is more clearly demarcated too.
class User < ApplicationRecord
def self.scope(name, body = nil, &)
if body.nil? && block_given?
raise ArgumentError, "can't pass an extension block while calling scope on a class method"
end
body = method(name) if body.nil?
super
rescue NameError => error
raise unless error.message.match?(/undefined method .*? for class/)
raise ArgumentError, "didn't pass a class method to scope, you may need to affix `self.` in `def self.method_name`"
end
scope def self.some_scope(skip: false)
where(id: 1) unless skip
end
# Just to show the NameError raised for `method(name)` since this is an instance method and not a class method.
# scope def some_instance_scope
# end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment