trailblazer 2.0.7 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +101 -0
  4. data/.rubocop.yml +20 -0
  5. data/.rubocop_todo.yml +556 -0
  6. data/.travis.yml +6 -10
  7. data/CHANGES.md +83 -1
  8. data/COMM-LICENSE +46 -75
  9. data/CONTRIBUTING.md +179 -0
  10. data/Gemfile +0 -27
  11. data/{LICENSE.txt → LICENSE} +4 -4
  12. data/README.md +39 -138
  13. data/Rakefile +2 -19
  14. data/lib/trailblazer.rb +3 -17
  15. data/lib/trailblazer/version.rb +3 -1
  16. data/test/test_helper.rb +12 -3
  17. data/trailblazer.gemspec +10 -14
  18. metadata +22 -147
  19. data/doc/Trb-The-Stack.png +0 -0
  20. data/doc/operation-2017.png +0 -0
  21. data/doc/trb.jpg +0 -0
  22. data/lib/trailblazer/dsl.rb +0 -47
  23. data/lib/trailblazer/operation/auto_inject.rb +0 -47
  24. data/lib/trailblazer/operation/callback.rb +0 -35
  25. data/lib/trailblazer/operation/contract.rb +0 -46
  26. data/lib/trailblazer/operation/guard.rb +0 -18
  27. data/lib/trailblazer/operation/model.rb +0 -60
  28. data/lib/trailblazer/operation/module.rb +0 -29
  29. data/lib/trailblazer/operation/nested.rb +0 -113
  30. data/lib/trailblazer/operation/persist.rb +0 -10
  31. data/lib/trailblazer/operation/policy.rb +0 -35
  32. data/lib/trailblazer/operation/procedural/contract.rb +0 -15
  33. data/lib/trailblazer/operation/procedural/validate.rb +0 -22
  34. data/lib/trailblazer/operation/pundit.rb +0 -38
  35. data/lib/trailblazer/operation/representer.rb +0 -31
  36. data/lib/trailblazer/operation/rescue.rb +0 -21
  37. data/lib/trailblazer/operation/test.rb +0 -17
  38. data/lib/trailblazer/operation/validate.rb +0 -68
  39. data/lib/trailblazer/operation/wrap.rb +0 -25
  40. data/test/docs/auto_inject_test.rb +0 -30
  41. data/test/docs/contract_test.rb +0 -525
  42. data/test/docs/dry_test.rb +0 -31
  43. data/test/docs/fast_test.rb +0 -164
  44. data/test/docs/guard_test.rb +0 -169
  45. data/test/docs/macro_test.rb +0 -36
  46. data/test/docs/model_test.rb +0 -75
  47. data/test/docs/nested_test.rb +0 -334
  48. data/test/docs/operation_test.rb +0 -408
  49. data/test/docs/policy_test.rb +0 -2
  50. data/test/docs/pundit_test.rb +0 -133
  51. data/test/docs/representer_test.rb +0 -268
  52. data/test/docs/rescue_test.rb +0 -154
  53. data/test/docs/wrap_test.rb +0 -183
  54. data/test/gemfiles/Gemfile.ruby-1.9 +0 -3
  55. data/test/gemfiles/Gemfile.ruby-2.0 +0 -12
  56. data/test/gemfiles/Gemfile.ruby-2.3 +0 -12
  57. data/test/module_test.rb +0 -100
  58. data/test/operation/callback_test.rb +0 -70
  59. data/test/operation/contract_test.rb +0 -420
  60. data/test/operation/dsl/callback_test.rb +0 -106
  61. data/test/operation/dsl/contract_test.rb +0 -294
  62. data/test/operation/dsl/representer_test.rb +0 -169
  63. data/test/operation/model_test.rb +0 -60
  64. data/test/operation/params_test.rb +0 -36
  65. data/test/operation/persist_test.rb +0 -44
  66. data/test/operation/pipedream_test.rb +0 -59
  67. data/test/operation/pipetree_test.rb +0 -104
  68. data/test/operation/present_test.rb +0 -24
  69. data/test/operation/pundit_test.rb +0 -104
  70. data/test/operation/representer_test.rb +0 -254
  71. data/test/operation/resolver_test.rb +0 -47
  72. data/test/operation_test.rb +0 -143
Binary file
Binary file
Binary file
@@ -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,10 +0,0 @@
1
- class Trailblazer::Operation
2
- module Contract
3
- def self.Persist(method: :save, name: "default")
4
- path = "contract.#{name}"
5
- step = ->(input, options) { options[path].send(method) }
6
-
7
- [ step, name: "persist.save" ]
8
- end
9
- end
10
- end
@@ -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