trailblazer-macro 2.1.0.rc1 → 2.1.0.rc11

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,11 @@
1
+ sudo: false
1
2
  language: ruby
2
- before_install:
3
- - gem install bundler
4
- matrix:
5
- include:
6
- - rvm: 2.1
7
- gemfile: Gemfile
8
- - rvm: 2.2
9
- gemfile: Gemfile
10
- - rvm: 2.3.1
11
- gemfile: Gemfile
12
- - rvm: 2.4.1
13
- gemfile: Gemfile
14
- - rvm: 2.5.0
15
- gemfile: Gemfile
3
+ rvm:
4
+ - 2.6.2
5
+ - 2.5.5
6
+ - 2.4.4
7
+ - 2.3.7
8
+ - 2.2
9
+ - 2.1
10
+
11
+ cache: bundler
data/CHANGES.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 2.1.0.rc11
2
+
3
+ * Works with `>= activity-0.8`.
4
+ * Implement old functionality of `Nested()`.
5
+
1
6
  # 2.1.0.rc1
2
7
 
3
8
  * Use `operation-0.4.1`.
data/Gemfile CHANGED
@@ -5,7 +5,9 @@ gemspec
5
5
 
6
6
  # gem "trailblazer", github: "trailblazer/trailblazer"
7
7
  # gem "trailblazer-activity", path: "../trailblazer-activity"
8
- # gem "trailblazer-operation", path: "../operation"
8
+ # gem "trailblazer-activity-dsl-linear", github: "trailblazer/trailblazer-activity-dsl-linear", branch: 'master'
9
+ # gem "trailblazer-activity-dsl-linear", path: "../trailblazer-activity-dsl-linear"
10
+ # gem "trailblazer-operation", github: "trailblazer/trailblazer-operation", branch: 'linear'
9
11
  # gem "trailblazer-activity"#, github: "trailblazer/trailblazer-activity"
10
12
  # gem "trailblazer-macro-contract", git: "https://github.com/trailblazer/trailblazer-macro-contract"
11
13
 
data/Rakefile CHANGED
@@ -1,8 +1,5 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
- require "rubocop/rake_task"
4
-
5
- task :default => %i[test rubocop]
6
3
 
7
4
  Rake::TestTask.new(:test) do |test|
8
5
  test.libs << 'test'
@@ -10,8 +7,4 @@ Rake::TestTask.new(:test) do |test|
10
7
  test.verbose = true
11
8
  end
12
9
 
13
- RuboCop::RakeTask.new(:rubocop) do |task|
14
- task.patterns = ['lib/**/*.rb', 'test/**/*.rb']
15
- task.options << "--display-cop-names"
16
- task.fail_on_error = false
17
- end
10
+ task default: :test
@@ -1,2 +1,2 @@
1
- require "trailblazer/macro/version"
1
+ require "trailblazer/macro/version" # rubocop:disable Naming/FileName
2
2
  require "trailblazer/macro"
@@ -1,11 +1,29 @@
1
- require "trailblazer/operation"
1
+ require "trailblazer/activity"
2
+ require "trailblazer/activity/dsl/linear" # TODO: remove this dependency
3
+ require "trailblazer/operation" # TODO: remove this dependency
2
4
 
3
- require "trailblazer/operation/inject"
5
+ require "trailblazer/macro/model"
6
+ require "trailblazer/macro/policy"
7
+ require "trailblazer/macro/guard"
8
+ require "trailblazer/macro/pundit"
9
+ require "trailblazer/macro/nested"
10
+ require "trailblazer/macro/rescue"
11
+ require "trailblazer/macro/wrap"
4
12
 
5
- require "trailblazer/operation/model"
6
- require "trailblazer/operation/policy"
7
- require "trailblazer/operation/guard"
8
- require "trailblazer/operation/pundit"
9
- require "trailblazer/operation/nested"
10
- require "trailblazer/operation/rescue"
11
- require "trailblazer/operation/wrap"
13
+ module Trailblazer
14
+ module Macro
15
+ # All macros sit in the {Trailblazer::Macro} namespace, where we forward calls from
16
+ # operations and activities to.
17
+ def self.forward_macros(target)
18
+ target.singleton_class.def_delegators Trailblazer::Macro, :Model, :Wrap, :Rescue, :Nested
19
+ target.const_set(:Policy, Trailblazer::Macro::Policy)
20
+ end
21
+ end
22
+ end
23
+
24
+ # TODO: Forwardable.def_delegators(Operation, Macro, :Model, :Wrap) would be amazing. It really sucks to extend a foreign class.
25
+ # Trailblazer::Operation.singleton_class.extend Forwardable
26
+ # Trailblazer::Macro.forward_macros(Trailblazer::Operation)
27
+
28
+ Trailblazer::Activity::FastTrack.singleton_class.extend Forwardable
29
+ Trailblazer::Macro.forward_macros(Trailblazer::Activity::FastTrack) # monkey-patching sucks.
@@ -1,7 +1,7 @@
1
- class Trailblazer::Operation
1
+ module Trailblazer::Macro
2
2
  module Policy
3
3
  def self.Guard(proc, name: :default, &block)
4
- Policy.step( Guard.build(proc), name: name )
4
+ Policy.step(Guard.build(proc), name: name)
5
5
  end
6
6
 
7
7
  module Guard
@@ -9,8 +9,8 @@ class Trailblazer::Operation
9
9
  option = Trailblazer::Option::KW(callable)
10
10
 
11
11
  # this gets wrapped in a Operation::Result object.
12
- ->( (options, *), circuit_args ) do
13
- Result.new(!!option.call(options, circuit_args), {})
12
+ ->((options, *), circuit_args) do
13
+ Trailblazer::Operation::Result.new(!!option.call(options, circuit_args), {})
14
14
  end
15
15
  end
16
16
  end
@@ -1,23 +1,21 @@
1
- class Trailblazer::Operation
2
- def self.Model(model_class, action=nil, find_by_key=nil)
1
+ module Trailblazer::Macro
2
+ def self.Model(model_class, action = nil, find_by_key = nil)
3
3
  task = Trailblazer::Activity::TaskBuilder::Binary(Model.new)
4
4
 
5
- extension = Trailblazer::Activity::TaskWrap::Merge.new(
6
- Wrap::Inject::Defaults(
7
- "model.class" => model_class,
8
- "model.action" => action,
9
- "model.find_by_key" => find_by_key
10
- )
5
+ injection = Trailblazer::Activity::TaskWrap::Inject::Defaults::Extension(
6
+ "model.class" => model_class,
7
+ "model.action" => action,
8
+ "model.find_by_key" => find_by_key
11
9
  )
12
10
 
13
- { task: task, id: "model.build", Trailblazer::Activity::DSL::Extension.new(extension) => true }
11
+ {task: task, id: "model.build", extensions: [injection]}
14
12
  end
15
13
 
16
14
  class Model
17
15
  def call(options, params:, **)
18
16
  builder = Model::Builder.new
19
17
  options[:model] = model = builder.call(options, params)
20
- options["result.model"] = result = Result.new(!model.nil?, {})
18
+ options["result.model"] = result = Trailblazer::Operation::Result.new(!model.nil?, {})
21
19
 
22
20
  result.success?
23
21
  end
@@ -0,0 +1,76 @@
1
+ # per default, everything we pass into a circuit is immutable. it's the ops/act's job to allow writing (via a Context)
2
+ module Trailblazer
3
+ module Macro
4
+ # {Nested} macro.
5
+ def self.Nested(callable, id: "Nested(#{callable})")
6
+ if callable.is_a?(Class) && callable < Nested.operation_class
7
+ warn %{[Trailblazer] Using the `Nested()` macro with operations and activities is deprecated. Replace `Nested(Create)` with `Subprocess(Create)`.}
8
+ return Nested.operation_class.Subprocess(callable)
9
+ end
10
+
11
+ # dynamic
12
+ task = Nested::Dynamic.new(callable)
13
+
14
+ merge = [
15
+ [Activity::TaskWrap::Pipeline.method(:insert_before), "task_wrap.call_task", ["Nested.compute_nested_activity", task.method(:compute_nested_activity)]],
16
+ [Activity::TaskWrap::Pipeline.method(:insert_after), "task_wrap.call_task", ["Nested.compute_return_signal", task.method(:compute_return_signal)]],
17
+ ]
18
+
19
+ task_wrap_extension = Activity::TaskWrap::Extension(merge: merge)
20
+
21
+ {
22
+ task: task,
23
+ id: id,
24
+ extensions: [task_wrap_extension],
25
+ outputs: task.outputs,
26
+ }
27
+ end
28
+
29
+ # @private
30
+ module Nested
31
+ def self.operation_class
32
+ Operation
33
+ end
34
+
35
+ # For dynamic `Nested`s that do not expose an {Activity} interface.
36
+ # Since we do not know its outputs, we have to map them to :success and :failure, only.
37
+ #
38
+ # This is what {Nested} in 2.0 used to do, where the outcome could only be true/false (or success/failure).
39
+ class Dynamic
40
+ def initialize(nested_activity_decider)
41
+ @nested_activity_decider = Option::KW(nested_activity_decider)
42
+
43
+ @outputs = {
44
+ :success => Activity::Output(Activity::Railway::End::Success.new(semantic: :success), :success),
45
+ :failure => Activity::Output(Activity::Railway::End::Failure.new(semantic: :failure), :failure)
46
+ }
47
+ end
48
+
49
+ attr_reader :outputs
50
+
51
+ # TaskWrap step.
52
+ def compute_nested_activity(wrap_ctx, original_args)
53
+ (ctx, _), original_circuit_options = original_args
54
+
55
+ # TODO: evaluate the option to get the actual "object" to call.
56
+ activity = @nested_activity_decider.(ctx, original_circuit_options)
57
+
58
+ # Overwrite :task so task_wrap.call_task will call this activity.
59
+ # This is a trick so we don't have to repeat logic from #call_task here.
60
+ wrap_ctx[:task] = activity
61
+
62
+ return wrap_ctx, original_args
63
+ end
64
+
65
+ def compute_return_signal(wrap_ctx, original_args)
66
+ # Translate the genuine nested signal to the generic Dynamic end (success/failure, only).
67
+ # Note that here we lose information about what specific event was emitted.
68
+ wrap_ctx[:return_signal] = wrap_ctx[:return_signal].kind_of?(Activity::Railway::End::Success) ?
69
+ @outputs[:success].signal : @outputs[:failure].signal
70
+
71
+ return wrap_ctx, original_args
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,9 +1,9 @@
1
- class Trailblazer::Operation
1
+ module Trailblazer::Macro
2
2
  module Policy
3
3
  # Step: This generically `call`s a policy and then pushes its result to `options`.
4
4
  # You can use any callable object as a policy with this step.
5
5
  class Eval
6
- def initialize(name:nil, path:nil)
6
+ def initialize(name: nil, path: nil)
7
7
  @name = name
8
8
  @path = path
9
9
  end
@@ -11,8 +11,8 @@ class Trailblazer::Operation
11
11
  # incoming low-level {Task API}.
12
12
  # outgoing Task::Binary API.
13
13
  def call((options, flow_options), **circuit_options)
14
- condition = options[ @path ] # this allows dependency injection.
15
- result = condition.( [options, flow_options], **circuit_options )
14
+ condition = options[@path] # this allows dependency injection.
15
+ result = condition.([options, flow_options], **circuit_options)
16
16
 
17
17
  options["policy.#{@name}"] = result["policy"] # assign the policy as a skill.
18
18
  options["result.policy.#{@name}"] = result
@@ -20,7 +20,7 @@ class Trailblazer::Operation
20
20
  # flow control
21
21
  signal = result.success? ? Trailblazer::Activity::Right : Trailblazer::Activity::Left # since we & this, it's only executed OnRight and the return boolean decides the direction, input is passed straight through.
22
22
 
23
- return signal, [ options, flow_options ]
23
+ return signal, [options, flow_options]
24
24
  end
25
25
  end
26
26
 
@@ -30,15 +30,13 @@ class Trailblazer::Operation
30
30
  name = options[:name]
31
31
  path = "policy.#{name}.eval"
32
32
 
33
- task = Eval.new( name: name, path: path )
33
+ task = Eval.new(name: name, path: path)
34
34
 
35
- extension = Trailblazer::Activity::TaskWrap::Merge.new(
36
- Trailblazer::Operation::Wrap::Inject::Defaults(
37
- path => condition
38
- )
35
+ injection = Trailblazer::Activity::TaskWrap::Inject::Defaults::Extension(
36
+ path => condition
39
37
  )
40
38
 
41
- { task: task, id: path, Trailblazer::Activity::DSL::Extension.new(extension) => true }
39
+ {task: task, id: path, extensions: [injection]}
42
40
  end
43
41
  end
44
42
  end
@@ -1,7 +1,7 @@
1
- class Trailblazer::Operation
1
+ module Trailblazer::Macro
2
2
  module Policy
3
3
  def self.Pundit(policy_class, action, name: :default)
4
- Policy.step( Pundit.build(policy_class, action), name: name )
4
+ Policy.step(Pundit.build(policy_class, action), name: name)
5
5
  end
6
6
 
7
7
  module Pundit
@@ -17,7 +17,7 @@ class Trailblazer::Operation
17
17
 
18
18
  # Instantiate the actual policy object, and call it.
19
19
  def call((options), *)
20
- policy = build_policy(options) # this translates to Pundit interface.
20
+ policy = build_policy(options) # this translates to Pundit interface.
21
21
  result!(policy.send(@action), policy)
22
22
  end
23
23
 
@@ -27,10 +27,10 @@ class Trailblazer::Operation
27
27
  end
28
28
 
29
29
  def result!(success, policy)
30
- data = { "policy" => policy }
30
+ data = {"policy" => policy}
31
31
  data["message"] = "Breach" if !success # TODO: how to allow messages here?
32
32
 
33
- Result.new(success, data)
33
+ Trailblazer::Operation::Result.new(success, data)
34
34
  end
35
35
  end
36
36
  end
@@ -0,0 +1,44 @@
1
+ module Trailblazer
2
+ module Macro
3
+ NoopHandler = lambda { |*| }
4
+
5
+ def self.Rescue(*exceptions, handler: NoopHandler, &block)
6
+ exceptions = [StandardError] unless exceptions.any?
7
+
8
+ handler = Rescue.deprecate_positional_handler_signature(handler)
9
+ handler = Trailblazer::Option(handler)
10
+
11
+ # This block is evaluated by {Wrap}.
12
+ rescue_block = ->((ctx, flow_options), **circuit_options, &nested_activity) do
13
+ begin
14
+ nested_activity.call
15
+ rescue *exceptions => exception
16
+ # DISCUSS: should we deprecate this signature and rather apply the Task API here?
17
+ handler.call(exception, ctx, **circuit_options) # FIXME: when there's an error here, it shows the wrong exception!
18
+
19
+ [Operation::Railway.fail!, [ctx, flow_options]]
20
+ end
21
+ end
22
+
23
+ Wrap(rescue_block, id: "Rescue(#{rand(100)})", &block)
24
+ # FIXME: name
25
+ # [ step, name: "Rescue:#{block.source_location.last}" ]
26
+ end
27
+
28
+ # TODO: remove me in 2.2.
29
+ module Rescue
30
+ def self.deprecate_positional_handler_signature(handler)
31
+ return handler if handler.is_a?(Symbol) # can't do nutting about this.
32
+
33
+ arity = handler.is_a?(Class) ? handler.method(:call).arity : handler.arity
34
+
35
+ return handler if arity != 2 # means (exception, (ctx, flow_options), *, &block), "new style"
36
+
37
+ ->(exception, (ctx, flow_options), **circuit_options, &block) do
38
+ warn "[Trailblazer] Rescue handlers have a new signature: (exception, *, &block)"
39
+ handler.(exception, ctx, &block)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,7 @@
1
1
  module Trailblazer
2
- module Macro
3
- VERSION = "2.1.0.rc1".freeze
2
+ module Version
3
+ module Macro
4
+ VERSION = "2.1.0.rc11".freeze
5
+ end
4
6
  end
5
7
  end
@@ -0,0 +1,80 @@
1
+ module Trailblazer
2
+ module Macro
3
+ def self.Wrap(user_wrap, id: "Wrap/#{rand(100)}", &block)
4
+ activity = Class.new(Activity::FastTrack, &block) # This is currently coupled to {dsl-linear}.
5
+
6
+ outputs = activity.to_h[:outputs]
7
+ outputs = Hash[outputs.collect { |output| [output.semantic, output] }] # TODO: make that a helper somewhere.
8
+
9
+ wrapped = Wrap::Wrapped.new(activity, user_wrap, outputs)
10
+
11
+ {task: wrapped, id: id, outputs: outputs}
12
+ end
13
+
14
+ module Wrap
15
+ # behaves like an operation so it plays with Nested and simply calls the operation in the user-provided block.
16
+ class Wrapped
17
+ private def deprecate_positional_wrap_signature(user_wrap)
18
+ parameters = user_wrap.is_a?(Module) ? user_wrap.method(:call).parameters : user_wrap.parameters
19
+
20
+ return user_wrap if parameters[0] == [:req] # means ((ctx, flow_options), *, &block), "new style"
21
+
22
+ ->((ctx, flow_options), **circuit_options, &block) do
23
+ warn "[Trailblazer] Wrap handlers have a new signature: ((ctx), *, &block)"
24
+ user_wrap.(ctx, &block)
25
+ end
26
+ end
27
+
28
+ def initialize(operation, user_wrap, outputs)
29
+ user_wrap = deprecate_positional_wrap_signature(user_wrap)
30
+
31
+ @operation = operation
32
+ @user_wrap = user_wrap
33
+
34
+ # Since in the user block, you can return Railway.pass! etc, we need to map
35
+ # those to the actual wrapped operation's end.
36
+ @signal_to_output = {
37
+ Operation::Railway.pass! => outputs[:success].signal,
38
+ Operation::Railway.fail! => outputs[:failure].signal,
39
+ Operation::Railway.pass_fast! => outputs[:pass_fast].signal,
40
+ Operation::Railway.fail_fast! => outputs[:fail_fast].signal,
41
+ true => outputs[:success].signal,
42
+ false => outputs[:failure].signal,
43
+ nil => outputs[:failure].signal,
44
+ }
45
+ end
46
+
47
+ def call((ctx, flow_options), **circuit_options)
48
+ block_calling_wrapped = -> {
49
+ call_wrapped_activity([ctx, flow_options], circuit_options)
50
+ }
51
+
52
+ # call the user's Wrap {} block in the operation.
53
+ # This will invoke block_calling_wrapped above if the user block yields.
54
+ returned = @user_wrap.([ctx, flow_options], **circuit_options, &block_calling_wrapped)
55
+
56
+ # {returned} can be
57
+ # 1. {circuit interface return} from the begin block, because the wrapped OP passed
58
+ # 2. {task interface return} because the user block returns "customized" signals, true of fale
59
+
60
+ if returned.is_a?(Array) # 1. {circuit interface return}, new style.
61
+ signal, (ctx, flow_options) = returned
62
+ else # 2. {task interface return}, only a signal (or true/false)
63
+ # TODO: deprecate this?
64
+ signal = returned
65
+ end
66
+
67
+ # Use the original {signal} if there's no mapping.
68
+ # This usually means signal is an End instance or a custom signal.
69
+ signal = @signal_to_output.fetch(signal, signal)
70
+
71
+ return signal, [ctx, flow_options]
72
+ end
73
+
74
+ def call_wrapped_activity((ctx, flow_options), **circuit_options)
75
+ @operation.to_h[:activity].([ctx, flow_options], **circuit_options) # :exec_context is this instance.
76
+ end
77
+ end
78
+ end # Wrap
79
+ end
80
+ end