# frozen_string_literal: true
module Rails
module GraphQL
module Helpers
# Helper module that allow classes to have specific type of attributes
# that are corretly delt when it is inherited by another class. It keeps
# track of its own value and allow access to all values of the property
# in the tree,
#
# TODO: Rewrite this!
module InheritedCollection
# All possible types of inheritable values
DEFAULT_TYPES = {
array: '[]',
set: 'Set.new',
hash: '{}',
hash_array: '::Hash.new { |h, k| h[k] = [] }',
hash_set: '::Hash.new { |h, k| h[k] = Set.new }',
}.freeze
# Declare a class-level attribute whose value is both isolated and also
# inherited from parent classes. Subclasses can change their own value
# and it will not impact parent class.
#
# Inspired by +class_attribute+ from ActiveSupport.
#
# ==== Options
#
# * :instance_reader - Sets the instance reader method (defaults to true).
# * :instance_predicate - Sets a predicate method (defaults to true).
# * :type - Defines the type of the values stored (defaults to :set).
#
# ==== Examples
#
# class Base
# inherited_collection :settings
# end
#
# class Subclass < Base
# end
#
# Base.settings << :a
# Subclass.settings # => []
# Subclass.all_settings # => [:a]
# Subclass.settings << :b
# Subclass.settings # => [:b]
# Subclass.all_settings # => [:a, :b]
# Base.settings # => [:a]
# Base.all_settings # => [:a]
#
# For convenience, an instance predicate method is defined as well,
# which checks for the +all_+ method. To skip it, pass
# instance_predicate: false.
#
# Subclass.settings? # => false
#
# To opt out of the instance reader method, pass instance_reader: false.
#
# object.settings # => NoMethodError
# object.settings? # => NoMethodError
#
def inherited_collection(
*attrs,
instance_reader: true,
instance_predicate: true,
type: :set
)
attrs.each do |name|
instance_eval(<<~RUBY, __FILE__, __LINE__ + 1)
def all_#{name}
return superclass.try(:all_#{name}) unless defined?(@#{name})
InheritedCollection::Base.handle(self, :@#{name}, :#{type})
end
def #{name}
@#{name} ||= #{DEFAULT_TYPES[type]}
end
RUBY
instance_eval(<<~RUBY, __FILE__, __LINE__ + 1) if instance_predicate
def #{name}?
(defined?(@#{name}) && @#{name}.present?) || superclass.try(:#{name}?)
end
RUBY
if instance_reader
delegate(name.to_sym, :"all_#{name}", to: :class)
delegate(:"#{name}?", to: :class) if instance_predicate
end
end
end
end
end
end
end
require_relative 'inherited_collection/base'
require_relative 'inherited_collection/array'
require_relative 'inherited_collection/hash'