trailblazer 2.0.7 → 2.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 +5 -5
- data/.gitignore +1 -0
- data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +101 -0
- data/.rubocop.yml +20 -0
- data/.rubocop_todo.yml +556 -0
- data/.travis.yml +6 -10
- data/CHANGES.md +83 -1
- data/COMM-LICENSE +46 -75
- data/CONTRIBUTING.md +179 -0
- data/Gemfile +0 -27
- data/{LICENSE.txt → LICENSE} +4 -4
- data/README.md +39 -138
- data/Rakefile +2 -19
- data/lib/trailblazer.rb +3 -17
- data/lib/trailblazer/version.rb +3 -1
- data/test/test_helper.rb +12 -3
- data/trailblazer.gemspec +10 -14
- metadata +22 -147
- data/doc/Trb-The-Stack.png +0 -0
- data/doc/operation-2017.png +0 -0
- data/doc/trb.jpg +0 -0
- data/lib/trailblazer/dsl.rb +0 -47
- data/lib/trailblazer/operation/auto_inject.rb +0 -47
- data/lib/trailblazer/operation/callback.rb +0 -35
- data/lib/trailblazer/operation/contract.rb +0 -46
- data/lib/trailblazer/operation/guard.rb +0 -18
- data/lib/trailblazer/operation/model.rb +0 -60
- data/lib/trailblazer/operation/module.rb +0 -29
- data/lib/trailblazer/operation/nested.rb +0 -113
- data/lib/trailblazer/operation/persist.rb +0 -10
- data/lib/trailblazer/operation/policy.rb +0 -35
- data/lib/trailblazer/operation/procedural/contract.rb +0 -15
- data/lib/trailblazer/operation/procedural/validate.rb +0 -22
- data/lib/trailblazer/operation/pundit.rb +0 -38
- data/lib/trailblazer/operation/representer.rb +0 -31
- data/lib/trailblazer/operation/rescue.rb +0 -21
- data/lib/trailblazer/operation/test.rb +0 -17
- data/lib/trailblazer/operation/validate.rb +0 -68
- data/lib/trailblazer/operation/wrap.rb +0 -25
- data/test/docs/auto_inject_test.rb +0 -30
- data/test/docs/contract_test.rb +0 -525
- data/test/docs/dry_test.rb +0 -31
- data/test/docs/fast_test.rb +0 -164
- data/test/docs/guard_test.rb +0 -169
- data/test/docs/macro_test.rb +0 -36
- data/test/docs/model_test.rb +0 -75
- data/test/docs/nested_test.rb +0 -334
- data/test/docs/operation_test.rb +0 -408
- data/test/docs/policy_test.rb +0 -2
- data/test/docs/pundit_test.rb +0 -133
- data/test/docs/representer_test.rb +0 -268
- data/test/docs/rescue_test.rb +0 -154
- data/test/docs/wrap_test.rb +0 -183
- data/test/gemfiles/Gemfile.ruby-1.9 +0 -3
- data/test/gemfiles/Gemfile.ruby-2.0 +0 -12
- data/test/gemfiles/Gemfile.ruby-2.3 +0 -12
- data/test/module_test.rb +0 -100
- data/test/operation/callback_test.rb +0 -70
- data/test/operation/contract_test.rb +0 -420
- data/test/operation/dsl/callback_test.rb +0 -106
- data/test/operation/dsl/contract_test.rb +0 -294
- data/test/operation/dsl/representer_test.rb +0 -169
- data/test/operation/model_test.rb +0 -60
- data/test/operation/params_test.rb +0 -36
- data/test/operation/persist_test.rb +0 -44
- data/test/operation/pipedream_test.rb +0 -59
- data/test/operation/pipetree_test.rb +0 -104
- data/test/operation/present_test.rb +0 -24
- data/test/operation/pundit_test.rb +0 -104
- data/test/operation/representer_test.rb +0 -254
- data/test/operation/resolver_test.rb +0 -47
- data/test/operation_test.rb +0 -143
data/doc/Trb-The-Stack.png
DELETED
Binary file
|
data/doc/operation-2017.png
DELETED
Binary file
|
data/doc/trb.jpg
DELETED
Binary file
|
data/lib/trailblazer/dsl.rb
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
module Trailblazer
|
2
|
-
module DSL
|
3
|
-
# Boring DSL code that allows to set a skill class, or define it ad-hoc using a block.
|
4
|
-
# passing a constant always wipes out the existing class.
|
5
|
-
#
|
6
|
-
# Used in Contract, Representer, Callback, ..
|
7
|
-
class Build
|
8
|
-
# options[:prefix]
|
9
|
-
# options[:class]
|
10
|
-
# options[:container]
|
11
|
-
|
12
|
-
# Currently, adds .class only to classes. this could break builder instances?
|
13
|
-
|
14
|
-
def call(options, name=nil, constant=nil, dsl_block, &block)
|
15
|
-
# contract MyForm
|
16
|
-
if name.is_a?(Class)
|
17
|
-
constant = name
|
18
|
-
name = :default
|
19
|
-
end
|
20
|
-
|
21
|
-
is_instance = !(constant.kind_of?(Class) || dsl_block) # i don't like this magic too much, but since it's the only DSL method in TRB, it should be ok. # DISCUSS: options[:is_instance]
|
22
|
-
|
23
|
-
path = path_name(options[:prefix], name, is_instance ? nil : "class") # "contract.default.class"
|
24
|
-
|
25
|
-
if is_instance
|
26
|
-
skill = constant
|
27
|
-
else
|
28
|
-
extended = options[:container][path] # Operation["contract.default.class"]
|
29
|
-
extended = yield extended if extended && block_given?
|
30
|
-
|
31
|
-
# only extend an existing skill class when NO constant was passed.
|
32
|
-
constant = (extended || options[:class]) if constant.nil?# && block_given?
|
33
|
-
|
34
|
-
skill = Class.new(constant)
|
35
|
-
skill.class_eval(&dsl_block) if dsl_block
|
36
|
-
end
|
37
|
-
|
38
|
-
[path, skill]
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
def path_name(prefix, name, suffix)
|
43
|
-
[prefix, name, suffix].compact.join(".") # "contract.class" for default, otherwise "contract.params.class" etc.
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
require "dry/auto_inject"
|
2
|
-
|
3
|
-
class Trailblazer::Operation
|
4
|
-
# Thanks, @timriley! <3
|
5
|
-
# https://gist.github.com/timriley/d314a58da9784912159006e208ba8ea9
|
6
|
-
module AutoInject
|
7
|
-
class InjectStrategy < Module
|
8
|
-
ClassMethods = Class.new(Module)
|
9
|
-
|
10
|
-
attr_reader :container
|
11
|
-
attr_reader :dependency_map
|
12
|
-
attr_reader :class_mod
|
13
|
-
|
14
|
-
def initialize(container, *dependency_names)
|
15
|
-
@container = container
|
16
|
-
@dependency_map = Dry::AutoInject::DependencyMap.new(*dependency_names)
|
17
|
-
@class_mod = ClassMethods.new
|
18
|
-
end
|
19
|
-
|
20
|
-
def included(klass)
|
21
|
-
define_call
|
22
|
-
|
23
|
-
klass.singleton_class.prepend @class_mod
|
24
|
-
|
25
|
-
super
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def define_call
|
31
|
-
class_mod.class_exec(container, dependency_map) do |container, dependency_map|
|
32
|
-
define_method :call do |params={}, options={}, *dependencies|
|
33
|
-
options_with_deps = dependency_map.to_h.each_with_object({}) { |(name, identifier), obj|
|
34
|
-
obj[name] = options[name] || container[identifier]
|
35
|
-
}.merge(options)
|
36
|
-
|
37
|
-
super(params, options_with_deps, *dependencies)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.AutoInject(container)
|
45
|
-
Dry::AutoInject(container, strategies: {default: AutoInject::InjectStrategy})
|
46
|
-
end
|
47
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
require "disposable/callback"
|
2
|
-
|
3
|
-
class Trailblazer::Operation
|
4
|
-
def self.Callback(group)
|
5
|
-
step = ->(input, options) { Callback.(group, input, options) }
|
6
|
-
|
7
|
-
[ step, name: "callback.#{group}" ]
|
8
|
-
end
|
9
|
-
|
10
|
-
module Callback
|
11
|
-
def self.call(name=:default, operation, options)
|
12
|
-
config = options["callback.#{name}.class"] || raise #.fetch(name) # TODO: test exception
|
13
|
-
group = config[:group].new(options["contract.default"])
|
14
|
-
|
15
|
-
options[:context] ||= (config[:context] == :operation ? operation : group)
|
16
|
-
group.(options)
|
17
|
-
|
18
|
-
options["result.callback.#{name}"] = group
|
19
|
-
end
|
20
|
-
|
21
|
-
module DSL
|
22
|
-
def callback(name=:default, constant=nil, &block)
|
23
|
-
heritage.record(:callback, name, constant, &block)
|
24
|
-
|
25
|
-
# FIXME: make this nicer. we want to extend same-named callback groups.
|
26
|
-
# TODO: allow the same with contract, or better, test it!
|
27
|
-
extended = self["callback.#{name}.class"] && self["callback.#{name}.class"]
|
28
|
-
|
29
|
-
path, group_class = Trailblazer::DSL::Build.new.({ prefix: :callback, class: Disposable::Callback::Group, container: self }, name, constant, block) { |extended| extended[:group] }
|
30
|
-
|
31
|
-
self[path] = { group: group_class, context: constant ? nil : :operation }
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# Best practices for using contract.
|
2
|
-
#
|
3
|
-
# * inject contract instance via constructor to #contract
|
4
|
-
# * allow contract setup and memo via #contract(model, options)
|
5
|
-
# * allow implicit automatic setup via #contract and class.contract_class
|
6
|
-
#
|
7
|
-
# Needs Operation#model.
|
8
|
-
# Needs #[], #[]= skill dependency.
|
9
|
-
class Trailblazer::Operation
|
10
|
-
module Contract
|
11
|
-
def self.Build(name:"default", constant:nil, builder: nil)
|
12
|
-
step = ->(input, options) { Build.for(input, options, name: name, constant: constant, builder: builder) }
|
13
|
-
|
14
|
-
[ step, name: "contract.build" ]
|
15
|
-
end
|
16
|
-
|
17
|
-
module Build
|
18
|
-
# bla build contract at runtime.
|
19
|
-
def self.for(operation, options, name:"default", constant:nil, builder: nil)
|
20
|
-
# TODO: we could probably clean this up a bit at some point.
|
21
|
-
contract_class = constant || options["contract.#{name}.class"]
|
22
|
-
model = options["model"] # FIXME: model.default
|
23
|
-
name = "contract.#{name}"
|
24
|
-
|
25
|
-
return options[name] = Option::KW.(builder).(operation, options, constant: contract_class, name: name) if builder
|
26
|
-
|
27
|
-
options[name] = contract_class.new(model)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
module DSL
|
32
|
-
# This is the class level DSL method.
|
33
|
-
# Op.contract #=> returns contract class
|
34
|
-
# Op.contract do .. end # defines contract
|
35
|
-
# Op.contract CommentForm # copies (and subclasses) external contract.
|
36
|
-
# Op.contract CommentForm do .. end # copies and extends contract.
|
37
|
-
def contract(name=:default, constant=nil, base: Reform::Form, &block)
|
38
|
-
heritage.record(:contract, name, constant, &block)
|
39
|
-
|
40
|
-
path, form_class = Trailblazer::DSL::Build.new.({ prefix: :contract, class: base, container: self }, name, constant, block)
|
41
|
-
|
42
|
-
self[path] = form_class
|
43
|
-
end
|
44
|
-
end # Contract
|
45
|
-
end
|
46
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
require "trailblazer/operation/policy"
|
2
|
-
|
3
|
-
class Trailblazer::Operation
|
4
|
-
module Policy
|
5
|
-
def self.Guard(proc, name: :default, &block)
|
6
|
-
Policy.step(Guard.build(proc), name: name)
|
7
|
-
end
|
8
|
-
|
9
|
-
module Guard
|
10
|
-
def self.build(callable)
|
11
|
-
value = Option::KW.(callable) # Operation::Option
|
12
|
-
|
13
|
-
# this gets wrapped in a Operation::Result object.
|
14
|
-
->(input, options) { Result.new( !!value.(input, options), {} ) }
|
15
|
-
end
|
16
|
-
end # Guard
|
17
|
-
end
|
18
|
-
end
|
@@ -1,60 +0,0 @@
|
|
1
|
-
class Trailblazer::Operation
|
2
|
-
def self.Model(model_class, action=nil)
|
3
|
-
step = Model.for(model_class, action)
|
4
|
-
|
5
|
-
step = Pipetree::Step.new(step, "model.class" => model_class, "model.action" => action)
|
6
|
-
|
7
|
-
[ step, name: "model.build" ]
|
8
|
-
end
|
9
|
-
|
10
|
-
module Model
|
11
|
-
def self.for(model_class, action)
|
12
|
-
builder = Model::Builder.new
|
13
|
-
|
14
|
-
->(input, options) do
|
15
|
-
options["model"] = model = builder.(options, options["params"])
|
16
|
-
|
17
|
-
options["result.model"] = result = Result.new(!model.nil?, {})
|
18
|
-
|
19
|
-
result.success?
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class Builder
|
24
|
-
def call(options, params)
|
25
|
-
deprecate_update!(options)
|
26
|
-
action = options["model.action"] || :new
|
27
|
-
model_class = options["model.class"]
|
28
|
-
|
29
|
-
action = :pass_through unless [:new, :find_by, :find].include?(action)
|
30
|
-
|
31
|
-
send("#{action}!", model_class, params, options["model.action"])
|
32
|
-
end
|
33
|
-
|
34
|
-
def new!(model_class, params, *)
|
35
|
-
model_class.new
|
36
|
-
end
|
37
|
-
|
38
|
-
def find!(model_class, params, *)
|
39
|
-
model_class.find(params[:id])
|
40
|
-
end
|
41
|
-
|
42
|
-
# Doesn't throw an exception and will return false to divert to Left.
|
43
|
-
def find_by!(model_class, params, *)
|
44
|
-
model_class.find_by(id: params[:id])
|
45
|
-
end
|
46
|
-
|
47
|
-
# Call any method on the model class and pass :id.
|
48
|
-
def pass_through!(model_class, params, action)
|
49
|
-
model_class.send(action, params[:id])
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
def deprecate_update!(options) # TODO: remove in 2.1.
|
54
|
-
return unless options["model.action"] == :update
|
55
|
-
options["model.action"] = :find
|
56
|
-
warn "[Trailblazer] Model( .., :update ) is deprecated, please use :find or :find_by."
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module Trailblazer::Operation::Module
|
2
|
-
def self.included(base)
|
3
|
-
base.extend ClassMethods
|
4
|
-
base.extend Included
|
5
|
-
end
|
6
|
-
|
7
|
-
module Included # TODO: use representable's inheritance mechanism.
|
8
|
-
def included(base)
|
9
|
-
super
|
10
|
-
instructions.each { |cfg|
|
11
|
-
method = cfg[0]
|
12
|
-
args = cfg[1].dup
|
13
|
-
block = cfg[2]
|
14
|
-
# options = args.extract_options!.dup # we need to duplicate options has as AM::Validations messes it up later.
|
15
|
-
|
16
|
-
base.send(method, *args, &block) } # property :name, {} do .. end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
module ClassMethods
|
21
|
-
def method_missing(method, *args, &block)
|
22
|
-
instructions << [method, args, block]
|
23
|
-
end
|
24
|
-
|
25
|
-
def instructions
|
26
|
-
@instructions ||= []
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,113 +0,0 @@
|
|
1
|
-
class Trailblazer::Operation
|
2
|
-
def self.Nested(callable, input:nil, output:nil)
|
3
|
-
step = Nested.for(callable, input, output)
|
4
|
-
|
5
|
-
[ step, { name: "Nested(#{callable})" } ]
|
6
|
-
end
|
7
|
-
|
8
|
-
# WARNING: this is experimental API, but it will end up with something like that.
|
9
|
-
module Element
|
10
|
-
# DISCUSS: add builders here.
|
11
|
-
def initialize(wrapped=nil)
|
12
|
-
@wrapped = wrapped
|
13
|
-
end
|
14
|
-
|
15
|
-
module Dynamic
|
16
|
-
def initialize(wrapped)
|
17
|
-
@wrapped = Option::KW.(wrapped)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
module Nested
|
23
|
-
# Please note that the instance_variable_get are here on purpose since the
|
24
|
-
# superinternal API is not entirely decided, yet.
|
25
|
-
# @api private
|
26
|
-
def self.for(step, input, output, is_nestable_object=method(:nestable_object?)) # DISCUSS: use builders here?
|
27
|
-
invoker = Caller::Dynamic.new(step)
|
28
|
-
invoker = Caller.new(step) if is_nestable_object.(step)
|
29
|
-
|
30
|
-
options_for_nested = Options.new
|
31
|
-
options_for_nested = Options::Dynamic.new(input) if input
|
32
|
-
|
33
|
-
options_for_composer = Options::Output.new
|
34
|
-
options_for_composer = Options::Output::Dynamic.new(output) if output
|
35
|
-
|
36
|
-
# This lambda is the strut added on the track, executed at runtime.
|
37
|
-
->(operation, options) do
|
38
|
-
result = invoker.(operation, options, options_for_nested.(operation, options)) # TODO: what about containers?
|
39
|
-
|
40
|
-
options_for_composer.(operation, options, result).each { |k,v| options[k] = v }
|
41
|
-
|
42
|
-
result.success? # DISCUSS: what if we could simply return the result object here?
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.nestable_object?(object)
|
47
|
-
# interestingly, with < we get a weird nil exception. bug in Ruby?
|
48
|
-
object.is_a?(Class) && object <= Trailblazer::Operation
|
49
|
-
end
|
50
|
-
|
51
|
-
# Is executed at runtime and calls the nested operation.
|
52
|
-
class Caller
|
53
|
-
include Element
|
54
|
-
|
55
|
-
def call(input, options, options_for_nested)
|
56
|
-
call_nested(nested(input, options), options_for_nested)
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
def call_nested(operation, options)
|
61
|
-
operation._call(options)
|
62
|
-
end
|
63
|
-
|
64
|
-
def nested(*); @wrapped end
|
65
|
-
|
66
|
-
class Dynamic < Caller
|
67
|
-
include Element::Dynamic
|
68
|
-
|
69
|
-
def nested(input, options)
|
70
|
-
@wrapped.(input, options)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
class Options
|
76
|
-
include Element
|
77
|
-
|
78
|
-
# Per default, only runtime data for nested operation.
|
79
|
-
def call(input, options)
|
80
|
-
options.to_runtime_data[0]
|
81
|
-
end
|
82
|
-
|
83
|
-
class Dynamic
|
84
|
-
include Element::Dynamic
|
85
|
-
|
86
|
-
def call(operation, options)
|
87
|
-
@wrapped.(operation, options, runtime_data: options.to_runtime_data[0], mutable_data: options.to_mutable_data )
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
class Output
|
92
|
-
include Element
|
93
|
-
|
94
|
-
def call(input, options, result)
|
95
|
-
mutable_data_for(result).each { |k,v| options[k] = v }
|
96
|
-
end
|
97
|
-
|
98
|
-
def mutable_data_for(result)
|
99
|
-
result.instance_variable_get(:@data).to_mutable_data
|
100
|
-
end
|
101
|
-
|
102
|
-
class Dynamic < Output
|
103
|
-
include Element::Dynamic
|
104
|
-
|
105
|
-
def call(input, options, result)
|
106
|
-
@wrapped.(input, options, mutable_data: mutable_data_for(result))
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
@@ -1,35 +0,0 @@
|
|
1
|
-
class Trailblazer::Operation
|
2
|
-
module Policy
|
3
|
-
# Step: This generically `call`s a policy and then pushes its result to `options`.
|
4
|
-
# You can use any callable object as a policy with this step.
|
5
|
-
class Eval
|
6
|
-
def initialize(name:nil, path:nil)
|
7
|
-
@name = name
|
8
|
-
@path = path
|
9
|
-
end
|
10
|
-
|
11
|
-
def call(input, options)
|
12
|
-
condition = options[@path] # this allows dependency injection.
|
13
|
-
result = condition.(input, options)
|
14
|
-
|
15
|
-
options["policy.#{@name}"] = result["policy"] # assign the policy as a skill.
|
16
|
-
options["result.policy.#{@name}"] = result
|
17
|
-
|
18
|
-
# flow control
|
19
|
-
result.success? # since we & this, it's only executed OnRight and the return boolean decides the direction, input is passed straight through.
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# Adds the `yield` result to the pipe and treats it like a
|
24
|
-
# policy-compatible object at runtime.
|
25
|
-
def self.step(condition, options, &block)
|
26
|
-
name = options[:name]
|
27
|
-
path = "policy.#{name}.eval"
|
28
|
-
|
29
|
-
step = Eval.new( name: name, path: path )
|
30
|
-
step = Pipetree::Step.new(step, path => condition)
|
31
|
-
|
32
|
-
[ step, name: path ]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|