course 0.2.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 42b93dcd580bb0523ecdab134c05b492f61e4801
4
+ data.tar.gz: 3d8210ab14b6e2dd573c6535ba589d0dd862f489
5
+ SHA512:
6
+ metadata.gz: 431a3ca4c25d0a76513866532f52b56850717ac24e00cdd0ced638ecd5676a951726d2a11cc001ef7f8461d47be5c38ea150ff591d84952e6825c9514f284f90
7
+ data.tar.gz: 10b86c99bdf4d84b921e1d6e5e178aea406cd92efcefbe782c30ba42eb889d1c34617fc202da17af6dc1d73f81621f5865849fef0fd55cdf1989caa07415d608
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.byebug*
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ /.idea/
@@ -0,0 +1,4 @@
1
+ PreCommit:
2
+ RuboCop:
3
+ enabled: true
4
+ on_warn: fail # Treat all warnings as failures
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --order rand
@@ -0,0 +1,20 @@
1
+ # https://github.com/bbatsov/rubocop/blob/master/config/default.yml
2
+
3
+ AllCops:
4
+ Exclude:
5
+ - '*.gemspec'
6
+ TargetRubyVersion: 2.4.1
7
+ DisplayCopNames: true
8
+
9
+ Metrics/LineLength:
10
+ Max: 110
11
+
12
+ Style/Documentation:
13
+ Enabled: false
14
+
15
+ Metrics/BlockLength:
16
+ Exclude:
17
+ - spec/**/*.rb
18
+
19
+ Style/SignalException:
20
+ Enabled: false
@@ -0,0 +1 @@
1
+ course
@@ -0,0 +1 @@
1
+ 2.4.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in course.gemspec
6
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Gleb Sinyavsky
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 all
13
+ 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 THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ changelog:
2
+ -!(which conventional-changelog) && npm i -g conventional-changelog-cli
3
+ conventional-changelog -p angular -i CHANGELOG.md -s -r 0
4
+
5
+ yard_server:
6
+ open http://localhost:8808
7
+ yard server --reload
8
+
9
+ .PHONY: all
@@ -0,0 +1,110 @@
1
+ # Course
2
+
3
+ Inspired and forked from [opie](https://github.com/guzart/opie)
4
+
5
+ **Course gives you a simple API for creating Operations using the
6
+ [Railsway oriented programming](https://vimeo.com/113707214) paradigm.**
7
+
8
+ ## Usage
9
+
10
+ **Simple Usage:**
11
+
12
+ ```ruby
13
+ # Create an Operation for completing a Todo
14
+ class Todos::CompleteTodo < Course::Operation
15
+ step :find_todo
16
+ step :mark_as_complete
17
+
18
+ def find_todo(todo_id)
19
+ todo = Todo.find_by(id: todo_id)
20
+ fail(:not_found, "Could not find the Todo using id: #{todo_id}") unless todo
21
+ todo
22
+ end
23
+
24
+ def mark_as_complete(todo)
25
+ success = todo.update(completed_at: Time.zone.now)
26
+ fail(:update) unless success
27
+ todo
28
+ end
29
+ end
30
+
31
+ class TodosController < ApplicationController
32
+ def complete
33
+ # invoke the operation
34
+ result = Todos::CompleteTodo.(params[:id])
35
+ if result.success? # if #success?
36
+ render status: :created, json: result.output # use output
37
+ else
38
+ render status: :bad_request, json: { error: error_message(result.failure) } # otherwise use #failure
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def error_message(failure)
45
+ case failure[:type]
46
+ when :not_found then failure[:data]
47
+ when :update then 'We were unable to make the changes to your todo'
48
+ else 'There was an unexpected error, sorry for the inconvenience'
49
+ end
50
+ end
51
+ end
52
+ ```
53
+
54
+ ## API
55
+
56
+ The `Course::Operation` API:
57
+ * `::step(Symbol) -> void` indicates a method that is executed in the operation sequence
58
+ * `#success? -> Boolean` indicates whether the operation was successful
59
+ * `#failure? -> Boolean` indicates whether the operation was a failure
60
+ * `#failure -> Hash | nil` the erorr if the operation is a `failure?`, nil when it's a success
61
+ * `#failures -> Array<Hash> | nil` an array with all errors
62
+ * `#output -> *` if succcessful, it returns the operation final output
63
+ validation error
64
+
65
+ Internal API:
66
+ * `#fail(error_type: Symbol, error_data: *) -> Hash`
67
+
68
+ _Tentative API_
69
+
70
+ * `::step(Array<Symbol>) -> void` a series of methods to be called in parallel
71
+ * `::step(Course::Step) -> void` an enforcer of a step signature which helps to compose other steps
72
+ * `::failure(Symbol) -> void` indicates the method that handles failures
73
+
74
+ ## Installation
75
+
76
+ Add this line to your application's Gemfile:
77
+
78
+ ```ruby
79
+ gem 'course'
80
+ ```
81
+
82
+ And then execute:
83
+
84
+ ```bash
85
+ $ bundle
86
+ ```
87
+
88
+ Or install it yourself as:
89
+
90
+ ```bash
91
+ $ gem install course
92
+ ```
93
+
94
+ ## Development
95
+
96
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
97
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
98
+
99
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update
100
+ the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for
101
+ the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
102
+
103
+ ## Contributing
104
+
105
+ Bug reports and pull requests are welcome on GitHub at https://github.com/zhulik/course
106
+
107
+
108
+ ## License
109
+
110
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'course'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'guard' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', Pathname.new(__FILE__).realpath)
13
+
14
+ require 'rubygems'
15
+ require 'bundler/setup'
16
+
17
+ load Gem.bin_path('guard', 'guard')
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', Pathname.new(__FILE__).realpath)
13
+
14
+ require 'rubygems'
15
+ require 'bundler/setup'
16
+
17
+ load Gem.bin_path('rspec-core', 'rspec')
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'course/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'course'
8
+ spec.version = Course::VERSION
9
+ spec.authors = ['Arturo Guzman', 'Gleb Sinyavsky']
10
+ spec.email = ['arturo@guzart.com', 'zhulik.gleb@gmail.com']
11
+
12
+ spec.summary = 'Operations API for Railway oriented programming in Ruby'
13
+ spec.description = <<~DOC
14
+ Course provides an API for building your application operations/transactions using
15
+ the Railway oriented programming paradigm
16
+ DOC
17
+ spec.homepage = 'https://github.com/zhulik/course'
18
+ spec.license = 'MIT'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
21
+ f.match(%r{^(test|spec|features)/})
22
+ end
23
+ spec.bindir = 'exe'
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ['lib']
26
+
27
+ spec.add_development_dependency 'awesome_print', '~> 1.7'
28
+ spec.add_development_dependency 'byebug', '~> 9.0'
29
+ spec.add_development_dependency 'codecov', '~> 0'
30
+ spec.add_development_dependency 'bundler', '~> 1.14'
31
+ spec.add_development_dependency 'guard', '~> 2.14'
32
+ spec.add_development_dependency 'guard-rspec', '~> 4.7'
33
+ spec.add_development_dependency 'rake', '~> 10.0'
34
+ spec.add_development_dependency 'rspec', '~> 3.5'
35
+ spec.add_development_dependency 'minitest', '~> 5.0'
36
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Course
4
+ require 'course/version'
5
+ require 'course/step'
6
+ require 'course/result'
7
+ require 'course/failure_error'
8
+ require 'course/failure'
9
+ require 'course/operation'
10
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Course
4
+ class Failure
5
+ attr_reader :data, :type
6
+
7
+ def initialize(type, data = nil)
8
+ @type = type
9
+ @data = data
10
+ end
11
+
12
+ def ==(other)
13
+ type == other.type && data == other.data
14
+ end
15
+
16
+ def hash
17
+ [type, (data || '').to_sym].hash
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Course
4
+ class FailureError < StandardError
5
+ attr_reader :failure
6
+
7
+ def initialize(failure)
8
+ super('Step failed')
9
+ @failure = failure
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Course
4
+ class Operation
5
+ class << self
6
+ def call(*input, &block)
7
+ new.call(*input, &block)
8
+ end
9
+
10
+ def step(name = nil, **options, &block)
11
+ steps << Course::Step.new(name, block, **options)
12
+ end
13
+
14
+ def steps
15
+ @steps ||= []
16
+ end
17
+
18
+ def inherited(child)
19
+ child.instance_variable_set('@steps', @steps.clone)
20
+ end
21
+ end
22
+
23
+ def call(*input, &block)
24
+ execute_steps(input, &block)
25
+ return yield result if block_given?
26
+ result
27
+ end
28
+
29
+ def result
30
+ Course::Result.new(output: @output, failure: @failure)
31
+ end
32
+
33
+ private
34
+
35
+ def fail(type, data = nil)
36
+ raise FailureError, Failure.new(type, data)
37
+ end
38
+
39
+ def execute_steps(*input)
40
+ @output = self.class.steps.inject(*input, &method(:execute))
41
+ rescue FailureError => e
42
+ @failure = e.failure
43
+ end
44
+
45
+ def execute(next_input, step)
46
+ if step.method?
47
+ execute_step(method(step.target), next_input)
48
+ elsif step.block?
49
+ execute_step(step.block, next_input)
50
+ else
51
+ execute_operation(step.target, *next_input)
52
+ end
53
+ end
54
+
55
+ def execute_step(next_step, input)
56
+ if input.is_a?(Array) && (next_step.arity == input.count || next_step.arity == -1)
57
+ next_step.call(*input)
58
+ else
59
+ next_step.call(input)
60
+ end
61
+ end
62
+
63
+ def execute_operation(klass, *input)
64
+ klass.call(*input) do |res|
65
+ res.on_success do |output|
66
+ return output
67
+ end
68
+ res.on_fail do |err|
69
+ raise FailureError, err
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Course
4
+ class Result
5
+ attr_reader :failure, :output
6
+
7
+ def initialize(output: nil, failure: nil)
8
+ @output = output
9
+ @failure = failure
10
+ end
11
+
12
+ def failure?
13
+ !success?
14
+ end
15
+
16
+ def on_fail
17
+ yield failure if block_given? && failure?
18
+ end
19
+
20
+ def success?
21
+ failure.nil?
22
+ end
23
+
24
+ def on_success
25
+ yield output if block_given? && success?
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Course
4
+ class Step
5
+ attr_reader :target, :options, :block
6
+
7
+ def initialize(target, block, **options)
8
+ raise ArgumentError, 'Cannot create a step with both name and block' if !target.nil? && !block.nil?
9
+ raise ArgumentError, 'Step name or block should be passed' if target.nil? && block.nil?
10
+
11
+ @target = target
12
+ @options = options
13
+ @block = block
14
+ end
15
+
16
+ def method?
17
+ target.is_a?(String) || target.is_a?(Symbol)
18
+ end
19
+
20
+ def block?
21
+ !@block.nil?
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Course
4
+ VERSION = '0.2.0'
5
+ end
metadata ADDED
@@ -0,0 +1,197 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: course
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Arturo Guzman
8
+ - Gleb Sinyavsky
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2018-01-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: awesome_print
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.7'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.7'
28
+ - !ruby/object:Gem::Dependency
29
+ name: byebug
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '9.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '9.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: codecov
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: bundler
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '1.14'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '1.14'
70
+ - !ruby/object:Gem::Dependency
71
+ name: guard
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '2.14'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '2.14'
84
+ - !ruby/object:Gem::Dependency
85
+ name: guard-rspec
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '4.7'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '4.7'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rake
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '10.0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '10.0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: rspec
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '3.5'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '3.5'
126
+ - !ruby/object:Gem::Dependency
127
+ name: minitest
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '5.0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '5.0'
140
+ description: |
141
+ Course provides an API for building your application operations/transactions using
142
+ the Railway oriented programming paradigm
143
+ email:
144
+ - arturo@guzart.com
145
+ - zhulik.gleb@gmail.com
146
+ executables: []
147
+ extensions: []
148
+ extra_rdoc_files: []
149
+ files:
150
+ - ".gitignore"
151
+ - ".overcommit.yml"
152
+ - ".rspec"
153
+ - ".rubocop.yml"
154
+ - ".ruby-gemset"
155
+ - ".ruby-version"
156
+ - Gemfile
157
+ - LICENSE
158
+ - Makefile
159
+ - README.md
160
+ - Rakefile
161
+ - bin/console
162
+ - bin/guard
163
+ - bin/rspec
164
+ - bin/setup
165
+ - course.gemspec
166
+ - lib/course.rb
167
+ - lib/course/failure.rb
168
+ - lib/course/failure_error.rb
169
+ - lib/course/operation.rb
170
+ - lib/course/result.rb
171
+ - lib/course/step.rb
172
+ - lib/course/version.rb
173
+ homepage: https://github.com/zhulik/course
174
+ licenses:
175
+ - MIT
176
+ metadata: {}
177
+ post_install_message:
178
+ rdoc_options: []
179
+ require_paths:
180
+ - lib
181
+ required_ruby_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ required_rubygems_version: !ruby/object:Gem::Requirement
187
+ requirements:
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ requirements: []
192
+ rubyforge_project:
193
+ rubygems_version: 2.6.14
194
+ signing_key:
195
+ specification_version: 4
196
+ summary: Operations API for Railway oriented programming in Ruby
197
+ test_files: []