methodist 0.1.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +56 -0
- data/Rakefile +23 -0
- data/lib/generators/builder/USAGE +9 -0
- data/lib/generators/builder/builder_generator.rb +27 -0
- data/lib/generators/builder/templates/builder.erb +10 -0
- data/lib/generators/builder/templates/builder_spec.erb +5 -0
- data/lib/generators/interactor/USAGE +9 -0
- data/lib/generators/interactor/interactor_generator.rb +28 -0
- data/lib/generators/interactor/templates/interactor.erb +22 -0
- data/lib/generators/interactor/templates/interactor_spec.erb +31 -0
- data/lib/generators/methodist_generator.rb +18 -0
- data/lib/generators/observer/USAGE +9 -0
- data/lib/generators/observer/observer_generator.rb +22 -0
- data/lib/generators/observer/templates/observer.erb +13 -0
- data/lib/generators/service/USAGE +9 -0
- data/lib/generators/service/service_generator.rb +27 -0
- data/lib/generators/service/templates/service.erb +10 -0
- data/lib/generators/service/templates/service_spec.erb +5 -0
- data/lib/methodist.rb +10 -0
- data/lib/methodist/builder.rb +100 -0
- data/lib/methodist/interactor.rb +112 -0
- data/lib/methodist/observer.rb +131 -0
- data/lib/methodist/pattern.rb +5 -0
- data/lib/methodist/railtie.rb +4 -0
- data/lib/methodist/service.rb +27 -0
- data/lib/methodist/version.rb +3 -0
- data/lib/tasks/methodist_tasks.rake +4 -0
- metadata +173 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 229590fd037e63ac5773c8a8e11c9ef451d3d01a5cc417c6e97c6c62fe5f30dc
|
4
|
+
data.tar.gz: acaaf7d57b19ab0b440fb132a43d85bbed2fd838027d2befef5e9d77329dfca2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 71f861ed48e4a2a390c78c570207ea9ef7fdc00989c383a7dcaec373c951a8a8cef9e165ae8d37bc6b78af50d7bee1edd88e47d9b0f865d39e32b2019d3a9f73
|
7
|
+
data.tar.gz: ed50190b816143494934cb50018f04e3f88a2e5e8f147bc34956bc7c1c3211a6643dab95f4d85b3d93d0f14a5030d93a7013deb05d9a18d54e6423960e5c1688
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 Sergey Nesterov
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Methodist
|
2
|
+
[](https://travis-ci.org/QNester/methodist)
|
3
|
+
[](https://codeclimate.com/github/QNester/methodist/maintainability)
|
4
|
+
[](https://gitter.im/gem-methodist/methodist?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
5
|
+
|
6
|
+
Methodist - a gem for Ruby on Rails created to stop chaos in your buisness logic.
|
7
|
+
This gem adds generators to your rails application using some patterns:
|
8
|
+
|
9
|
+
- __Interactor__: a class for doing some complex job.
|
10
|
+
- __Observer__: notifies one part of an application about changes in another part of an application.
|
11
|
+
- __Builder__: is used to create an object with complex configuration (including your business logic, validation etc.)
|
12
|
+
- __Service__: a class with collection of methods. Useful when using internal services.
|
13
|
+
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'methodist'
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
```bash
|
24
|
+
$ bundle
|
25
|
+
```
|
26
|
+
|
27
|
+
Or install it yourself:
|
28
|
+
```bash
|
29
|
+
$ gem install methodist
|
30
|
+
```
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
Just execute in a terminal
|
34
|
+
```
|
35
|
+
rails g <pattern_name> <generated_class>
|
36
|
+
```
|
37
|
+
where _<pattern_name>_ is one of the patterns (observer, interactor, etc.)
|
38
|
+
|
39
|
+
About every Methodist pattern you can read [here](https://github.com/QNester/methodist/wiki)
|
40
|
+
|
41
|
+
## Contributing
|
42
|
+
To contribute just:
|
43
|
+
1) Create issue. Issue should contain information about goals of your
|
44
|
+
future changes.
|
45
|
+
2) Fork project.
|
46
|
+
3) Create branch. The name of the branch should begin with the ID of your issue.
|
47
|
+
Examle: `ISSUE-205-create-new-pattern-generator`.
|
48
|
+
4) Make changes.
|
49
|
+
5) *Write tests*.
|
50
|
+
6) Make a commit. The name of the commit should begin with the ID of your issue.
|
51
|
+
Examle: `[ISSUE-205] Create new pattern generator`.
|
52
|
+
7) Push.
|
53
|
+
8) Create a pull request to the `develop` branch.
|
54
|
+
|
55
|
+
## License
|
56
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Methodist'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'bundler/gem_tasks'
|
18
|
+
|
19
|
+
require "rspec/core/rake_task"
|
20
|
+
|
21
|
+
RSpec::Core::RakeTask.new(:spec)
|
22
|
+
|
23
|
+
task :default => :spec
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../methodist_generator'
|
2
|
+
|
3
|
+
class BuilderGenerator < MethodistGenerator
|
4
|
+
desc 'Create a builder'
|
5
|
+
source_root File.expand_path('templates', __dir__)
|
6
|
+
|
7
|
+
PATTERN_FOLDER = 'builders'.freeze
|
8
|
+
TEMPLATE_FILE = 'builder.erb'.freeze
|
9
|
+
TEMPLATE_SPEC_FILE = 'builder_spec.erb'.freeze
|
10
|
+
|
11
|
+
class_option 'path', type: :string, desc: "Parent module for a new builder", default: PATTERN_FOLDER
|
12
|
+
|
13
|
+
def generate
|
14
|
+
template(
|
15
|
+
TEMPLATE_FILE,
|
16
|
+
"#{filename_with_path}_builder.rb"
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_spec
|
21
|
+
template(
|
22
|
+
TEMPLATE_SPEC_FILE,
|
23
|
+
"#{filename_with_path(prefix: 'spec')}_builder_spec.rb"
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%- unless options['clean'] -%>
|
2
|
+
# See docs to understand how to use a builder:
|
3
|
+
# https://github.com/QNester/methodist/tree/master/docs/builder.md
|
4
|
+
<% end -%>
|
5
|
+
class <%= name.camelcase %>Builder < Methodist::Builder
|
6
|
+
<%- unless options['clean'] -%>
|
7
|
+
# Use `field` method to define a builder attribute.
|
8
|
+
# Example: field :title, ->(val) { val.is_a?(String) }
|
9
|
+
<%- end -%>
|
10
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../methodist_generator'
|
2
|
+
|
3
|
+
class InteractorGenerator < MethodistGenerator
|
4
|
+
desc 'Create an interactor'
|
5
|
+
source_root File.expand_path('templates', __dir__)
|
6
|
+
|
7
|
+
PATTERN_FOLDER = 'interactors'.freeze
|
8
|
+
TEMPLATE_FILE = 'interactor.erb'.freeze
|
9
|
+
TEMPLATE_SPEC_FILE = 'interactor_spec.erb'.freeze
|
10
|
+
|
11
|
+
class_option 'skip-validations', type: :boolean, desc: "Skip validations in generated files", default: false
|
12
|
+
class_option 'path', type: :string, desc: "Parent module for a new interactor", default: PATTERN_FOLDER
|
13
|
+
|
14
|
+
def generate
|
15
|
+
template(
|
16
|
+
TEMPLATE_FILE,
|
17
|
+
"#{filename_with_path}.rb"
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate_spec
|
22
|
+
template(
|
23
|
+
TEMPLATE_SPEC_FILE,
|
24
|
+
"#{filename_with_path(prefix: 'spec')}_spec.rb"
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<% unless options['clean'] -%>
|
2
|
+
# Methodist interactors use dry-transaction.
|
3
|
+
# Check docs here: https://github.com/dry-rb/dry-transaction
|
4
|
+
<% end -%>
|
5
|
+
class <%= name.camelcase %> < Methodist::Interactor
|
6
|
+
<% unless options['skip-validations'] %>
|
7
|
+
# See here the syntax for validation http://dry-rb.org/gems/dry-validation/
|
8
|
+
schema do
|
9
|
+
# required(:name).value(:str?)
|
10
|
+
end
|
11
|
+
<% end %>
|
12
|
+
<% unless options['clean'] -%>
|
13
|
+
# Use your step, tee, try or map
|
14
|
+
# More info: http://dry-rb.org/gems/dry-transaction/step-adapters/
|
15
|
+
<% end -%>
|
16
|
+
<%- unless options['skip-validations'] %>
|
17
|
+
step :validate
|
18
|
+
<%- end -%>
|
19
|
+
<%- unless options['clean'] -%>
|
20
|
+
# step :do_something
|
21
|
+
<%- end %>
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
RSpec.describe <%= name.camelcase %> do
|
4
|
+
subject { described_class.new.call(input) }
|
5
|
+
|
6
|
+
<%- unless options['skip-validations'] %>
|
7
|
+
context 'valid input' do
|
8
|
+
let!(:input) do
|
9
|
+
<%- unless options['clean'] %>
|
10
|
+
# valid input for your interactor
|
11
|
+
<%- end %>
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns success result' do
|
15
|
+
expect(subject).to be_success
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'invalid input' do
|
20
|
+
let!(:input) do
|
21
|
+
<%- unless options['clean'] %>
|
22
|
+
# failure input for yout interactor
|
23
|
+
<%- end %>
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns success result' do
|
27
|
+
expect(subject).to be_failure
|
28
|
+
end
|
29
|
+
end
|
30
|
+
<%- end -%>
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class MethodistGenerator < Rails::Generators::NamedBase
|
2
|
+
DEFAULT_PREFIX = 'app'.freeze
|
3
|
+
|
4
|
+
class_option 'clean', type: :boolean, desc: "Skip comments and annotations in generated files", default: false
|
5
|
+
class_option 'in-lib', type: :boolean, desc: "Create files in lib path, not in app", default: false
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def filename_with_path(prefix: DEFAULT_PREFIX)
|
10
|
+
prefix = 'lib' if options['in-lib'] && prefix != 'spec'
|
11
|
+
prefix = 'spec/lib' if options['in-lib'] && prefix == 'spec'
|
12
|
+
|
13
|
+
name_as_arr = name.split('/')
|
14
|
+
path = name_as_arr.first(name_as_arr.size - 1).join('/')
|
15
|
+
name = name_as_arr.last
|
16
|
+
"#{prefix}/#{options['path']}/#{path}/#{name}"
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative '../methodist_generator'
|
2
|
+
|
3
|
+
class ObserverGenerator < MethodistGenerator
|
4
|
+
desc 'Create an observer'
|
5
|
+
source_root File.expand_path('templates', __dir__)
|
6
|
+
|
7
|
+
PATTERN_FOLDER = 'observers'.freeze
|
8
|
+
TEMPLATE_FILE = 'observer.erb'.freeze
|
9
|
+
TEMPLATE_SPEC_FILE = 'observer_spec.erb'.freeze
|
10
|
+
DEFAULT_PREFIX = 'config/initializers'.freeze
|
11
|
+
|
12
|
+
class_option 'skip-validations', type: :boolean, desc: "Skip validations in generated files", default: false
|
13
|
+
class_option 'path', type: :string, desc: "Parent module for new a interactor", default: PATTERN_FOLDER
|
14
|
+
|
15
|
+
def generate
|
16
|
+
template(
|
17
|
+
TEMPLATE_FILE,
|
18
|
+
"#{filename_with_path(prefix: DEFAULT_PREFIX)}_observer.rb"
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class <%= name.camelcase %>Observer < Methodist::Observer
|
2
|
+
# observe Klass, :instance_method
|
3
|
+
|
4
|
+
execute do |klass, instance_method|
|
5
|
+
# Write here actions you want to trigger
|
6
|
+
# Example:
|
7
|
+
# CounterService.new.increment("#{klass.downcase.demodulize}_instance_method")
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
# Define here any methods you want to use in the execute block.
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../methodist_generator'
|
2
|
+
|
3
|
+
class ServiceGenerator < MethodistGenerator
|
4
|
+
desc 'Create service'
|
5
|
+
source_root File.expand_path('templates', __dir__)
|
6
|
+
|
7
|
+
PATTERN_FOLDER = 'services'.freeze
|
8
|
+
TEMPLATE_FILE = 'service.erb'.freeze
|
9
|
+
TEMPLATE_SPEC_FILE = 'service_spec.erb'.freeze
|
10
|
+
|
11
|
+
class_option 'path', type: :string, desc: "Parent module for a new service", default: PATTERN_FOLDER
|
12
|
+
|
13
|
+
def generate
|
14
|
+
template(
|
15
|
+
TEMPLATE_FILE,
|
16
|
+
"#{filename_with_path}_service.rb"
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_spec
|
21
|
+
template(
|
22
|
+
TEMPLATE_SPEC_FILE,
|
23
|
+
"#{filename_with_path(prefix: 'spec')}_service_spec.rb"
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%- unless options['clean'] -%>
|
2
|
+
# See docs to learn how to use service:
|
3
|
+
# https://github.com/QNester/methodist/tree/master/docs/service.md
|
4
|
+
<% end -%>
|
5
|
+
class <%= name.camelcase %>Service < Methodist::Service
|
6
|
+
<%- unless options['clean'] -%>
|
7
|
+
# Use the `client` method to define your client.
|
8
|
+
# Example: client Redis.new(Settings.redis.to_hash)
|
9
|
+
<%- end -%>
|
10
|
+
end
|
data/lib/methodist.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require_relative 'pattern'
|
2
|
+
|
3
|
+
class Methodist::Builder < Methodist::Pattern
|
4
|
+
class << self
|
5
|
+
def attr_accessor(*args)
|
6
|
+
@attributes ||= []
|
7
|
+
@attributes += args
|
8
|
+
@attributes.uniq!
|
9
|
+
super(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Define attribute and set validation for value
|
14
|
+
#
|
15
|
+
# ==== Parameters
|
16
|
+
# * +attr_name+ [String] - name of defined attribute
|
17
|
+
# * +proc+ [Proc] - proc for validate contains value. If proc call result
|
18
|
+
# will returns true then #valid_attr? will returns true.
|
19
|
+
#
|
20
|
+
##
|
21
|
+
def field(attr_name, proc = nil)
|
22
|
+
attr_accessor(attr_name)
|
23
|
+
set_proc_to_const(proc, attr_name) if proc
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Define multiple attributes and set validation for values
|
28
|
+
#
|
29
|
+
# ==== Parameters
|
30
|
+
# * +attr_names+ [Args] - name of defined attribute
|
31
|
+
# * +proc+ [Proc] - proc for validate contains values.
|
32
|
+
#
|
33
|
+
##
|
34
|
+
def fields(*attr_names, &block)
|
35
|
+
attr_names.each { |attr_name| field(attr_name, &block) }
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Array of defined attributes
|
40
|
+
##
|
41
|
+
def attributes
|
42
|
+
@attributes || []
|
43
|
+
end
|
44
|
+
|
45
|
+
alias_method :attrs, :attributes
|
46
|
+
|
47
|
+
def set_proc_to_const(proc, attr_name)
|
48
|
+
const_set(proc_const_name(attr_name), proc)
|
49
|
+
end
|
50
|
+
|
51
|
+
def proc_const_name(attr_name)
|
52
|
+
"VALIDATION_PROC_#{attr_name.upcase}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Convert attributes and values to hash.
|
58
|
+
#
|
59
|
+
# ==== Example
|
60
|
+
# In builder with attributes 'title', 'author'
|
61
|
+
# when you will use this method you will have:
|
62
|
+
# { title: 'Title', author: 'Author' }
|
63
|
+
##
|
64
|
+
def to_h
|
65
|
+
hash = {}
|
66
|
+
self.class.attributes.each do |att|
|
67
|
+
hash[att] = self.send(att)
|
68
|
+
end
|
69
|
+
hash
|
70
|
+
end
|
71
|
+
|
72
|
+
alias_method :to_hash, :to_h
|
73
|
+
|
74
|
+
##
|
75
|
+
# Validate all attributes.
|
76
|
+
##
|
77
|
+
def valid?
|
78
|
+
self.class.attributes.all? { |att| valid_attr?(att) }
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Validate attribute value.
|
83
|
+
# Proc passed in #field method use for validation.
|
84
|
+
# If proc was not defined just return true.
|
85
|
+
#
|
86
|
+
# ==== Parameters
|
87
|
+
# * +attr_name+ [String/Symbol] - name of attribute
|
88
|
+
##
|
89
|
+
def valid_attr?(attr_name)
|
90
|
+
proc = get_proc(attr_name)
|
91
|
+
return true if proc.nil?
|
92
|
+
proc.call(self.send(attr_name))
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def get_proc(attr_name)
|
98
|
+
self.class.const_get(self.class.proc_const_name(attr_name)) # rescue nil
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require "dry/transaction"
|
2
|
+
require "dry/validation"
|
3
|
+
|
4
|
+
##
|
5
|
+
# == Methodist::Interactor
|
6
|
+
# Base class for a Methodist interactor.
|
7
|
+
#
|
8
|
+
# Methodist::Interactor dependency from dry-rb transactions and dry-rb validations
|
9
|
+
#
|
10
|
+
#
|
11
|
+
class Methodist::Interactor < Methodist::Pattern
|
12
|
+
include Dry::Transaction
|
13
|
+
|
14
|
+
attr_accessor :validation_result
|
15
|
+
|
16
|
+
SCHEMA_CONST = 'SCHEMA'
|
17
|
+
|
18
|
+
class << self
|
19
|
+
##
|
20
|
+
# Method set Dry::Validation schema for an interactor.
|
21
|
+
#
|
22
|
+
# Receive block for generating Dry::Validation schema
|
23
|
+
# result.
|
24
|
+
#
|
25
|
+
# class InteractorClass < Methodist::Interactor
|
26
|
+
# schema do
|
27
|
+
# required(:name).value(:str?)
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# step :validate
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# InteractorClass.new.call(name: nil) #=> Failure(ValidationError)
|
34
|
+
#
|
35
|
+
#
|
36
|
+
# ==== See
|
37
|
+
# http://dry-rb.org/gems/dry-validation/
|
38
|
+
#
|
39
|
+
# https://github.com/dry-rb/dry-transaction
|
40
|
+
##
|
41
|
+
def schema(&block)
|
42
|
+
if block_given?
|
43
|
+
const_set SCHEMA_CONST, Dry::Validation.Schema(&block)
|
44
|
+
else
|
45
|
+
raise SchemaDefinitionError, 'You must pass block to `schema`'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Method for validation input of interactor parameters.
|
52
|
+
#
|
53
|
+
# ==== Parameters
|
54
|
+
# * +input+ [Hash] - parameters for interactor
|
55
|
+
#
|
56
|
+
# ==== Example
|
57
|
+
#
|
58
|
+
# # app/interactors/interactor_class.rb
|
59
|
+
# class InteractorClass < Methodist::Interactor
|
60
|
+
# schema do
|
61
|
+
# required(:name).value(:str?)
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# step :validate
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# # your controller action
|
68
|
+
# def create
|
69
|
+
# result = InteractorClass.new.call(name: nil) #=> Failure(ValidationError)
|
70
|
+
# raise InteractorError, result.value if result.failure?
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# ==== Return
|
74
|
+
# * +Dry::Monads::Result::Success+ - success result of interactor step
|
75
|
+
# * +Dry::Monads::Result::Failure+ - failure result of interactor step
|
76
|
+
#
|
77
|
+
# ==== Raise
|
78
|
+
# * +SchemaDefinitionError+ - raise if method was called without a schema definition
|
79
|
+
#
|
80
|
+
#
|
81
|
+
# ==== Attention
|
82
|
+
# You can redefine failure_validation_value for a custom
|
83
|
+
# value returning in case of validation Failure
|
84
|
+
##
|
85
|
+
def validate(input)
|
86
|
+
input = {} unless input
|
87
|
+
raise InputClassError, 'If you want to use custom #validate, you have to pass a hash to an interactor' unless input.is_a?(Hash)
|
88
|
+
schema = self.class.const_get SCHEMA_CONST rescue nil
|
89
|
+
raise SchemaDefinitionError, 'You have to define a schema with #schema method' unless schema
|
90
|
+
@validation_result = schema.call(input)
|
91
|
+
return Success(validation_result.to_h) if validation_result.success?
|
92
|
+
Failure(failure_validation_value)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
##
|
98
|
+
# Method for validation input of interactor parameters.
|
99
|
+
##
|
100
|
+
def failure_validation_value
|
101
|
+
field = validation_result.errors.keys.first
|
102
|
+
{
|
103
|
+
error: 'ValidationError',
|
104
|
+
field: field,
|
105
|
+
reason: "#{field}: #{validation_result.errors[field].first}"
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
class SchemaDefinitionError < StandardError; end
|
111
|
+
class InputClassError < StandardError; end
|
112
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
##
|
2
|
+
# == Methodist::Observer
|
3
|
+
# Base class for Methodist observers
|
4
|
+
#
|
5
|
+
#
|
6
|
+
class Methodist::Observer < Methodist::Pattern
|
7
|
+
CONST_EXECUTION_BLOCK = 'EXEC_BLOCK'.freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
##
|
11
|
+
# Subscribe to the instance method of the klass to observe
|
12
|
+
#
|
13
|
+
# ==== Parameters
|
14
|
+
# * +klass+ [Class] - The owner of the method for observation
|
15
|
+
# * +method_name+ [String/Symbol] - Observation method name
|
16
|
+
#
|
17
|
+
# ===== Options
|
18
|
+
# * +skip_if+ [Proc] - Skip triggered execution if condition is true
|
19
|
+
#
|
20
|
+
# ==== Yield
|
21
|
+
# main_block - execution block. If this block was passed,
|
22
|
+
# `#execute` block will be ignored
|
23
|
+
#
|
24
|
+
##
|
25
|
+
def observe(klass, method_name, skip_if: nil, &main_block)
|
26
|
+
method_name = method_name.to_sym
|
27
|
+
original_method = klass.instance_method(method_name)
|
28
|
+
method_observe = observer_method(method_name)
|
29
|
+
method_dump = method_dump(method_name)
|
30
|
+
me = self
|
31
|
+
|
32
|
+
return false if method_defined?(klass, method_dump)
|
33
|
+
|
34
|
+
klass.send(:alias_method, method_dump, method_name) # dump method
|
35
|
+
|
36
|
+
observer_method_proc = -> (*args, &block) do
|
37
|
+
result = original_method.bind(self).call(*args, &block)
|
38
|
+
unless skip_if.nil?
|
39
|
+
return if skip_if.call(result)
|
40
|
+
end
|
41
|
+
if block_given?
|
42
|
+
main_block.call(klass, method_name)
|
43
|
+
else
|
44
|
+
me.trigger!(klass, method_name)
|
45
|
+
end
|
46
|
+
result # return the result of the original method
|
47
|
+
end
|
48
|
+
|
49
|
+
klass.send(:define_method, method_observe, observer_method_proc)
|
50
|
+
|
51
|
+
klass.send(:alias_method, method_name, method_observe) # redefine the original method
|
52
|
+
add_observed(klass, method_name)
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Stop observation instance method of klass for observe
|
58
|
+
#
|
59
|
+
# ==== Parameters
|
60
|
+
# * +klass+ [Class] - Klass owner of the observed method
|
61
|
+
# * +method_name+ [String/Symbol] - Name of the observed method
|
62
|
+
##
|
63
|
+
def stop_observe(klass, method_name)
|
64
|
+
method_observe = observer_method(method_name)
|
65
|
+
method_dump = method_dump(method_name)
|
66
|
+
return false unless method_defined?(klass, method_observe) && method_defined?(klass, method_dump)
|
67
|
+
|
68
|
+
klass.send(:alias_method, method_name, method_dump) # restore dumped method
|
69
|
+
klass.send(:remove_method, method_observe) # remove observed method
|
70
|
+
klass.send(:remove_method, method_dump) # remove dump method
|
71
|
+
remove_from_observed(klass, method_name)
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# The executable block is passed to #execute
|
77
|
+
# Parameters *klass* and *method_name* are passed to block call
|
78
|
+
#
|
79
|
+
# ==== Parameters
|
80
|
+
# * +klass+ [Class] - Klass owner of the observed method
|
81
|
+
# * +method+ [Class] - Name of the observed method
|
82
|
+
#
|
83
|
+
# ===== Raise
|
84
|
+
# +ExecuteBlockWasNotDefined+ - when no block was passed to the execute method in the observer class
|
85
|
+
##
|
86
|
+
def trigger!(klass, method_name)
|
87
|
+
block = const_get(CONST_EXECUTION_BLOCK) rescue nil
|
88
|
+
raise ExecuteBlockWasNotDefined, "You must define execute block in your #{self.name}" unless block
|
89
|
+
block.call(klass, method_name)
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Method for passing execution block for execution after observed method
|
94
|
+
##
|
95
|
+
def execute(&block)
|
96
|
+
const_set(CONST_EXECUTION_BLOCK, block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def observed_methods
|
100
|
+
@observed_method ||= []
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def observer_method(method)
|
106
|
+
"#{method}_observer"
|
107
|
+
end
|
108
|
+
|
109
|
+
def method_dump(method)
|
110
|
+
"#{method}_dump"
|
111
|
+
end
|
112
|
+
|
113
|
+
def klass_with_method(klass, method)
|
114
|
+
"#{klass}##{method}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def method_defined?(klass, method)
|
118
|
+
klass.instance_methods(false).include?(method.to_sym)
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_observed(klass, method_name)
|
122
|
+
observed_methods << klass_with_method(klass, method_name)
|
123
|
+
end
|
124
|
+
|
125
|
+
def remove_from_observed(klass, method_name)
|
126
|
+
observed_methods.delete(klass_with_method(klass, method_name))
|
127
|
+
end
|
128
|
+
|
129
|
+
class ExecuteBlockWasNotDefined < StandardError; end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'pattern'
|
2
|
+
|
3
|
+
class Methodist::Service < Methodist::Pattern
|
4
|
+
CONST_CLIENT = 'CLIENT'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
##
|
8
|
+
# Define client for service
|
9
|
+
#
|
10
|
+
# ==== Parameters
|
11
|
+
# * +client_instance+ [Instance of client klass] - Instance of client class.
|
12
|
+
# It could be different clients like redis, elastic, internal api etc.
|
13
|
+
##
|
14
|
+
def client(client_instance)
|
15
|
+
const_set(CONST_CLIENT, client_instance)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Instance method use to get defined client
|
21
|
+
##
|
22
|
+
def client
|
23
|
+
@client ||= self.class.const_get(CONST_CLIENT) #rescue nil
|
24
|
+
end
|
25
|
+
|
26
|
+
class ResponseError < StandardError; end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: methodist
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sergey Nesterov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-validation
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.11'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.11'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: dry-transaction
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.11'
|
48
|
+
type: :runtime
|
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: sqlite3
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.7'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.7'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: generator_spec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.9'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.9'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: ffaker
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.9'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.9'
|
111
|
+
description: "Methodist - a gem for Ruby on Rails created to stop chaos in your business
|
112
|
+
logic. This gem adds generators to your rails application using some patterns: interactor,
|
113
|
+
builder, observer and service. Just use `rails g <pattern> <new_class_name>`. \nDocs:
|
114
|
+
https://github.com/QNester/methodist/wiki"
|
115
|
+
email:
|
116
|
+
- qnesterr@gmail.com
|
117
|
+
executables: []
|
118
|
+
extensions: []
|
119
|
+
extra_rdoc_files: []
|
120
|
+
files:
|
121
|
+
- MIT-LICENSE
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- lib/generators/builder/USAGE
|
125
|
+
- lib/generators/builder/builder_generator.rb
|
126
|
+
- lib/generators/builder/templates/builder.erb
|
127
|
+
- lib/generators/builder/templates/builder_spec.erb
|
128
|
+
- lib/generators/interactor/USAGE
|
129
|
+
- lib/generators/interactor/interactor_generator.rb
|
130
|
+
- lib/generators/interactor/templates/interactor.erb
|
131
|
+
- lib/generators/interactor/templates/interactor_spec.erb
|
132
|
+
- lib/generators/methodist_generator.rb
|
133
|
+
- lib/generators/observer/USAGE
|
134
|
+
- lib/generators/observer/observer_generator.rb
|
135
|
+
- lib/generators/observer/templates/observer.erb
|
136
|
+
- lib/generators/service/USAGE
|
137
|
+
- lib/generators/service/service_generator.rb
|
138
|
+
- lib/generators/service/templates/service.erb
|
139
|
+
- lib/generators/service/templates/service_spec.erb
|
140
|
+
- lib/methodist.rb
|
141
|
+
- lib/methodist/builder.rb
|
142
|
+
- lib/methodist/interactor.rb
|
143
|
+
- lib/methodist/observer.rb
|
144
|
+
- lib/methodist/pattern.rb
|
145
|
+
- lib/methodist/railtie.rb
|
146
|
+
- lib/methodist/service.rb
|
147
|
+
- lib/methodist/version.rb
|
148
|
+
- lib/tasks/methodist_tasks.rake
|
149
|
+
homepage: https://github.com/QNester/methodist
|
150
|
+
licenses:
|
151
|
+
- MIT
|
152
|
+
metadata: {}
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
requirements: []
|
168
|
+
rubyforge_project:
|
169
|
+
rubygems_version: 2.7.6
|
170
|
+
signing_key:
|
171
|
+
specification_version: 4
|
172
|
+
summary: Gem for Ruby on Rails created to stop chaos in your buisness logic.
|
173
|
+
test_files: []
|