Skip to content

Instantly share code, notes, and snippets.

@ka8725
Last active April 18, 2019 03:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ka8725/de9e6a87d83a0f58ad3e3ba20ebaf3ae to your computer and use it in GitHub Desktop.
Save ka8725/de9e6a87d83a0f58ad3e3ba20ebaf3ae to your computer and use it in GitHub Desktop.
# Allows to stub a chain of scopes and aggregate functions on a `has_many` association.
# For example, there is `Subscription` `has_many` `users` association and somewhere in the code there is a call
# like `subscription.users.active.verified.maximum(:id)`. This class can be used to stub this association easily so that
# any scope's call in the chain returns the given collection of records and aggregate functions behave like
# `ActiveRecord`'s ones.
#
# Example:
#
# ```ruby
# users = ActiveRecordRelationStub.new(User, [user1, user], scopes: %i[active verified])
# allow(subscription).to receive(:users).and_return(users)
# ```
#
# @note If you find some aggregate functions missing feel free to add them.
class ActiveRecordRelationStub
attr_reader :records
alias to_a records
delegate :sum, :select, :map, :lazy, :each, :first, to: :records
# @param model_klass [ActiveRecord::Base] the stubbing association's class
# @param records [Array] list of records the association holds
# @param scopes [Array] list of stubbed scopes
def initialize(model_klass, records, scopes: [])
@records = records
define_scopes(model_klass, scopes)
define_aggregates(model_klass)
end
def all
self
end
private
def define_scopes(model_klass, scopes)
scopes.each do |scope|
fail NotImplementedError, scope unless model_klass.respond_to?(scope)
define_singleton_method(scope) do
self
end
end
end
def define_aggregates(model_klass)
fail NotImplementedError, :maximum unless model_klass.respond_to?(:maximum)
define_singleton_method(:maximum) do |field|
records.map(&field).max
end
fail NotImplementedError, :sum unless model_klass.respond_to?(:sum)
define_singleton_method(:sum) do |field = nil, &block|
# in case no argument provided AR raises ActiveRecord::StatementInvalid
fail ArgumentError, 'no argument provided' if field.nil? && block.nil?
field ? @records.sum(&field) : @records.sum(&block)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment