pathway 0.0.9
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/.gitignore +12 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +42 -0
- data/Rakefile +6 -0
- data/bin/byebug +17 -0
- data/bin/console +14 -0
- data/bin/rake +17 -0
- data/bin/rspec +17 -0
- data/bin/setup +8 -0
- data/lib/pathway.rb +192 -0
- data/lib/pathway/plugins/authorization.rb +26 -0
- data/lib/pathway/plugins/dry_validation.rb +73 -0
- data/lib/pathway/plugins/responder.rb +14 -0
- data/lib/pathway/plugins/sequel_models.rb +57 -0
- data/lib/pathway/responder.rb +35 -0
- data/lib/pathway/result.rb +61 -0
- data/lib/pathway/rspec.rb +1 -0
- data/lib/pathway/rspec/matchers.rb +4 -0
- data/lib/pathway/rspec/matchers/accept_optional_fields.rb +41 -0
- data/lib/pathway/rspec/matchers/fail_on.rb +98 -0
- data/lib/pathway/rspec/matchers/field_list_helpers.rb +21 -0
- data/lib/pathway/rspec/matchers/form_schema_helpers.rb +57 -0
- data/lib/pathway/rspec/matchers/list_helpers.rb +17 -0
- data/lib/pathway/rspec/matchers/require_fields.rb +41 -0
- data/lib/pathway/rspec/matchers/succeed_on.rb +46 -0
- data/lib/pathway/version.rb +3 -0
- data/pathway.gemspec +37 -0
- metadata +213 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: f08df7daa90f1675a099551783c3bba0cb8371f8
         | 
| 4 | 
            +
              data.tar.gz: 313773af0d379310bd55d5bbfc536ce51a91b70a
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 0c9c704a68d3f24756cf8b43a6049663bcecaf8055b9c6794c227fad096770a346a3d7d6b00b1da53013d8879fe3693ab469b95298ca167c5d550f46f56c7b4f
         | 
| 7 | 
            +
              data.tar.gz: fbf219d58c759cadb6753317b828497e7cba6b133edda435b95c33a0d72fdfca9e3c3c61c52b4ddaa25dc3ab65d194181c1a70998602ed8414eb927c7fa6ed47
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.rspec
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            The MIT License (MIT)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2017 Pablo Herrero
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            +
            all copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
         | 
| 21 | 
            +
            THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            # Pathway
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            [](https://circleci.com/gh/pabloh/pathway/tree/master)
         | 
| 4 | 
            +
            [](https://coveralls.io/github/pabloh/pathway?branch=master)
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/pathway`. To experiment with that code, run `bin/console` for an interactive prompt.
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            TODO: Delete this and the text above, and describe your gem
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            ## Installation
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            Add this line to your application's Gemfile:
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ```ruby
         | 
| 15 | 
            +
            gem 'pathway'
         | 
| 16 | 
            +
            ```
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            And then execute:
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                $ bundle
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            Or install it yourself as:
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                $ gem install pathway
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            ## Usage
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            TODO: Write usage instructions here
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            ## Development
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            ## Contributing
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            Bug reports and pull requests are welcome on GitHub at https://github.com/pabloh/pathway.
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            ## License
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/bin/byebug
    ADDED
    
    | @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            # This file was generated by Bundler.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # The application 'byebug' is installed as part of a gem, and
         | 
| 7 | 
            +
            # this file is here to facilitate running it.
         | 
| 8 | 
            +
            #
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            require "pathname"
         | 
| 11 | 
            +
            ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
         | 
| 12 | 
            +
              Pathname.new(__FILE__).realpath)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require "rubygems"
         | 
| 15 | 
            +
            require "bundler/setup"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            load Gem.bin_path("byebug", "byebug")
         | 
    
        data/bin/console
    ADDED
    
    | @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "bundler/setup"
         | 
| 4 | 
            +
            require "pathway"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # You can add fixtures and/or initialization code here to make experimenting
         | 
| 7 | 
            +
            # with your gem easier. You can also use a different console, if you like.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # (If you use this, don't forget to add pry to your Gemfile!)
         | 
| 10 | 
            +
            # require "pry"
         | 
| 11 | 
            +
            # Pry.start
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            require "pry"
         | 
| 14 | 
            +
            Pry.start(Pathway)
         | 
    
        data/bin/rake
    ADDED
    
    | @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            # This file was generated by Bundler.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # The application 'rake' is installed as part of a gem, and
         | 
| 7 | 
            +
            # this file is here to facilitate running it.
         | 
| 8 | 
            +
            #
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            require "pathname"
         | 
| 11 | 
            +
            ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
         | 
| 12 | 
            +
              Pathname.new(__FILE__).realpath)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require "rubygems"
         | 
| 15 | 
            +
            require "bundler/setup"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            load Gem.bin_path("rake", "rake")
         | 
    
        data/bin/rspec
    ADDED
    
    | @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            # This file was generated by Bundler.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # The application 'rspec' is installed as part of a gem, and
         | 
| 7 | 
            +
            # this file is here to facilitate running it.
         | 
| 8 | 
            +
            #
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            require "pathname"
         | 
| 11 | 
            +
            ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
         | 
| 12 | 
            +
              Pathname.new(__FILE__).realpath)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require "rubygems"
         | 
| 15 | 
            +
            require "bundler/setup"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            load Gem.bin_path("rspec-core", "rspec")
         | 
    
        data/bin/setup
    ADDED
    
    
    
        data/lib/pathway.rb
    ADDED
    
    | @@ -0,0 +1,192 @@ | |
| 1 | 
            +
            require 'forwardable'
         | 
| 2 | 
            +
            require 'inflecto'
         | 
| 3 | 
            +
            require 'contextualizer'
         | 
| 4 | 
            +
            require 'pathway/version'
         | 
| 5 | 
            +
            require 'pathway/result'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Pathway
         | 
| 8 | 
            +
              class Operation
         | 
| 9 | 
            +
                def self.plugin(name)
         | 
| 10 | 
            +
                  require "pathway/plugins/#{Inflecto.underscore(name)}" if name.is_a?(Symbol)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  plugin = name.is_a?(Module) ? name : Plugins.const_get(Inflecto.camelize(name))
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  self.extend plugin::ClassMethods if plugin.const_defined? :ClassMethods
         | 
| 15 | 
            +
                  self.include plugin::InstanceMethods if plugin.const_defined? :InstanceMethods
         | 
| 16 | 
            +
                  # TODO: Separate DSL per operation hierarchy
         | 
| 17 | 
            +
                  DSL.include plugin::DSLMethods if plugin.const_defined? :DSLMethods
         | 
| 18 | 
            +
                  plugin.apply(self) if plugin.respond_to?(:apply)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              class DSL
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              class Error < StandardError
         | 
| 26 | 
            +
                attr_reader :type, :message, :details
         | 
| 27 | 
            +
                singleton_class.send :attr_accessor, :default_messages
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                @default_messages = {}
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                alias :error_type :type
         | 
| 32 | 
            +
                alias :error_message :message
         | 
| 33 | 
            +
                alias :errors :details
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def initialize(type:, message: nil, details: nil)
         | 
| 36 | 
            +
                  @type    = type.to_sym
         | 
| 37 | 
            +
                  @message = message || default_message_for(type)
         | 
| 38 | 
            +
                  @details = details || {}
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                private
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def default_message_for(type)
         | 
| 44 | 
            +
                  self.class.default_messages[type] || Inflecto.humanize(type)
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              class State
         | 
| 49 | 
            +
                extend Forwardable
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def initialize(operation, values = {})
         | 
| 52 | 
            +
                  @hash = operation.context.merge(values)
         | 
| 53 | 
            +
                  @result_key = operation.result_key
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                delegate %i([] []= fetch store include?) => :@hash
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def update(kargs)
         | 
| 59 | 
            +
                  @hash.update(kargs)
         | 
| 60 | 
            +
                  self
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def result
         | 
| 64 | 
            +
                  @hash[@result_key]
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def to_hash
         | 
| 68 | 
            +
                  @hash
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                alias :to_h :to_hash
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
              module Plugins
         | 
| 75 | 
            +
                module Base
         | 
| 76 | 
            +
                  module ClassMethods
         | 
| 77 | 
            +
                    attr_accessor :result_key
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    def process(&bl)
         | 
| 80 | 
            +
                      define_method(:call) do |input|
         | 
| 81 | 
            +
                        DSL.new(self, input).run(&bl)
         | 
| 82 | 
            +
                          .then { |state| state[result_key] }
         | 
| 83 | 
            +
                      end
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    alias :result_at :result_key=
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                    def inherited(subclass)
         | 
| 89 | 
            +
                      super
         | 
| 90 | 
            +
                      subclass.result_key = result_key
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  module InstanceMethods
         | 
| 95 | 
            +
                    extend Forwardable
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                    def result_key
         | 
| 98 | 
            +
                      self.class.result_key
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                    def call(*)
         | 
| 102 | 
            +
                      fail "must implement at subclass"
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    delegate %i[result success failure] => Result
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                    alias :wrap :result
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                    def error(type, message: nil, details: nil)
         | 
| 110 | 
            +
                      failure Error.new(type: type, message: message, details: details)
         | 
| 111 | 
            +
                    end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                    def wrap_if_present(value, type: :not_found, message: nil, details: [])
         | 
| 114 | 
            +
                      value.nil? ? error(type, message: message, details: details) : success(value)
         | 
| 115 | 
            +
                    end
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                  def self.apply(klass)
         | 
| 119 | 
            +
                    klass.extend Contextualizer
         | 
| 120 | 
            +
                    klass.result_key = :value
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  module DSLMethods
         | 
| 124 | 
            +
                    def initialize(operation, input)
         | 
| 125 | 
            +
                      @result = wrap(State.new(operation, input: input))
         | 
| 126 | 
            +
                      @operation = operation
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                    def run(&bl)
         | 
| 130 | 
            +
                      instance_eval(&bl)
         | 
| 131 | 
            +
                      @result
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                    # Execute step and preserve the former state
         | 
| 135 | 
            +
                    def step(callable, *args)
         | 
| 136 | 
            +
                      bl = _callable(callable)
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                      @result = @result.tee { |state| bl.call(state, *args) }
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                    # Execute step and modify the former state setting the key
         | 
| 142 | 
            +
                    def set(callable, *args, to: @operation.result_key)
         | 
| 143 | 
            +
                      bl = _callable(callable)
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                      @result = @result.then do |state|
         | 
| 146 | 
            +
                        wrap(bl.call(state, *args))
         | 
| 147 | 
            +
                          .then { |value| state.update(to => value) }
         | 
| 148 | 
            +
                      end
         | 
| 149 | 
            +
                    end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    # Execute step and replace the current state completely
         | 
| 152 | 
            +
                    def map(callable)
         | 
| 153 | 
            +
                      bl = _callable(callable)
         | 
| 154 | 
            +
                      @result = @result.then(bl)
         | 
| 155 | 
            +
                    end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                    def sequence(with_seq, &bl)
         | 
| 158 | 
            +
                      @result.then do |state|
         | 
| 159 | 
            +
                        seq = -> { @result = dup.run(&bl) }
         | 
| 160 | 
            +
                        _callable(with_seq).call(seq, state)
         | 
| 161 | 
            +
                      end
         | 
| 162 | 
            +
                    end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                    def guard(cond, &bl)
         | 
| 165 | 
            +
                      cond = _callable(cond)
         | 
| 166 | 
            +
                      sequence(-> seq, state {
         | 
| 167 | 
            +
                        seq.call if cond.call(state)
         | 
| 168 | 
            +
                      }, &bl)
         | 
| 169 | 
            +
                    end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                    private
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                    def wrap(obj)
         | 
| 174 | 
            +
                      Result.result(obj)
         | 
| 175 | 
            +
                    end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                    def _callable(callable)
         | 
| 178 | 
            +
                      case callable
         | 
| 179 | 
            +
                      when Proc
         | 
| 180 | 
            +
                        -> *args { @operation.instance_exec(*args, &callable) }
         | 
| 181 | 
            +
                      when Symbol
         | 
| 182 | 
            +
                        -> *args { @operation.send(callable, *args) }
         | 
| 183 | 
            +
                      else
         | 
| 184 | 
            +
                        callable
         | 
| 185 | 
            +
                      end
         | 
| 186 | 
            +
                    end
         | 
| 187 | 
            +
                  end
         | 
| 188 | 
            +
                end
         | 
| 189 | 
            +
              end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
              Operation.plugin Plugins::Base
         | 
| 192 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            module Pathway
         | 
| 2 | 
            +
              module Plugins
         | 
| 3 | 
            +
                module Authorization
         | 
| 4 | 
            +
                  module ClassMethods
         | 
| 5 | 
            +
                    def authorization(&block)
         | 
| 6 | 
            +
                      define_method(:authorized?, &block)
         | 
| 7 | 
            +
                    end
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  module InstanceMethods
         | 
| 11 | 
            +
                    def authorize(state, using: nil)
         | 
| 12 | 
            +
                      authorize_with(state[using || result_key]).then { state }
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    def authorize_with(*objs)
         | 
| 16 | 
            +
                      objs = objs.first if objs.size <= 1
         | 
| 17 | 
            +
                      authorized?(*objs) ? wrap(objs) : error(:forbidden)
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    def authorized?(*_)
         | 
| 21 | 
            +
                      true
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,73 @@ | |
| 1 | 
            +
            require 'dry/validation'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Pathway
         | 
| 4 | 
            +
              module Plugins
         | 
| 5 | 
            +
                module DryValidation
         | 
| 6 | 
            +
                  module ClassMethods
         | 
| 7 | 
            +
                    attr_reader :form_class
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    def form(base = nil, **opts, &block)
         | 
| 10 | 
            +
                      if block_given?
         | 
| 11 | 
            +
                        base ||= _base_form
         | 
| 12 | 
            +
                        self.form_class = Dry::Validation.Form(_form_class(base), _form_opts(opts), &block)
         | 
| 13 | 
            +
                      elsif base
         | 
| 14 | 
            +
                        self.form_class = _form_class(base)
         | 
| 15 | 
            +
                      else
         | 
| 16 | 
            +
                        raise ArgumentError, 'Either a form class or a block must be provided'
         | 
| 17 | 
            +
                      end
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    def form_class= klass
         | 
| 21 | 
            +
                      @builded_form = klass.options.empty? ? klass.new : nil
         | 
| 22 | 
            +
                      @form_class = klass
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    def build_form(opts = {})
         | 
| 26 | 
            +
                      @builded_form || form_class.new(opts)
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def inherited(subclass)
         | 
| 30 | 
            +
                      super
         | 
| 31 | 
            +
                      subclass.form_class = form_class
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    private
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    def _base_form
         | 
| 37 | 
            +
                      superclass.respond_to?(:form_class) ? superclass.form_class : Dry::Validation::Schema::Form
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    def _form_class(form)
         | 
| 41 | 
            +
                      form.is_a?(Class) ? form : form.class
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    def _form_opts(opts = {})
         | 
| 45 | 
            +
                      opts.merge(build: false)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  module InstanceMethods
         | 
| 50 | 
            +
                    extend Forwardable
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    delegate :build_form => 'self.class'
         | 
| 53 | 
            +
                    alias :form :build_form
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    def validate(state, with: [])
         | 
| 56 | 
            +
                      opts = Array(with).map { |key| [key, state[key]] }.to_h
         | 
| 57 | 
            +
                      validate_with(state[:input], opts)
         | 
| 58 | 
            +
                        .then { |params| state.update(params: params) }
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    def validate_with(params, opts = {})
         | 
| 62 | 
            +
                      val = form(opts).call(params)
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                      val.success? ? wrap(val.output) : error(:validation, details: val.messages)
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def self.apply(operation)
         | 
| 69 | 
            +
                    operation.form_class = Dry::Validation::Schema::Form
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
            end
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require 'pathway/responder'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Pathway
         | 
| 4 | 
            +
              module Plugins
         | 
| 5 | 
            +
                module Responder
         | 
| 6 | 
            +
                  module ClassMethods
         | 
| 7 | 
            +
                    def call(ctx, *params, &bl)
         | 
| 8 | 
            +
                      result = new(ctx).call(*params)
         | 
| 9 | 
            +
                      block_given? ? Pathway::Responder.respond(result, &bl) : result
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            require 'sequel/model'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Pathway
         | 
| 4 | 
            +
              module Plugins
         | 
| 5 | 
            +
                module SequelModels
         | 
| 6 | 
            +
                  module DSLMethods
         | 
| 7 | 
            +
                    def transaction(&bl)
         | 
| 8 | 
            +
                      sequence(-> seq, _ {
         | 
| 9 | 
            +
                        db.transaction(savepoint: true) do
         | 
| 10 | 
            +
                          raise Sequel::Rollback if seq.call.failure?
         | 
| 11 | 
            +
                        end
         | 
| 12 | 
            +
                      }, &bl)
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  module InstanceMethods
         | 
| 17 | 
            +
                    module Finder
         | 
| 18 | 
            +
                      def self.[](model_class, by: :id)
         | 
| 19 | 
            +
                        Module.new do
         | 
| 20 | 
            +
                          include InstanceMethods
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                          define_singleton_method :included do |klass|
         | 
| 23 | 
            +
                            klass.class_eval do
         | 
| 24 | 
            +
                              result_at Inflecto.underscore(model_class.name.split('::').last).to_sym
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                              define_method(:model_class) { model_class }
         | 
| 27 | 
            +
                              define_method(:field)       { by }
         | 
| 28 | 
            +
                              define_method(:db)          { model_class.db }
         | 
| 29 | 
            +
                            end
         | 
| 30 | 
            +
                          end
         | 
| 31 | 
            +
                        end
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    extend Forwardable
         | 
| 36 | 
            +
                    delegate %i[model_class db field] => 'self.class'
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    def fetch_model(state)
         | 
| 39 | 
            +
                      fetch_model_with(state[:input])
         | 
| 40 | 
            +
                        .then { |model| state.update(result_key => model) }
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    def fetch_model_with(params)
         | 
| 44 | 
            +
                      wrap_if_present(find_model_with(params[field]))
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    def build_model_with(params)
         | 
| 48 | 
            +
                      wrap(model_class.new(params))
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    def find_model_with(key)
         | 
| 52 | 
            +
                      model_class.first(field => key)
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            module Pathway
         | 
| 2 | 
            +
              class Responder
         | 
| 3 | 
            +
                def self.respond(result, &bl)
         | 
| 4 | 
            +
                  r = new(result, &bl)
         | 
| 5 | 
            +
                  r.respond
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize(result, &bl)
         | 
| 9 | 
            +
                  @result, @context, @fails = result, bl.binding.receiver, {}
         | 
| 10 | 
            +
                  instance_eval(&bl)
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def success(&bl)
         | 
| 14 | 
            +
                  @ok = bl
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def failure(type = nil, &bl)
         | 
| 18 | 
            +
                  if type.nil?
         | 
| 19 | 
            +
                    @fail_default = bl
         | 
| 20 | 
            +
                  else
         | 
| 21 | 
            +
                    @fails[type] = bl
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def respond
         | 
| 26 | 
            +
                  if @result.success?
         | 
| 27 | 
            +
                    @context.instance_exec(@result.value, &@ok)
         | 
| 28 | 
            +
                  elsif Error === @result.error && fail_block = @fails[@result.error.type]
         | 
| 29 | 
            +
                    @context.instance_exec(@result.error, &fail_block)
         | 
| 30 | 
            +
                  else
         | 
| 31 | 
            +
                    @context.instance_exec(@result.error, &@fail_default)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,61 @@ | |
| 1 | 
            +
            module Pathway
         | 
| 2 | 
            +
              class Result
         | 
| 3 | 
            +
                extend Forwardable
         | 
| 4 | 
            +
                attr_reader :value, :error
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                class Success < Result
         | 
| 7 | 
            +
                  def initialize(value)
         | 
| 8 | 
            +
                    @value = value
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def success?
         | 
| 12 | 
            +
                    true
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def then(bl=nil)
         | 
| 16 | 
            +
                    result(block_given? ? yield(value): bl.call(value))
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def tee(bl=nil, &block)
         | 
| 20 | 
            +
                    follow = self.then(bl, &block)
         | 
| 21 | 
            +
                    follow.failure? ? follow : self
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                class Failure < Result
         | 
| 26 | 
            +
                  def initialize(error)
         | 
| 27 | 
            +
                    @error = error
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def success?
         | 
| 31 | 
            +
                    false
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def then(_=nil)
         | 
| 35 | 
            +
                    self
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def tee(_=nil)
         | 
| 39 | 
            +
                    self
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def failure?
         | 
| 44 | 
            +
                  !success?
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def self.success(value)
         | 
| 48 | 
            +
                  Success.new(value)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def self.failure(error)
         | 
| 52 | 
            +
                  Failure.new(error)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def self.result(object)
         | 
| 56 | 
            +
                  object.is_a?(Result) ? object : success(object)
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                delegate :result => 'self.class'
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            require 'pathway/rspec/matchers'
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            require 'pathway/rspec/matchers/form_schema_helpers'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            RSpec::Matchers.define :accept_optional_fields do |*fields|
         | 
| 4 | 
            +
              match do |form|
         | 
| 5 | 
            +
                @form, @fields = form, fields
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                not_defined.empty? && not_optional.empty?
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              match_when_negated do |form|
         | 
| 11 | 
            +
                @form, @fields = form, fields
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                not_defined.empty? && optional.empty?
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              description do
         | 
| 17 | 
            +
                "accept #{field_list} as optional #{pluralize_fields}"
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              failure_message do
         | 
| 21 | 
            +
                "Expected to accept #{field_list} as optional #{pluralize_fields} but " +
         | 
| 22 | 
            +
                  [not_optional_list, not_defined_list].compact.join("; and ")
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              failure_message_when_negated do
         | 
| 26 | 
            +
                "Did not expect to accept #{field_list} as optional #{pluralize_fields} but " +
         | 
| 27 | 
            +
                  [optional_list, not_defined_list].compact.join("; and ")
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              include Pathway::Rspec::FormSchemaHelpers
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              def optional_list
         | 
| 33 | 
            +
                "#{as_list(optional)} #{were_was(optional)} optional" if optional.any?
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              def not_optional_list
         | 
| 37 | 
            +
                "#{as_list(not_required)} #{were_was(not_required)} not optional" if not_optional.any?
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            RSpec::Matchers.alias_matcher :accept_optional_field, :accept_optional_fields
         | 
| @@ -0,0 +1,98 @@ | |
| 1 | 
            +
            require 'pathway/rspec/matchers/list_helpers'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            RSpec::Matchers.define :fail_on do |input|
         | 
| 4 | 
            +
              match do |operation|
         | 
| 5 | 
            +
                @operation, @input = operation, input
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                failure? && type_matches? && message_matches? && details_matches?
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              match_when_negated do |operation|
         | 
| 11 | 
            +
                raise NotImplementedError, '`expect().not_to fail_on(input).with_type()` is not supported.' if @type
         | 
| 12 | 
            +
                raise NotImplementedError, '`expect().not_to fail_on(input).with_message()` is not supported.' if @message
         | 
| 13 | 
            +
                raise NotImplementedError, '`expect().not_to fail_on(input).with_details()` is not supported.' if @details
         | 
| 14 | 
            +
                @operation, @input = operation, input
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                !failure?
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              chain :type do |type|
         | 
| 20 | 
            +
                @type = type
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              alias :with_type :type
         | 
| 24 | 
            +
              alias :and_type :type
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              chain :message do |message|
         | 
| 27 | 
            +
                @message = message
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              alias :with_message :message
         | 
| 31 | 
            +
              alias :and_message :message
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              chain :details do |details|
         | 
| 34 | 
            +
                @details = details
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              alias :with_details :details
         | 
| 38 | 
            +
              alias :and_details :details
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              description do
         | 
| 41 | 
            +
                "fail" + (@type ? " with :#@type error" : '')
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              failure_message do
         | 
| 45 | 
            +
                if !failure?
         | 
| 46 | 
            +
                  "Expected operation to fail but it didn't"
         | 
| 47 | 
            +
                else
         | 
| 48 | 
            +
                  "Expected failed operation to " +
         | 
| 49 | 
            +
                    as_sentence(failure_descriptions, connector: '; ', last_connector: '; and ')
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              failure_message_when_negated do
         | 
| 54 | 
            +
                "Did not to expected operation to fail but it did"
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              def failure?
         | 
| 58 | 
            +
                result.failure?
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              def type_matches?
         | 
| 62 | 
            +
                @type.nil? || @type == error.type
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
              def message_matches?
         | 
| 66 | 
            +
                @message.nil? || values_match?(@message, error.message)
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              def details_matches?
         | 
| 70 | 
            +
                @details.nil? || values_match?(@details, error.details)
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
              def result
         | 
| 74 | 
            +
                @result ||= @operation.call(@input)
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              def error
         | 
| 78 | 
            +
                result.error
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
              def failure_descriptions
         | 
| 82 | 
            +
                [type_failure_description, message_failure_description, details_failure_description].compact
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
              def type_failure_description
         | 
| 86 | 
            +
                type_matches? ? nil : "have type :#@type but instead was :#{error.type}"
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
              def message_failure_description
         | 
| 90 | 
            +
                message_matches? ? nil : "have message like #{description_of(@message)} but instead got #{description_of(error.message)}"
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
              def details_failure_description
         | 
| 94 | 
            +
                details_matches? ? nil : "have details like #{description_of(@details)} but instead got #{description_of(error.details)}"
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
              include Pathway::Rspec::ListHelpers
         | 
| 98 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            require 'pathway/rspec/matchers/list_helpers'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Pathway
         | 
| 4 | 
            +
              module Rspec
         | 
| 5 | 
            +
                module FieldListHelpers
         | 
| 6 | 
            +
                  include ListHelpers
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def field_list
         | 
| 9 | 
            +
                    as_list(@fields)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def were_was(list)
         | 
| 13 | 
            +
                    list.size > 1 ? "were" : "was"
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def pluralize_fields
         | 
| 17 | 
            +
                    @fields.size > 1 ? "fields" : "field"
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            require 'pathway/rspec/matchers/field_list_helpers'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Pathway
         | 
| 4 | 
            +
              module Rspec
         | 
| 5 | 
            +
                module FormSchemaHelpers
         | 
| 6 | 
            +
                  include FieldListHelpers
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def rules
         | 
| 9 | 
            +
                    @form.rules
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def not_defined_list
         | 
| 13 | 
            +
                    "#{as_list(not_defined)} #{were_was(not_defined)} not defined" if not_defined.any?
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def required
         | 
| 17 | 
            +
                    @required ||= @fields.select { |field| required?(field) }
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def optional
         | 
| 21 | 
            +
                    @optional ||= @fields.select { |field| optional?(field) }
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def not_required
         | 
| 25 | 
            +
                    @not_required ||= defined - required
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def not_optional
         | 
| 29 | 
            +
                    @not_required ||= defined - optional
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def not_defined
         | 
| 33 | 
            +
                    @not_defined ||= @fields - defined
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def defined
         | 
| 37 | 
            +
                    @defined ||= @fields & rules.keys
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def optional?(field)
         | 
| 41 | 
            +
                    if rules[field]&.type == :implication
         | 
| 42 | 
            +
                      left = rules[field].left
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                      left.type == :predicate && left.name == :key? && left.args.first == field
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def required?(field)
         | 
| 49 | 
            +
                    if rules[field]&.type == :and
         | 
| 50 | 
            +
                      left = rules[field].left
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                      left.type == :predicate && left.name == :key? && left.args.first == field
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            module Pathway
         | 
| 2 | 
            +
              module Rspec
         | 
| 3 | 
            +
                module ListHelpers
         | 
| 4 | 
            +
                  def as_list(items)
         | 
| 5 | 
            +
                    as_sentence(items.map(&:inspect))
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def as_sentence(items, connector: ", ", last_connector: " and ")
         | 
| 9 | 
            +
                    *rest, last = items
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    result = String.new
         | 
| 12 | 
            +
                    result << rest.join(connector) << last_connector if rest.any?
         | 
| 13 | 
            +
                    result << last
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            require 'pathway/rspec/matchers/form_schema_helpers'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            RSpec::Matchers.define :require_fields do |*fields|
         | 
| 4 | 
            +
              match do |form|
         | 
| 5 | 
            +
                @form, @fields = form, fields
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                not_defined.empty? && not_required.empty?
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              match_when_negated do |form|
         | 
| 11 | 
            +
                @form, @fields = form, fields
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                not_defined.empty? && required.empty?
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              description do
         | 
| 17 | 
            +
                "require #{field_list} as #{pluralize_fields}"
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              failure_message do
         | 
| 21 | 
            +
                "Expected to require #{field_list} as #{pluralize_fields} but " +
         | 
| 22 | 
            +
                  [not_required_list, not_defined_list].compact.join("; and ")
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              failure_message_when_negated do
         | 
| 26 | 
            +
                "Did not expect to require #{field_list} as #{pluralize_fields} but " +
         | 
| 27 | 
            +
                  [required_list, not_defined_list].compact.join("; and ")
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              include Pathway::Rspec::FormSchemaHelpers
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              def required_list
         | 
| 33 | 
            +
                "#{as_list(required)} #{were_was(required)} required" if required.any?
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              def not_required_list
         | 
| 37 | 
            +
                "#{as_list(not_required)} #{were_was(not_required)} not required" if not_required.any?
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            RSpec::Matchers.alias_matcher :require_field, :require_fields
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            RSpec::Matchers.define :succeed_on do |input|
         | 
| 2 | 
            +
              match do |operation|
         | 
| 3 | 
            +
                @operation, @input = operation, input
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                success? && return_value_matches?
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              match_when_negated do |operation|
         | 
| 9 | 
            +
                raise NotImplementedError, '`expect().not_to succeed_on(input).returning()` is not supported.' if @value
         | 
| 10 | 
            +
                @operation, @input = operation, input
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                !success?
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              chain :returning do |value|
         | 
| 16 | 
            +
                @value = value
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              description do
         | 
| 20 | 
            +
                "be successful"
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              failure_message do
         | 
| 24 | 
            +
                if !success?
         | 
| 25 | 
            +
                  "Expected operation to be successful but failed with :#{result.error.type} error"
         | 
| 26 | 
            +
                else
         | 
| 27 | 
            +
                  "Expected successful operation to return #{description_of(@value)} but instead got #{description_of(result.value)}"
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              failure_message_when_negated do
         | 
| 32 | 
            +
                "Did not to expected operation to be successful but it was"
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              def success?
         | 
| 36 | 
            +
                result.success?
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def return_value_matches?
         | 
| 40 | 
            +
                @value.nil? || values_match?(@value, result.value)
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              def result
         | 
| 44 | 
            +
                @result ||= @operation.call(@input)
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
    
        data/pathway.gemspec
    ADDED
    
    | @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            # coding: utf-8
         | 
| 2 | 
            +
            lib = File.expand_path("../lib", __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require "pathway/version"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |spec|
         | 
| 7 | 
            +
              spec.name          = "pathway"
         | 
| 8 | 
            +
              spec.version       = Pathway::VERSION
         | 
| 9 | 
            +
              spec.authors       = ["Pablo Herrero"]
         | 
| 10 | 
            +
              spec.email         = ["pablodherrero@gmail.com"]
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              spec.summary       = %q{Define your bussines logic in simple steps.}
         | 
| 13 | 
            +
              spec.description   = %q{Define your bussines logic in simple steps.}
         | 
| 14 | 
            +
              #spec.homepage     = "TODO: Put your gem's website or public repo URL here."
         | 
| 15 | 
            +
              spec.license       = "MIT"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              spec.files         = `git ls-files -z`.split("\x0").reject do |f|
         | 
| 18 | 
            +
                f.match(%r{^(test|spec|features)/})
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
              spec.bindir        = "exe"
         | 
| 21 | 
            +
              spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         | 
| 22 | 
            +
              spec.require_paths = ["lib"]
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              spec.required_ruby_version = ">= 2.4.0"
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              spec.add_dependency "inflecto"
         | 
| 27 | 
            +
              spec.add_dependency "contextualizer", "~> 0.0.3"
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              spec.add_development_dependency "dry-validation", "~> 0.11"
         | 
| 30 | 
            +
              spec.add_development_dependency "bundler", ">= 1.14.0"
         | 
| 31 | 
            +
              spec.add_development_dependency "sequel", "~> 4.46.0"
         | 
| 32 | 
            +
              spec.add_development_dependency "rake", "~> 10.0"
         | 
| 33 | 
            +
              spec.add_development_dependency "rspec", "~> 3.0"
         | 
| 34 | 
            +
              spec.add_development_dependency "coveralls"
         | 
| 35 | 
            +
              spec.add_development_dependency "pry"
         | 
| 36 | 
            +
              spec.add_development_dependency "pry-byebug"
         | 
| 37 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,213 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: pathway
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.9
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Pablo Herrero
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: exe
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2017-07-04 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: inflecto
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - ">="
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '0'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - ">="
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '0'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: contextualizer
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: 0.0.3
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: 0.0.3
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: dry-validation
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '0.11'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '0.11'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: bundler
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - ">="
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: 1.14.0
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - ">="
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: 1.14.0
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: sequel
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - "~>"
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: 4.46.0
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - "~>"
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: 4.46.0
         | 
| 83 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            +
              name: rake
         | 
| 85 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 | 
            +
                requirements:
         | 
| 87 | 
            +
                - - "~>"
         | 
| 88 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            +
                    version: '10.0'
         | 
| 90 | 
            +
              type: :development
         | 
| 91 | 
            +
              prerelease: false
         | 
| 92 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 | 
            +
                requirements:
         | 
| 94 | 
            +
                - - "~>"
         | 
| 95 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            +
                    version: '10.0'
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: rspec
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - "~>"
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: '3.0'
         | 
| 104 | 
            +
              type: :development
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - "~>"
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: '3.0'
         | 
| 111 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 112 | 
            +
              name: coveralls
         | 
| 113 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 114 | 
            +
                requirements:
         | 
| 115 | 
            +
                - - ">="
         | 
| 116 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 117 | 
            +
                    version: '0'
         | 
| 118 | 
            +
              type: :development
         | 
| 119 | 
            +
              prerelease: false
         | 
| 120 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 121 | 
            +
                requirements:
         | 
| 122 | 
            +
                - - ">="
         | 
| 123 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 124 | 
            +
                    version: '0'
         | 
| 125 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 126 | 
            +
              name: pry
         | 
| 127 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 128 | 
            +
                requirements:
         | 
| 129 | 
            +
                - - ">="
         | 
| 130 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 131 | 
            +
                    version: '0'
         | 
| 132 | 
            +
              type: :development
         | 
| 133 | 
            +
              prerelease: false
         | 
| 134 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 135 | 
            +
                requirements:
         | 
| 136 | 
            +
                - - ">="
         | 
| 137 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 138 | 
            +
                    version: '0'
         | 
| 139 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 140 | 
            +
              name: pry-byebug
         | 
| 141 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 142 | 
            +
                requirements:
         | 
| 143 | 
            +
                - - ">="
         | 
| 144 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 145 | 
            +
                    version: '0'
         | 
| 146 | 
            +
              type: :development
         | 
| 147 | 
            +
              prerelease: false
         | 
| 148 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 149 | 
            +
                requirements:
         | 
| 150 | 
            +
                - - ">="
         | 
| 151 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 152 | 
            +
                    version: '0'
         | 
| 153 | 
            +
            description: Define your bussines logic in simple steps.
         | 
| 154 | 
            +
            email:
         | 
| 155 | 
            +
            - pablodherrero@gmail.com
         | 
| 156 | 
            +
            executables: []
         | 
| 157 | 
            +
            extensions: []
         | 
| 158 | 
            +
            extra_rdoc_files: []
         | 
| 159 | 
            +
            files:
         | 
| 160 | 
            +
            - ".gitignore"
         | 
| 161 | 
            +
            - ".rspec"
         | 
| 162 | 
            +
            - Gemfile
         | 
| 163 | 
            +
            - LICENSE.txt
         | 
| 164 | 
            +
            - README.md
         | 
| 165 | 
            +
            - Rakefile
         | 
| 166 | 
            +
            - bin/byebug
         | 
| 167 | 
            +
            - bin/console
         | 
| 168 | 
            +
            - bin/rake
         | 
| 169 | 
            +
            - bin/rspec
         | 
| 170 | 
            +
            - bin/setup
         | 
| 171 | 
            +
            - lib/pathway.rb
         | 
| 172 | 
            +
            - lib/pathway/plugins/authorization.rb
         | 
| 173 | 
            +
            - lib/pathway/plugins/dry_validation.rb
         | 
| 174 | 
            +
            - lib/pathway/plugins/responder.rb
         | 
| 175 | 
            +
            - lib/pathway/plugins/sequel_models.rb
         | 
| 176 | 
            +
            - lib/pathway/responder.rb
         | 
| 177 | 
            +
            - lib/pathway/result.rb
         | 
| 178 | 
            +
            - lib/pathway/rspec.rb
         | 
| 179 | 
            +
            - lib/pathway/rspec/matchers.rb
         | 
| 180 | 
            +
            - lib/pathway/rspec/matchers/accept_optional_fields.rb
         | 
| 181 | 
            +
            - lib/pathway/rspec/matchers/fail_on.rb
         | 
| 182 | 
            +
            - lib/pathway/rspec/matchers/field_list_helpers.rb
         | 
| 183 | 
            +
            - lib/pathway/rspec/matchers/form_schema_helpers.rb
         | 
| 184 | 
            +
            - lib/pathway/rspec/matchers/list_helpers.rb
         | 
| 185 | 
            +
            - lib/pathway/rspec/matchers/require_fields.rb
         | 
| 186 | 
            +
            - lib/pathway/rspec/matchers/succeed_on.rb
         | 
| 187 | 
            +
            - lib/pathway/version.rb
         | 
| 188 | 
            +
            - pathway.gemspec
         | 
| 189 | 
            +
            homepage: 
         | 
| 190 | 
            +
            licenses:
         | 
| 191 | 
            +
            - MIT
         | 
| 192 | 
            +
            metadata: {}
         | 
| 193 | 
            +
            post_install_message: 
         | 
| 194 | 
            +
            rdoc_options: []
         | 
| 195 | 
            +
            require_paths:
         | 
| 196 | 
            +
            - lib
         | 
| 197 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 198 | 
            +
              requirements:
         | 
| 199 | 
            +
              - - ">="
         | 
| 200 | 
            +
                - !ruby/object:Gem::Version
         | 
| 201 | 
            +
                  version: 2.4.0
         | 
| 202 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 203 | 
            +
              requirements:
         | 
| 204 | 
            +
              - - ">="
         | 
| 205 | 
            +
                - !ruby/object:Gem::Version
         | 
| 206 | 
            +
                  version: '0'
         | 
| 207 | 
            +
            requirements: []
         | 
| 208 | 
            +
            rubyforge_project: 
         | 
| 209 | 
            +
            rubygems_version: 2.6.9
         | 
| 210 | 
            +
            signing_key: 
         | 
| 211 | 
            +
            specification_version: 4
         | 
| 212 | 
            +
            summary: Define your bussines logic in simple steps.
         | 
| 213 | 
            +
            test_files: []
         |