Created
January 14, 2017 19:39
-
-
Save wojtha/130e6533b9215dffde7c6951efa4a3ac to your computer and use it in GitHub Desktop.
Status factory lib for Ruby
This file contains hidden or 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 Result | |
| include StatusFactory.new(:create, :update, :delete, :identical, callbacks: true) | |
| end |
This file contains hidden or 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 Result | |
| def self.create; new(:create); end | |
| def self.update; new(:update); end | |
| def self.delete; new(:delete); end | |
| def self.identical; new(:identical); end | |
| def initialize(status) | |
| @status = status | |
| end | |
| def create? | |
| @status == :create | |
| end | |
| def update? | |
| @status == :update | |
| end | |
| def delete? | |
| @status == :delete | |
| end | |
| def identical? | |
| @status == :identical | |
| end | |
| def on_create | |
| yield if create? | |
| end | |
| def on_update | |
| yield if update? | |
| end | |
| def on_delete | |
| yield if delete? | |
| end | |
| def on_identical | |
| yield if identical? | |
| end | |
| end |
This file contains hidden or 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 |
This file contains hidden or 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
| require 'spec_helper' | |
| describe StatusFactory, '.new' do | |
| context 'with statuses :succes, and :failure' do | |
| let(:status_class) { | |
| Class.new do | |
| include StatusFactory.new(:success, :failure, query: true, callbacks: true, factory: true, equality: true) | |
| end | |
| } | |
| describe 'factory methods' do | |
| it 'defines class factory methods for all statuses' do | |
| expect( status_class ).to respond_to(:success) | |
| expect( status_class ).to respond_to(:failure) | |
| end | |
| end | |
| context 'when target class initialized with :success' do | |
| subject { status_class.new(:success) } | |
| it 'returns true for #success? and false for #failure' do | |
| is_expected.to have_attributes( | |
| success?: true, | |
| failure?: false, | |
| ) | |
| end | |
| it 'yields for #on_success' do | |
| expect { |b| subject.on_success(&b) }.to yield_with_no_args | |
| end | |
| it 'does not yield for #on_failure' do | |
| expect { |b| subject.on_failure(&b) }.not_to yield_control | |
| end | |
| end | |
| describe '#eql? equality' do | |
| it 'status :success is equal to :success' do | |
| status = status_class.new(:success) | |
| other = status_class.new(:success) | |
| expect( status ).to eql other | |
| end | |
| it 'status :success is not equal to :failure' do | |
| status = status_class.new(:success) | |
| other = status_class.new(:failure) | |
| expect( status ).not_to eql other | |
| end | |
| end | |
| describe '#== equality' do | |
| it 'status :success is equal to :success' do | |
| status = status_class.new(:success) | |
| other = status_class.new(:success) | |
| expect( status ).to eq other | |
| end | |
| it 'status :success is not equal to :failure' do | |
| status = status_class.new(:success) | |
| other = status_class.new(:failure) | |
| expect( status ).not_to eq other | |
| end | |
| end | |
| describe '#hash'do | |
| it 'status :success is equal to :success' do | |
| status = status_class.new(:success) | |
| other = status_class.new(:success) | |
| expect( status.hash ).to eq other.hash | |
| end | |
| it 'status :success is not equal to :failure' do | |
| status = status_class.new(:success) | |
| other = status_class.new(:failure) | |
| expect( status.hash ).not_to eq other.hash | |
| end | |
| end | |
| end | |
| describe 'with method override' do | |
| let(:status_class) { | |
| Class.new do | |
| include StatusFactory.new(:success, :failure) | |
| def success? | |
| 'hell yes!' | |
| end | |
| def on_success(&block) | |
| block.call('hell yes!') if success? | |
| end | |
| end | |
| } | |
| subject { status_class.new(:success) } | |
| it 'returns overriden result' do | |
| expect( subject.success? ).to eq 'hell yes!' | |
| end | |
| end | |
| end | |
| describe StatusFactory::Builder, '.new' do | |
| context 'with every option on' do | |
| let(:status_class) { | |
| status_builder = StatusFactory::Builder.new(query: true, callbacks: true, factory: true, equality: true) | |
| Class.new do | |
| include status_builder.new(:success) | |
| end | |
| } | |
| let(:status_instance) { status_class.new(:success) } | |
| it 'defines class factory methods' do | |
| expect( status_class ).to respond_to(:success) | |
| end | |
| it 'defines query methods' do | |
| expect( status_instance ).to respond_to(:success?) | |
| end | |
| it 'defines callback methods' do | |
| expect( status_instance ).to respond_to(:success?) | |
| end | |
| end | |
| context 'with every option off' do | |
| let(:status_class) { | |
| status_builder = StatusFactory::Builder.new(query: false, callbacks: false, factory: false, equality: false) | |
| Class.new do | |
| include status_builder.new(:success) | |
| end | |
| } | |
| let(:status_instance) { status_class.new(:success) } | |
| it 'defines class factory methods' do | |
| expect( status_class ).to_not respond_to(:success) | |
| end | |
| it 'defines query methods' do | |
| expect( status_instance ).to_not respond_to(:success?) | |
| end | |
| it 'defines callback methods' do | |
| expect( status_instance ).to_not respond_to(:success?) | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment