hexx 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/.rubocop.yml +45 -0
 - data/CHANGELOG.rdoc +1 -0
 - data/LICENSE.rdoc +21 -0
 - data/README.rdoc +235 -0
 - data/Rakefile +31 -0
 - data/bin/hexx +54 -0
 - data/lib/generators/base.rb +59 -0
 - data/lib/generators/controller/controller.rb +87 -0
 - data/lib/generators/controller/templates/controller.erb +18 -0
 - data/lib/generators/controller/templates/controller_action.erb +8 -0
 - data/lib/generators/controller/templates/controller_action_spec.erb +28 -0
 - data/lib/generators/controller/templates/controller_spec.erb +52 -0
 - data/lib/generators/controller/templates/routing_action_spec.erb +10 -0
 - data/lib/generators/controller/templates/routing_spec.erb +10 -0
 - data/lib/generators/dependency/dependency.rb +34 -0
 - data/lib/generators/dependency/templates/dependency_setting.erb +4 -0
 - data/lib/generators/dependency/templates/dependency_setting_spec.erb +34 -0
 - data/lib/generators/dependency/templates/module_spec.erb +22 -0
 - data/lib/generators/domain/domain.rb +24 -0
 - data/lib/generators/domain/templates/spec.erb +84 -0
 - data/lib/generators/install/install.rb +115 -0
 - data/lib/generators/install/templates/CHANGELOG.erb +1 -0
 - data/lib/generators/install/templates/Gemfile.erb +5 -0
 - data/lib/generators/install/templates/LICENSE.erb +21 -0
 - data/lib/generators/install/templates/README.erb +55 -0
 - data/lib/generators/install/templates/Rakefile.erb +34 -0
 - data/lib/generators/install/templates/bin/rails.erb +11 -0
 - data/lib/generators/install/templates/config/routes.erb +6 -0
 - data/lib/generators/install/templates/gemspec.erb +29 -0
 - data/lib/generators/install/templates/lib/engine.erb +12 -0
 - data/lib/generators/install/templates/lib/lib.erb +10 -0
 - data/lib/generators/install/templates/lib/version.erb +4 -0
 - data/lib/generators/install/templates/spec/coveralls.erb +4 -0
 - data/lib/generators/install/templates/spec/database_cleaner.erb +27 -0
 - data/lib/generators/install/templates/spec/factory_girl.erb +6 -0
 - data/lib/generators/install/templates/spec/factory_girl_rails.erb +1 -0
 - data/lib/generators/install/templates/spec/focus.erb +5 -0
 - data/lib/generators/install/templates/spec/garbage_collection.erb +11 -0
 - data/lib/generators/install/templates/spec/i18n.erb +1 -0
 - data/lib/generators/install/templates/spec/migrations.erb +3 -0
 - data/lib/generators/install/templates/spec/rails.erb +6 -0
 - data/lib/generators/install/templates/spec/random_order.erb +4 -0
 - data/lib/generators/install/templates/spec/rspec.erb +5 -0
 - data/lib/generators/install/templates/spec/spec_helper.erb +12 -0
 - data/lib/generators/install/templates/spec/timecop.erb +1 -0
 - data/lib/generators/request/request.rb +52 -0
 - data/lib/generators/request/templates/request_spec.erb +73 -0
 - data/lib/generators/use_case/templates/use_case.erb +29 -0
 - data/lib/generators/use_case/templates/use_case_spec.erb +77 -0
 - data/lib/generators/use_case/use_case.rb +31 -0
 - data/lib/hexx.rb +2 -0
 - data/lib/hexx/exceptions/not_found_error.rb +12 -0
 - data/lib/hexx/exceptions/record_invalid.rb +12 -0
 - data/lib/hexx/exceptions/runtime_error.rb +24 -0
 - data/lib/hexx/exceptions/use_case_invalid.rb +12 -0
 - data/lib/hexx/models.rb +82 -0
 - data/lib/hexx/settings.rb +47 -0
 - data/lib/hexx/use_case.rb +228 -0
 - data/lib/hexx/version.rb +4 -0
 - data/spec/hexx/exceptions/not_found_error_spec.rb +27 -0
 - data/spec/hexx/exceptions/record_invalid_spec.rb +27 -0
 - data/spec/hexx/exceptions/runtime_error_spec.rb +61 -0
 - data/spec/hexx/exceptions/use_case_invalid_spec.rb +27 -0
 - data/spec/hexx/models_spec.rb +64 -0
 - data/spec/hexx/settings_spec.rb +51 -0
 - data/spec/hexx/use_case_spec.rb +262 -0
 - data/spec/spec_helper.rb +3 -0
 - data/spec/support/initializers/coveralls.rb +3 -0
 - data/spec/support/initializers/focus.rb +5 -0
 - data/spec/support/initializers/garbage_collection.rb +11 -0
 - data/spec/support/initializers/i18n.rb +1 -0
 - data/spec/support/initializers/random_order.rb +4 -0
 - data/spec/support/initializers/rspec.rb +5 -0
 - metadata +236 -0
 
| 
         @@ -0,0 +1,77 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "spec_helper"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module <%= module_name %>
         
     | 
| 
      
 4 
     | 
    
         
            +
              describe <%= class_name %> do
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Prepare environment
         
     | 
| 
      
 8 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # before { Timecop.freeze }
         
     | 
| 
      
 11 
     | 
    
         
            +
                # after  { Timecop.return }
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 14 
     | 
    
         
            +
                # Prepare variables
         
     | 
| 
      
 15 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # let!(:params)          { { key: value } }
         
     | 
| 
      
 18 
     | 
    
         
            +
                # let!(:expected_result) { { something.to_struct.inspect } }
         
     | 
| 
      
 19 
     | 
    
         
            +
                # let!(:listener)        { double "listener" }
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                # def prepare_case(params)
         
     | 
| 
      
 22 
     | 
    
         
            +
                #   use_case = <%= class_name %>.new params
         
     | 
| 
      
 23 
     | 
    
         
            +
                #   use_case.subscribe(listener)
         
     | 
| 
      
 24 
     | 
    
         
            +
                #   use_case
         
     | 
| 
      
 25 
     | 
    
         
            +
                # end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 28 
     | 
    
         
            +
                # Run tests
         
     | 
| 
      
 29 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                describe "#run" do
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  # context "with proper params" do
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  #   let!(:use_case) { prepare_case params }
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  #   it "does something" do
         
     | 
| 
      
 38 
     | 
    
         
            +
                  #     expect { use_case.run }.to change { something }
         
     | 
| 
      
 39 
     | 
    
         
            +
                  #       .from(something).to(something)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  #   end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  #   it "returns something" do
         
     | 
| 
      
 43 
     | 
    
         
            +
                  #     expect(use_case.run.inspect).to eq expected_result
         
     | 
| 
      
 44 
     | 
    
         
            +
                  #   end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  #   it "sends 'something' to subscribers" do
         
     | 
| 
      
 47 
     | 
    
         
            +
                  #     expect(listener).to receive :something do |result|
         
     | 
| 
      
 48 
     | 
    
         
            +
                  #       expect(result.inspect).to eq expected_result
         
     | 
| 
      
 49 
     | 
    
         
            +
                  #     end
         
     | 
| 
      
 50 
     | 
    
         
            +
                  #     use_case.run
         
     | 
| 
      
 51 
     | 
    
         
            +
                  #   end
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  # context "with some improper params" do
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  #   before { params[:something] = something }
         
     | 
| 
      
 57 
     | 
    
         
            +
                  #   let!(:use_case) { prepare_case params }
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  #   it "doesn't do something" do
         
     | 
| 
      
 60 
     | 
    
         
            +
                  #     expect { use_case.run }.not_to change { something }
         
     | 
| 
      
 61 
     | 
    
         
            +
                  #       .from something
         
     | 
| 
      
 62 
     | 
    
         
            +
                  #   end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  #   it "returns nil" do
         
     | 
| 
      
 65 
     | 
    
         
            +
                  #     expect(use_case.run).to be_nil
         
     | 
| 
      
 66 
     | 
    
         
            +
                  #   end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                  #   it "sends 'error' to subscribers" do
         
     | 
| 
      
 69 
     | 
    
         
            +
                  #     expect(listener).to receive :error do |messages|
         
     | 
| 
      
 70 
     | 
    
         
            +
                  #       expect(messages).not_to be_blank
         
     | 
| 
      
 71 
     | 
    
         
            +
                  #     end
         
     | 
| 
      
 72 
     | 
    
         
            +
                  #     use_case.run
         
     | 
| 
      
 73 
     | 
    
         
            +
                  #   end
         
     | 
| 
      
 74 
     | 
    
         
            +
                  # end
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
              end
         
     | 
| 
      
 77 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,31 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative "../base"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Hexx
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Generators
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                # Use case scaffolder.
         
     | 
| 
      
 7 
     | 
    
         
            +
                class UseCase < Base
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def self.source_root
         
     | 
| 
      
 10 
     | 
    
         
            +
                    super __FILE__
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def add_use_case
         
     | 
| 
      
 14 
     | 
    
         
            +
                    template "use_case.erb", "app/#{ use_cases_path }/#{ file_name }.rb"
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def add_use_case_spec
         
     | 
| 
      
 18 
     | 
    
         
            +
                    template(
         
     | 
| 
      
 19 
     | 
    
         
            +
                      "use_case_spec.erb",
         
     | 
| 
      
 20 
     | 
    
         
            +
                      "spec/#{ use_cases_path }/#{ file_name }_spec.rb"
         
     | 
| 
      
 21 
     | 
    
         
            +
                    )
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  private
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def use_cases_path
         
     | 
| 
      
 27 
     | 
    
         
            +
                    "use_cases/#{ gem_name }"
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/hexx.rb
    ADDED
    
    
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Hexx
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              # An exception to be raised by some method with given object.
         
     | 
| 
      
 4 
     | 
    
         
            +
              #
         
     | 
| 
      
 5 
     | 
    
         
            +
              # It is expected, that the object stores error messages in its <tt>errors</tt>
         
     | 
| 
      
 6 
     | 
    
         
            +
              # collection.
         
     | 
| 
      
 7 
     | 
    
         
            +
              #
         
     | 
| 
      
 8 
     | 
    
         
            +
              class RuntimeError < ::RuntimeError
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                attr_reader :object
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def initialize(object)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @object = object
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def errors
         
     | 
| 
      
 17 
     | 
    
         
            +
                  object.errors if object
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def message
         
     | 
| 
      
 21 
     | 
    
         
            +
                  "Runtime error: #{ errors.inspect }"
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/hexx/models.rb
    ADDED
    
    | 
         @@ -0,0 +1,82 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "active_support"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Hexx
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              # Module defines:
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              # * <tt>validate!</tt> public instance method
         
     | 
| 
      
 8 
     | 
    
         
            +
              # * +attr_coerced+ public class methods.
         
     | 
| 
      
 9 
     | 
    
         
            +
              #
         
     | 
| 
      
 10 
     | 
    
         
            +
              # Include the module into the Rails model:
         
     | 
| 
      
 11 
     | 
    
         
            +
              #
         
     | 
| 
      
 12 
     | 
    
         
            +
              #   require "hexx"
         
     | 
| 
      
 13 
     | 
    
         
            +
              #   require_relative "attributes/string"
         
     | 
| 
      
 14 
     | 
    
         
            +
              #
         
     | 
| 
      
 15 
     | 
    
         
            +
              #   class User < ActiveRecord::Base
         
     | 
| 
      
 16 
     | 
    
         
            +
              #     include Hexx::Models
         
     | 
| 
      
 17 
     | 
    
         
            +
              #
         
     | 
| 
      
 18 
     | 
    
         
            +
              #     # coerces attributes
         
     | 
| 
      
 19 
     | 
    
         
            +
              #     attr_coerced :name, login, type: Attributes::String
         
     | 
| 
      
 20 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 21 
     | 
    
         
            +
              #
         
     | 
| 
      
 22 
     | 
    
         
            +
              module Models
         
     | 
| 
      
 23 
     | 
    
         
            +
                extend ActiveSupport::Concern
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def validate!
         
     | 
| 
      
 26 
     | 
    
         
            +
                  return true if valid?
         
     | 
| 
      
 27 
     | 
    
         
            +
                  fail RecordInvalid.new self
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                # Model class helpers for attributes coercion.
         
     | 
| 
      
 31 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def attr_coerced(*names, type:)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    names.each { |name| _attr_coerced(name, type) }
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  private
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def _attr_coerced(name, type)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    if ancestors.map(&:name).include? "ActiveRecord::Base"
         
     | 
| 
      
 41 
     | 
    
         
            +
                      coerce_activerecord_reader name, type
         
     | 
| 
      
 42 
     | 
    
         
            +
                      coerce_activerecord_writer name, type
         
     | 
| 
      
 43 
     | 
    
         
            +
                    else
         
     | 
| 
      
 44 
     | 
    
         
            +
                      coerce_simple_reader name, type
         
     | 
| 
      
 45 
     | 
    
         
            +
                      coerce_simple_writer name, type
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def coerce_simple_reader(name, type)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    class_eval(
         
     | 
| 
      
 51 
     | 
    
         
            +
                      "def #{ name };
         
     | 
| 
      
 52 
     | 
    
         
            +
                        #{ type.name }.new(@#{ name });
         
     | 
| 
      
 53 
     | 
    
         
            +
                      end"
         
     | 
| 
      
 54 
     | 
    
         
            +
                    )
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  def coerce_simple_writer(name, type)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    class_eval(
         
     | 
| 
      
 59 
     | 
    
         
            +
                      "def #{ name }=(value);
         
     | 
| 
      
 60 
     | 
    
         
            +
                        @#{ name } = #{ type.name }.new(value);
         
     | 
| 
      
 61 
     | 
    
         
            +
                      end"
         
     | 
| 
      
 62 
     | 
    
         
            +
                    )
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  def coerce_activerecord_reader(name, type)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    class_eval(
         
     | 
| 
      
 67 
     | 
    
         
            +
                      "def #{ name };
         
     | 
| 
      
 68 
     | 
    
         
            +
                        #{ type.name }.new read_attribute(:#{ name });
         
     | 
| 
      
 69 
     | 
    
         
            +
                      end"
         
     | 
| 
      
 70 
     | 
    
         
            +
                    )
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  def coerce_activerecord_writer(name, type)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    class_eval(
         
     | 
| 
      
 75 
     | 
    
         
            +
                      "def #{ name }=(value);
         
     | 
| 
      
 76 
     | 
    
         
            +
                        write_attribute :#{ name }, #{ type.name }.new(value);
         
     | 
| 
      
 77 
     | 
    
         
            +
                      end"
         
     | 
| 
      
 78 
     | 
    
         
            +
                    )
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
              end
         
     | 
| 
      
 82 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "active_support"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Hexx
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              # Storage for dependencies.
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              # Include it to your domain module and declare necessary dependencies.
         
     | 
| 
      
 8 
     | 
    
         
            +
              #
         
     | 
| 
      
 9 
     | 
    
         
            +
              #   module MyProject
         
     | 
| 
      
 10 
     | 
    
         
            +
              #     include Hexx::Settigns
         
     | 
| 
      
 11 
     | 
    
         
            +
              #     class << self
         
     | 
| 
      
 12 
     | 
    
         
            +
              #
         
     | 
| 
      
 13 
     | 
    
         
            +
              #       depends_on :some_class
         
     | 
| 
      
 14 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 15 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 16 
     | 
    
         
            +
              #
         
     | 
| 
      
 17 
     | 
    
         
            +
              # Then you can add a setting:
         
     | 
| 
      
 18 
     | 
    
         
            +
              #
         
     | 
| 
      
 19 
     | 
    
         
            +
              #   # config/initializers/my_project.rb
         
     | 
| 
      
 20 
     | 
    
         
            +
              #   MyProject.configure do |c|
         
     | 
| 
      
 21 
     | 
    
         
            +
              #     c.some_class_name = "ExternalModule::SomeClass"
         
     | 
| 
      
 22 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 23 
     | 
    
         
            +
              #
         
     | 
| 
      
 24 
     | 
    
         
            +
              # And use it in a code:
         
     | 
| 
      
 25 
     | 
    
         
            +
              #
         
     | 
| 
      
 26 
     | 
    
         
            +
              #   MyProject.some_class # => ExternalModule::SomeClass
         
     | 
| 
      
 27 
     | 
    
         
            +
              #
         
     | 
| 
      
 28 
     | 
    
         
            +
              module Settings
         
     | 
| 
      
 29 
     | 
    
         
            +
                extend ActiveSupport::Concern
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                # Settings helpers
         
     | 
| 
      
 32 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def configure(&block)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    block.call(self) if block_given?
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def depends_on(name)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    cattr_accessor "#{ name }_name"
         
     | 
| 
      
 40 
     | 
    
         
            +
                    define_singleton_method name do
         
     | 
| 
      
 41 
     | 
    
         
            +
                      const = send "#{ name }_name"
         
     | 
| 
      
 42 
     | 
    
         
            +
                      const ? Kernel.const_get(const) : nil
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,228 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "active_model"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "wisper"
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Hexx
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              # = About
         
     | 
| 
      
 7 
     | 
    
         
            +
              #
         
     | 
| 
      
 8 
     | 
    
         
            +
              # Use cases are a core part of a domain. They implement case-specific business
         
     | 
| 
      
 9 
     | 
    
         
            +
              # rules (unlike Entities) and named as an imperative (_Add_Doc_, _Get_Doc_).
         
     | 
| 
      
 10 
     | 
    
         
            +
              #
         
     | 
| 
      
 11 
     | 
    
         
            +
              # Typical use case provides 5 methods:
         
     | 
| 
      
 12 
     | 
    
         
            +
              #
         
     | 
| 
      
 13 
     | 
    
         
            +
              # +new+:: class method that initializes use case instance.
         
     | 
| 
      
 14 
     | 
    
         
            +
              # +subscribe+:: subscribes listeners for the use case notifications.
         
     | 
| 
      
 15 
     | 
    
         
            +
              # +run+:: implements the use case.
         
     | 
| 
      
 16 
     | 
    
         
            +
              # +run!+:: raises exceptions in case of errors.
         
     | 
| 
      
 17 
     | 
    
         
            +
              # +errors+:: collects use case errors.
         
     | 
| 
      
 18 
     | 
    
         
            +
              #
         
     | 
| 
      
 19 
     | 
    
         
            +
              # The +run+ method returns a corresponding Value object, and notifies
         
     | 
| 
      
 20 
     | 
    
         
            +
              # subscribers about the results (following The Observer Pattern).
         
     | 
| 
      
 21 
     | 
    
         
            +
              #
         
     | 
| 
      
 22 
     | 
    
         
            +
              # = Usage
         
     | 
| 
      
 23 
     | 
    
         
            +
              #
         
     | 
| 
      
 24 
     | 
    
         
            +
              # Inherit a use case from the <tt>Hexx::UseCase</tt> class:
         
     | 
| 
      
 25 
     | 
    
         
            +
              #
         
     | 
| 
      
 26 
     | 
    
         
            +
              #   # app/my_domain/use_cases/do_something.rb
         
     | 
| 
      
 27 
     | 
    
         
            +
              #   require "hexx"
         
     | 
| 
      
 28 
     | 
    
         
            +
              #
         
     | 
| 
      
 29 
     | 
    
         
            +
              #   module MyDomain
         
     | 
| 
      
 30 
     | 
    
         
            +
              #     class DoSomething < Hexx::UseCase
         
     | 
| 
      
 31 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 32 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 33 
     | 
    
         
            +
              #
         
     | 
| 
      
 34 
     | 
    
         
            +
              # Then add a <tt>run!</tt> instance method to the Use Case.
         
     | 
| 
      
 35 
     | 
    
         
            +
              #
         
     | 
| 
      
 36 
     | 
    
         
            +
              #   class DoSomething < Hexx::UseCase
         
     | 
| 
      
 37 
     | 
    
         
            +
              #
         
     | 
| 
      
 38 
     | 
    
         
            +
              #     def run!
         
     | 
| 
      
 39 
     | 
    
         
            +
              #       validate!
         
     | 
| 
      
 40 
     | 
    
         
            +
              #       # do something
         
     | 
| 
      
 41 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 42 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 43 
     | 
    
         
            +
              #
         
     | 
| 
      
 44 
     | 
    
         
            +
              # The +run+ (without a bang) method is defined by default (see below). If
         
     | 
| 
      
 45 
     | 
    
         
            +
              # you need catching some exceptions specifically, do it in your <tt>run!</tt>
         
     | 
| 
      
 46 
     | 
    
         
            +
              # method.
         
     | 
| 
      
 47 
     | 
    
         
            +
              #
         
     | 
| 
      
 48 
     | 
    
         
            +
              # Unless the <tt>run!</tt> method defined, calling the +run+ raises
         
     | 
| 
      
 49 
     | 
    
         
            +
              # the <tt>NotImplementedError</tt>.
         
     | 
| 
      
 50 
     | 
    
         
            +
              #
         
     | 
| 
      
 51 
     | 
    
         
            +
              # == Allow params
         
     | 
| 
      
 52 
     | 
    
         
            +
              #
         
     | 
| 
      
 53 
     | 
    
         
            +
              # Use case constructor takes one argument with a parameters hash.
         
     | 
| 
      
 54 
     | 
    
         
            +
              #
         
     | 
| 
      
 55 
     | 
    
         
            +
              #   use_case = DoSomething.new id: 1, name: "name"
         
     | 
| 
      
 56 
     | 
    
         
            +
              #
         
     | 
| 
      
 57 
     | 
    
         
            +
              # This sets the private argument +params+ to be blank hash.
         
     | 
| 
      
 58 
     | 
    
         
            +
              #
         
     | 
| 
      
 59 
     | 
    
         
            +
              #   use_case.send :params # => {}
         
     | 
| 
      
 60 
     | 
    
         
            +
              #
         
     | 
| 
      
 61 
     | 
    
         
            +
              # For options to be assigned to +params+, their keys should be whitelisted:
         
     | 
| 
      
 62 
     | 
    
         
            +
              #
         
     | 
| 
      
 63 
     | 
    
         
            +
              #   class DoSomething < Hexx::UseCase
         
     | 
| 
      
 64 
     | 
    
         
            +
              #
         
     | 
| 
      
 65 
     | 
    
         
            +
              #     allow_params :id, :name
         
     | 
| 
      
 66 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 67 
     | 
    
         
            +
              #
         
     | 
| 
      
 68 
     | 
    
         
            +
              # This will allow assigning values. Note that all the keys are stringified:
         
     | 
| 
      
 69 
     | 
    
         
            +
              #
         
     | 
| 
      
 70 
     | 
    
         
            +
              #   use_case = DoSomething.new id: 1, name: "name", wrong_key: :value
         
     | 
| 
      
 71 
     | 
    
         
            +
              #   use_case.send :params # => { "id" => 1, "name" => "name" }
         
     | 
| 
      
 72 
     | 
    
         
            +
              #
         
     | 
| 
      
 73 
     | 
    
         
            +
              # == Validations
         
     | 
| 
      
 74 
     | 
    
         
            +
              #
         
     | 
| 
      
 75 
     | 
    
         
            +
              # You can use ActiveRecord validations.
         
     | 
| 
      
 76 
     | 
    
         
            +
              #
         
     | 
| 
      
 77 
     | 
    
         
            +
              # Be careful! Both the <tt>valid?</tt>, and <tt>invalid?</tt> are private.
         
     | 
| 
      
 78 
     | 
    
         
            +
              # It is expected validations to be used implicitly in a course of use case
         
     | 
| 
      
 79 
     | 
    
         
            +
              # running.
         
     | 
| 
      
 80 
     | 
    
         
            +
              #
         
     | 
| 
      
 81 
     | 
    
         
            +
              # To do this a private method <tt>validate!</tt> is available. It raises the
         
     | 
| 
      
 82 
     | 
    
         
            +
              # <tt>Hexx::UseCaseInvalid</tt> exception in case of validation fails.
         
     | 
| 
      
 83 
     | 
    
         
            +
              #
         
     | 
| 
      
 84 
     | 
    
         
            +
              # Note the <tt>validate!</tt> private method call from the <tt>run!</tt>
         
     | 
| 
      
 85 
     | 
    
         
            +
              # method in the example above.
         
     | 
| 
      
 86 
     | 
    
         
            +
              #
         
     | 
| 
      
 87 
     | 
    
         
            +
              # == Running a use case
         
     | 
| 
      
 88 
     | 
    
         
            +
              #
         
     | 
| 
      
 89 
     | 
    
         
            +
              # The <tt>run</tt> method is defined in a base class. This method
         
     | 
| 
      
 90 
     | 
    
         
            +
              # catches some exceptions and publishes corresponding notifications:
         
     | 
| 
      
 91 
     | 
    
         
            +
              #
         
     | 
| 
      
 92 
     | 
    
         
            +
              # <tt>Hexx::NotFoundError</tt>:: publishes the <tt>not_found(messages)</tt>;
         
     | 
| 
      
 93 
     | 
    
         
            +
              # <tt>Hexx::UseCaseInvalid</tt>:: publishes the <tt>error(messages)</tt>;
         
     | 
| 
      
 94 
     | 
    
         
            +
              # <tt>Hexx::EntityInvalid</tt>:: publishes the <tt>error(messages)</tt>;
         
     | 
| 
      
 95 
     | 
    
         
            +
              # <tt>StandardError</tt>:: any other runtume exception.
         
     | 
| 
      
 96 
     | 
    
         
            +
              #
         
     | 
| 
      
 97 
     | 
    
         
            +
              # == Notifications publishing
         
     | 
| 
      
 98 
     | 
    
         
            +
              #
         
     | 
| 
      
 99 
     | 
    
         
            +
              # A use case is expected to publish notifications for its subscribers.
         
     | 
| 
      
 100 
     | 
    
         
            +
              #
         
     | 
| 
      
 101 
     | 
    
         
            +
              #   class DoSomething < Hexx::UseCase
         
     | 
| 
      
 102 
     | 
    
         
            +
              #
         
     | 
| 
      
 103 
     | 
    
         
            +
              #     def run!
         
     | 
| 
      
 104 
     | 
    
         
            +
              #       validate!
         
     | 
| 
      
 105 
     | 
    
         
            +
              #       # do something (raise in case of any error)
         
     | 
| 
      
 106 
     | 
    
         
            +
              #       publish :done, result
         
     | 
| 
      
 107 
     | 
    
         
            +
              #       return result
         
     | 
| 
      
 108 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 109 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 110 
     | 
    
         
            +
              #
         
     | 
| 
      
 111 
     | 
    
         
            +
              # This will call a +done+ method of any subscriber. For details see the
         
     | 
| 
      
 112 
     | 
    
         
            +
              # {wisper gem documentation}[https://github.com/krisleech/wisper].
         
     | 
| 
      
 113 
     | 
    
         
            +
              #
         
     | 
| 
      
 114 
     | 
    
         
            +
              # == Calling a use case
         
     | 
| 
      
 115 
     | 
    
         
            +
              #
         
     | 
| 
      
 116 
     | 
    
         
            +
              # Use cases can be called in two styles:
         
     | 
| 
      
 117 
     | 
    
         
            +
              #
         
     | 
| 
      
 118 
     | 
    
         
            +
              # === Observer Pattern style (main usage)
         
     | 
| 
      
 119 
     | 
    
         
            +
              #
         
     | 
| 
      
 120 
     | 
    
         
            +
              # From a controller you can call a use case:
         
     | 
| 
      
 121 
     | 
    
         
            +
              #
         
     | 
| 
      
 122 
     | 
    
         
            +
              #   # app/controllers/my_controller.rb
         
     | 
| 
      
 123 
     | 
    
         
            +
              #   class MyController < ActionController::Base
         
     | 
| 
      
 124 
     | 
    
         
            +
              #
         
     | 
| 
      
 125 
     | 
    
         
            +
              #     def my_action
         
     | 
| 
      
 126 
     | 
    
         
            +
              #       # initialize a use case
         
     | 
| 
      
 127 
     | 
    
         
            +
              #       use_case = DoSomething.new params
         
     | 
| 
      
 128 
     | 
    
         
            +
              #       # subscribe both controller (in a presenter role) and other services
         
     | 
| 
      
 129 
     | 
    
         
            +
              #       # such as mailers etc. to receive notifications.
         
     | 
| 
      
 130 
     | 
    
         
            +
              #       use_case.subscribe self
         
     | 
| 
      
 131 
     | 
    
         
            +
              #       use_case.subscribe MyMailer.new
         
     | 
| 
      
 132 
     | 
    
         
            +
              #       # run a use_case
         
     | 
| 
      
 133 
     | 
    
         
            +
              #       use_case.run
         
     | 
| 
      
 134 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 135 
     | 
    
         
            +
              #
         
     | 
| 
      
 136 
     | 
    
         
            +
              #     # the method will be called by <tt>use_case.run</tt> in case of
         
     | 
| 
      
 137 
     | 
    
         
            +
              #     # success (see the Notification publishing example above).
         
     | 
| 
      
 138 
     | 
    
         
            +
              #     def done(result)
         
     | 
| 
      
 139 
     | 
    
         
            +
              #       # return a response 200 to the user
         
     | 
| 
      
 140 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 141 
     | 
    
         
            +
              #
         
     | 
| 
      
 142 
     | 
    
         
            +
              #     # the method will be called by <tt>use_case.run</tt> in case of
         
     | 
| 
      
 143 
     | 
    
         
            +
              #     # NotFoundError raised.
         
     | 
| 
      
 144 
     | 
    
         
            +
              #     def not_found(options = {})
         
     | 
| 
      
 145 
     | 
    
         
            +
              #       # return a response 404 to the user
         
     | 
| 
      
 146 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 147 
     | 
    
         
            +
              #
         
     | 
| 
      
 148 
     | 
    
         
            +
              #     # this method will be called by use_case in case of any error
         
     | 
| 
      
 149 
     | 
    
         
            +
              #     def error(messages = [])
         
     | 
| 
      
 150 
     | 
    
         
            +
              #       # return a response 422 to the user
         
     | 
| 
      
 151 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 152 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 153 
     | 
    
         
            +
              #
         
     | 
| 
      
 154 
     | 
    
         
            +
              # === Procedural style
         
     | 
| 
      
 155 
     | 
    
         
            +
              #
         
     | 
| 
      
 156 
     | 
    
         
            +
              # When you use a case from another case it can be useful to get value directly
         
     | 
| 
      
 157 
     | 
    
         
            +
              # without a subscription.
         
     | 
| 
      
 158 
     | 
    
         
            +
              #
         
     | 
| 
      
 159 
     | 
    
         
            +
              #   use_case = DoSomething.new params
         
     | 
| 
      
 160 
     | 
    
         
            +
              #   result = use_case.run
         
     | 
| 
      
 161 
     | 
    
         
            +
              #
         
     | 
| 
      
 162 
     | 
    
         
            +
              # The +run+ method returns nil in case of any error.
         
     | 
| 
      
 163 
     | 
    
         
            +
              #
         
     | 
| 
      
 164 
     | 
    
         
            +
              # = Dependencies notes
         
     | 
| 
      
 165 
     | 
    
         
            +
              #
         
     | 
| 
      
 166 
     | 
    
         
            +
              # Use cases depends on:
         
     | 
| 
      
 167 
     | 
    
         
            +
              #
         
     | 
| 
      
 168 
     | 
    
         
            +
              # * *Entities* and their *Repositories*;
         
     | 
| 
      
 169 
     | 
    
         
            +
              # * *Values* as an interfaces to external services (controllers, mailers etc.)
         
     | 
| 
      
 170 
     | 
    
         
            +
              #
         
     | 
| 
      
 171 
     | 
    
         
            +
              # Use cases should not depend from external services outside of the domain
         
     | 
| 
      
 172 
     | 
    
         
            +
              # model: controllers, mailers, databases etc.
         
     | 
| 
      
 173 
     | 
    
         
            +
              #
         
     | 
| 
      
 174 
     | 
    
         
            +
              class UseCase
         
     | 
| 
      
 175 
     | 
    
         
            +
                include ActiveModel::Validations, Wisper::Publisher
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                  def allow_params(*keys)
         
     | 
| 
      
 180 
     | 
    
         
            +
                    @params = keys.map(&:to_s)
         
     | 
| 
      
 181 
     | 
    
         
            +
                  end
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                  private
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                  def params
         
     | 
| 
      
 186 
     | 
    
         
            +
                    @params ||= []
         
     | 
| 
      
 187 
     | 
    
         
            +
                  end
         
     | 
| 
      
 188 
     | 
    
         
            +
                end
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                def initialize(options = {})
         
     | 
| 
      
 191 
     | 
    
         
            +
                  if options.is_a? Hash
         
     | 
| 
      
 192 
     | 
    
         
            +
                    @params = options.stringify_keys.slice(*self.class.send(:params))
         
     | 
| 
      
 193 
     | 
    
         
            +
                  else
         
     | 
| 
      
 194 
     | 
    
         
            +
                    @params = {}
         
     | 
| 
      
 195 
     | 
    
         
            +
                  end
         
     | 
| 
      
 196 
     | 
    
         
            +
                end
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
                def run!
         
     | 
| 
      
 199 
     | 
    
         
            +
                  fail(NotImplementedError.new "#{ self.class.name }#run! not implemented")
         
     | 
| 
      
 200 
     | 
    
         
            +
                end
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
                def run
         
     | 
| 
      
 203 
     | 
    
         
            +
                  run!
         
     | 
| 
      
 204 
     | 
    
         
            +
                rescue Hexx::NotFoundError => error
         
     | 
| 
      
 205 
     | 
    
         
            +
                  finish_with :not_found, error.errors.values
         
     | 
| 
      
 206 
     | 
    
         
            +
                rescue Hexx::RuntimeError => error
         
     | 
| 
      
 207 
     | 
    
         
            +
                  finish_with :error, error.errors.values
         
     | 
| 
      
 208 
     | 
    
         
            +
                rescue StandardError => error
         
     | 
| 
      
 209 
     | 
    
         
            +
                  finish_with :error, [error.message]
         
     | 
| 
      
 210 
     | 
    
         
            +
                end
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                private :valid?, :invalid?
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                private
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
                attr_reader :params
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                def validate!
         
     | 
| 
      
 219 
     | 
    
         
            +
                  return if valid?
         
     | 
| 
      
 220 
     | 
    
         
            +
                  fail UseCaseInvalid.new(self)
         
     | 
| 
      
 221 
     | 
    
         
            +
                end
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
                def finish_with(name, arg)
         
     | 
| 
      
 224 
     | 
    
         
            +
                  publish name, arg
         
     | 
| 
      
 225 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 226 
     | 
    
         
            +
                end
         
     | 
| 
      
 227 
     | 
    
         
            +
              end
         
     | 
| 
      
 228 
     | 
    
         
            +
            end
         
     |