course 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []