Created
March 31, 2020 09:43
-
-
Save wojtha/a16e45a6171c75433c2a39f038521cfa to your computer and use it in GitHub Desktop.
Ruby: Class StatusFactory is module factory to generate Status classes
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
# Class StatusFactory is module factory to generate Status classes | |
# | |
# @example Status class definition | |
# | |
# class Result | |
# include StatusFactory.new(:success, :failure, query: true, callbacks: true, factory: true, equality: true) | |
# end | |
# | |
# # is equivalent of: | |
# | |
# class Result | |
# | |
# # Factory methods | |
# | |
# def self.success, *args, **kwargs) | |
# new(:success, *args, **kwargs) | |
# end | |
# | |
# def self.failure, *args, **kwargs) | |
# new(:failure, *args, **kwargs) | |
# end | |
# | |
# # Base methods, included everytime | |
# | |
# def initialize(status, *, **) | |
# @status = status | |
# end | |
# | |
# def status | |
# @status | |
# end | |
# | |
# # Query methods | |
# | |
# def success? | |
# @status == :success | |
# end | |
# | |
# def failure? | |
# @status == :failure | |
# end | |
# | |
# # Equality methods | |
# | |
# def hash | |
# status.hash | |
# end | |
# | |
# def eql?(other) | |
# instance_of?(other.class) && @status.eql?(other.status) | |
# end | |
# | |
# def ==(other) | |
# other.kind_of?(self.class) && @status == other.status | |
# end | |
# | |
# end | |
# | |
# @example Use in the code | |
# | |
# class LuckService | |
# def call | |
# result = | |
# if rand(10) > 4 | |
# Result.success | |
# else | |
# Result.failure | |
# end | |
# yield result if block_given? | |
# result | |
# end | |
# end | |
# | |
# result = LuckService.new.call | |
# | |
# if result.success? | |
# puts "tada" | |
# end | |
# | |
# case result.status | |
# when :success then puts "tada" | |
# when :failure then puts "oops" | |
# end | |
# | |
# LuckService.new.call do |r| | |
# r.on_success { puts "tada" } | |
# r.on_failure { puts "oops" } | |
# end | |
# | |
# @see Confident Ruby by Avdi Grimm, chapter Yield a status object | |
# @see Module Factory description https://blog.codeminer42.com/cracking-the-box-open-with-module-factories-2ef048193a7e#.jl953uu5v | |
# @see Equalizer for some inspiration https://github.com/dkubb/equalizer | |
# | |
class StatusFactory < Module | |
# Class Builder allows to prefabricate StatusFactory reusable configurations. | |
# | |
# @example | |
# SimpleStatusFactory = StatusFactory::Builder.new(query: true, factory: true) | |
# RichStatusFactory = StatusFactory::Builder.new(query: true, factory: true, callbacks: true, equality: true) | |
# | |
# class Result | |
# include SimpleStatusFactory.new(:success, :failure) | |
# # or | |
# include RichStatusFactory.new(:success, :failure) | |
# # or | |
# # Note: You can still override the builder defaults | |
# include RichStatusFactory.new(:success, :failure, equality: false) | |
# end | |
# | |
class Builder | |
# Initialize StatusFactory::Builder with the given options | |
# | |
# @param see StatusFactory#initialize | |
# | |
def initialize(query: false, factory: false, callbacks: false, equality: false) | |
@default_options = { | |
query: query, | |
callbacks: callbacks, | |
factory: factory, | |
equality: equality, | |
} | |
end | |
# Emulates Class#new | |
# | |
# @param [Array<Symbol>] *statuses | |
# The list of statuses | |
# @param [Hash] **overrides | |
# The overriden options see StatusFactory#initialize | |
# | |
# @return [StatusFactory] the factory module instance with applied defaults | |
def new(*statuses, **overrides) | |
StatusFactory.new(*statuses, **@default_options.merge(overrides)) | |
end | |
end | |
# Predefined factory builder with all options disabled | |
# | |
# @return [StatusFactory::Builder] | |
Empty = Builder.new(query: false, callbacks: false, factory: false, equality: false) | |
# Predefined factory builder with all options enabled | |
# | |
# @return [StatusFactory::Builder] | |
Full = Builder.new(query: true, callbacks: true, factory: true, equality: true) | |
# Initialize StatusFactory with the given statuses and options | |
# | |
# @param [Array<Symbol>] *statuses | |
# The list of statuses | |
# @param [Boolean] query: true | |
# Whether to include query methods like #success? | |
# @param [Boolean] callbacks: false | |
# Whether to include callback methods like #on_success | |
# @param [Boolean] factory: false | |
# Whether to include class factory methods like .success | |
# @param [Boolean] equality: false <description> | |
# Whether to include class factory methods like .success | |
# | |
# @return [undefined] | |
# | |
# @api private | |
def initialize(*statuses, query: true, factory: true, callbacks: false, equality: false) | |
@statuses = [*statuses].map(&:to_sym) | |
@query_methods = query | |
@callback_methods = callbacks | |
@factory_methods = factory | |
@equality_methods = equality | |
define_query_methods if @query_methods | |
define_callback_methods if @callback_methods | |
freeze | |
end | |
private | |
# @api private | |
def included(descendant) | |
super | |
descendant.include Methods | |
descendant.include Equality if @equality_methods | |
define_factory_methods(descendant) if @factory_methods | |
end | |
# @api private | |
def define_query_methods | |
@statuses.each do |status| | |
define_method("#{status}?") do | |
@status == status | |
end | |
end | |
end | |
# @api private | |
def define_callback_methods | |
@statuses.each do |status| | |
# Interesting, can't be replaced with yield, with yield it gives the following: | |
# LocalJumpError: no block given (yield) | |
define_method("on_#{status}") do |&block| | |
block.call if @status == status | |
end | |
end | |
end | |
# @api private | |
def define_factory_methods(descendant) | |
@statuses.each do |status| | |
descendant.define_singleton_method(status) do |*args, **kwargs| | |
new(status, *args, **kwargs) | |
end | |
end | |
end | |
# Module StatusFactory::Methods contains methods included in every descendant. | |
# | |
module Methods | |
def initialize(status, *, **) | |
@status = status | |
end | |
# @return [Symbol] | |
# the status itself | |
# | |
# @api public | |
def status | |
@status | |
end | |
end | |
# Module StatusFactory::Equality contains methods included in descendats with enabled equality | |
# | |
module Equality | |
# @return [String] | |
# the status hash | |
# | |
# @api public | |
def hash | |
@status.hash | |
end | |
# Compare the object with other object for equality | |
# | |
# @example | |
# object.eql?(other) # => true or false | |
# | |
# @param [Object] other | |
# the other object to compare with | |
# | |
# @return [Boolean] | |
# | |
# @api public | |
def eql?(other) | |
instance_of?(other.class) && @status.eql?(other.status) | |
end | |
# Compare the object with other object for equivalency | |
# | |
# @example | |
# object == other # => true or false | |
# | |
# @param [Object] other | |
# the other object to compare with | |
# | |
# @return [Boolean] | |
# | |
# @api public | |
def ==(other) | |
other.kind_of?(self.class) && @status == other.status | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment