interactify 0.3.0.pre.alpha.1 → 0.4.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/.ruby-version +1 -0
  4. data/Appraisals +23 -0
  5. data/CHANGELOG.md +14 -0
  6. data/README.md +33 -44
  7. data/gemfiles/.bundle/config +2 -0
  8. data/gemfiles/no_railties_no_sidekiq.gemfile +18 -0
  9. data/gemfiles/no_railties_no_sidekiq.gemfile.lock +127 -0
  10. data/gemfiles/railties_6.gemfile +14 -0
  11. data/gemfiles/railties_6.gemfile.lock +253 -0
  12. data/gemfiles/railties_6_no_sidekiq.gemfile +19 -0
  13. data/gemfiles/railties_6_no_sidekiq.gemfile.lock +159 -0
  14. data/gemfiles/railties_6_sidekiq.gemfile +20 -0
  15. data/gemfiles/railties_6_sidekiq.gemfile.lock +168 -0
  16. data/gemfiles/railties_7_no_sidekiq.gemfile +19 -0
  17. data/gemfiles/railties_7_no_sidekiq.gemfile.lock +158 -0
  18. data/gemfiles/railties_7_sidekiq.gemfile +20 -0
  19. data/gemfiles/railties_7_sidekiq.gemfile.lock +167 -0
  20. data/lib/interactify/async/job_klass.rb +63 -0
  21. data/lib/interactify/async/job_maker.rb +58 -0
  22. data/lib/interactify/async/jobable.rb +96 -0
  23. data/lib/interactify/async/null_job.rb +23 -0
  24. data/lib/interactify/configuration.rb +15 -0
  25. data/lib/interactify/contracts/call_wrapper.rb +19 -0
  26. data/lib/interactify/contracts/failure.rb +8 -0
  27. data/lib/interactify/contracts/helpers.rb +81 -0
  28. data/lib/interactify/contracts/mismatching_promise_error.rb +19 -0
  29. data/lib/interactify/contracts/promising.rb +36 -0
  30. data/lib/interactify/contracts/setup.rb +39 -0
  31. data/lib/interactify/dsl/each_chain.rb +90 -0
  32. data/lib/interactify/dsl/if_interactor.rb +81 -0
  33. data/lib/interactify/dsl/if_klass.rb +82 -0
  34. data/lib/interactify/dsl/organizer.rb +32 -0
  35. data/lib/interactify/dsl/unique_klass_name.rb +23 -0
  36. data/lib/interactify/dsl/wrapper.rb +74 -0
  37. data/lib/interactify/dsl.rb +12 -6
  38. data/lib/interactify/rspec_matchers/matchers.rb +68 -0
  39. data/lib/interactify/version.rb +1 -1
  40. data/lib/interactify/{interactor_wiring → wiring}/callable_representation.rb +2 -2
  41. data/lib/interactify/{interactor_wiring → wiring}/constants.rb +4 -4
  42. data/lib/interactify/{interactor_wiring → wiring}/error_context.rb +1 -1
  43. data/lib/interactify/{interactor_wiring → wiring}/files.rb +1 -1
  44. data/lib/interactify/{interactor_wiring.rb → wiring.rb} +5 -5
  45. data/lib/interactify.rb +58 -38
  46. metadata +48 -71
  47. data/lib/interactify/async_job_klass.rb +0 -61
  48. data/lib/interactify/call_wrapper.rb +0 -17
  49. data/lib/interactify/contract_failure.rb +0 -6
  50. data/lib/interactify/contract_helpers.rb +0 -71
  51. data/lib/interactify/each_chain.rb +0 -88
  52. data/lib/interactify/if_interactor.rb +0 -70
  53. data/lib/interactify/interactor_wrapper.rb +0 -72
  54. data/lib/interactify/job_maker.rb +0 -56
  55. data/lib/interactify/jobable.rb +0 -92
  56. data/lib/interactify/mismatching_promise_error.rb +0 -17
  57. data/lib/interactify/organizer.rb +0 -30
  58. data/lib/interactify/promising.rb +0 -34
  59. data/lib/interactify/rspec/matchers.rb +0 -67
  60. data/lib/interactify/unique_klass_name.rb +0 -21
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactify
4
+ module Async
5
+ class JobKlass
6
+ attr_reader :container_klass, :klass_suffix
7
+
8
+ def initialize(container_klass:, klass_suffix:)
9
+ @container_klass = container_klass
10
+ @klass_suffix = klass_suffix
11
+ end
12
+
13
+ def async_job_klass
14
+ klass = Class.new do
15
+ include Interactor
16
+ include Interactor::Contracts
17
+ end
18
+
19
+ attach_call(klass)
20
+ attach_call!(klass)
21
+
22
+ klass
23
+ end
24
+
25
+ def attach_call(async_job_klass)
26
+ # e.g. SomeInteractor::AsyncWithSuffix.call(foo: 'bar')
27
+ async_job_klass.send(:define_singleton_method, :call) do |context|
28
+ call!(context)
29
+ end
30
+ end
31
+
32
+ def attach_call!(async_job_klass)
33
+ this = self
34
+
35
+ # e.g. SomeInteractor::AsyncWithSuffix.call!(foo: 'bar')
36
+ async_job_klass.send(:define_singleton_method, :call!) do |context|
37
+ # e.g. SomeInteractor::JobWithSuffix
38
+ job_klass = this.container_klass.const_get("Job#{this.klass_suffix}")
39
+
40
+ # e.g. SomeInteractor::JobWithSuffix.perform_async({foo: 'bar'})
41
+ job_klass.perform_async(this.args(context))
42
+ end
43
+ end
44
+
45
+ def args(context)
46
+ args = context.to_h.stringify_keys
47
+
48
+ return args unless container_klass.respond_to?(:expected_keys)
49
+
50
+ restrict_to_optional_or_keys_from_contract(args)
51
+ end
52
+
53
+ def restrict_to_optional_or_keys_from_contract(args)
54
+ keys = container_klass.expected_keys.map(&:to_s)
55
+
56
+ optional = Array(container_klass.optional_attrs).map(&:to_s)
57
+ keys += optional
58
+
59
+ args.slice(*keys)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "interactify/async/job_klass"
4
+ require "interactify/async/null_job"
5
+
6
+ module Interactify
7
+ module Async
8
+ class JobMaker
9
+ attr_reader :opts, :method_name, :container_klass, :klass_suffix
10
+
11
+ def initialize(container_klass:, opts:, klass_suffix:, method_name: :call!)
12
+ @container_klass = container_klass
13
+ @opts = opts
14
+ @method_name = method_name
15
+ @klass_suffix = klass_suffix
16
+ end
17
+
18
+ concerning :JobClass do
19
+ def job_klass
20
+ @job_klass ||= define_job_klass
21
+ end
22
+
23
+ private
24
+
25
+ def define_job_klass
26
+ return NullJob if Interactify.sidekiq_missing?
27
+
28
+ this = self
29
+
30
+ invalid_keys = this.opts.symbolize_keys.keys - %i[queue retry dead backtrace pool tags]
31
+
32
+ raise ArgumentError, "Invalid keys: #{invalid_keys}" if invalid_keys.any?
33
+
34
+ build_job_klass(opts).tap do |klass|
35
+ klass.const_set(:JOBABLE_OPTS, opts)
36
+ klass.const_set(:JOBABLE_METHOD_NAME, method_name)
37
+ end
38
+ end
39
+
40
+ def build_job_klass(opts)
41
+ Class.new do
42
+ include Sidekiq::Job
43
+
44
+ sidekiq_options(opts)
45
+
46
+ def perform(...)
47
+ self.class.module_parent.send(self.class::JOBABLE_METHOD_NAME, ...)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ def async_job_klass
54
+ JobKlass.new(container_klass:, klass_suffix:).async_job_klass
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "interactify/async/job_maker"
4
+
5
+ module Interactify
6
+ module Async
7
+ module Jobable
8
+ extend ActiveSupport::Concern
9
+
10
+ # e.g. if Klass < Base
11
+ # and Base has a Base::Job class
12
+ #
13
+ # then let's make sure to define Klass::Job separately
14
+ included do |base|
15
+ next if Interactify.sidekiq_missing?
16
+
17
+ def base.inherited(klass)
18
+ super_klass = klass.superclass
19
+ super_job = super_klass::Job # really spiffing
20
+
21
+ opts = super_job::JOBABLE_OPTS
22
+ jobable_method_name = super_job::JOBABLE_METHOD_NAME
23
+
24
+ to_call = defined?(super_klass::Async) ? :interactor_job : :job_calling
25
+
26
+ klass.send(to_call, opts:, method_name: jobable_method_name)
27
+ super(klass)
28
+ end
29
+ end
30
+
31
+ class_methods do
32
+ # create a Job class and an Async class
33
+ # see job_calling for details on the Job class
34
+ #
35
+ # the Async class is a wrapper around the Job class
36
+ # that allows it to be used in an interactor chain
37
+ #
38
+ # E.g.
39
+ #
40
+ # class ExampleInteractor
41
+ # include Interactify
42
+ # expect :foo
43
+ #
44
+ # include Jobable
45
+ # interactor_job
46
+ # end
47
+ #
48
+ # doing the following will immediately enqueue a job
49
+ # that calls the interactor ExampleInteractor with (foo: 'bar')
50
+ # ExampleInteractor::Async.call(foo: 'bar')
51
+ #
52
+ # it will also ensure to pluck only the expects from the context
53
+ # so that you can have other non primitive values in the context
54
+ # but the job will only have the expects passed to it
55
+ #
56
+ # obviously you will need to be aware that later interactors
57
+ # in an interactor chain cannot depend on the result of the async
58
+ # interactor
59
+ def interactor_job(method_name: :call!, opts: {}, klass_suffix: "")
60
+ job_maker = JobMaker.new(container_klass: self, opts:, method_name:, klass_suffix:)
61
+ # with WhateverInteractor::Job you can perform the interactor as a job
62
+ # from sidekiq
63
+ # e.g. WhateverInteractor::Job.perform_async(...)
64
+ const_set("Job#{klass_suffix}", job_maker.job_klass)
65
+
66
+ # with WhateverInteractor::Async you can call WhateverInteractor::Job
67
+ # in an organizer oro on its oen using normal interactor call call! semantics
68
+ # e.g. WhateverInteractor::Async.call(...)
69
+ # WhateverInteractor::Async.call!(...)
70
+ const_set("Async#{klass_suffix}", job_maker.async_job_klass)
71
+ end
72
+
73
+ # if this was defined in ExampleClass this creates the following class
74
+ # ExampleClass::Job
75
+ # this class ia added as a convenience so you can easily turn a
76
+ # class method into a job
77
+ #
78
+ # Example:
79
+ #
80
+ # class ExampleClass
81
+ # include Jobable
82
+ # job_calling method_name: :some_method
83
+ # end
84
+ #
85
+ # # the following class is created that you can use to enqueue a job
86
+ # in the sidekiq yaml file
87
+ # ExampleClass::Job.some_method
88
+ def job_calling(method_name:, opts: {}, klass_suffix: "")
89
+ job_maker = JobMaker.new(container_klass: self, opts:, method_name:, klass_suffix:)
90
+
91
+ const_set("Job#{klass_suffix}", job_maker.job_klass)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactify
4
+ module Async
5
+ class NullJob
6
+ def method_missing(...)
7
+ self
8
+ end
9
+
10
+ def self.method_missing(...)
11
+ self
12
+ end
13
+
14
+ def respond_to_missing?(...)
15
+ true
16
+ end
17
+
18
+ def self.respond_to_missing?(...)
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactify
4
+ class Configuration
5
+ attr_writer :root
6
+
7
+ def root
8
+ @root ||= fallback
9
+ end
10
+
11
+ def fallback
12
+ Rails.root / "app" if Interactify.railties?
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactify
4
+ module Contracts
5
+ module CallWrapper
6
+ # https://github.com/collectiveidea/interactor/blob/57b2af9a5a5afeb2c01059c40b792485cc21b052/lib/interactor.rb#L114
7
+ # Interactor#run calls Interactor#run!
8
+ # https://github.com/collectiveidea/interactor/blob/57b2af9a5a5afeb2c01059c40b792485cc21b052/lib/interactor.rb#L49
9
+ # Interactor.call calls Interactor.run
10
+ #
11
+ # The non bang methods call the bang methods and rescue
12
+ def run
13
+ @_interactor_called_by_non_bang_method = true
14
+
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactify
4
+ module Contracts
5
+ class Failure < ::Interactor::Failure
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "interactify/async/jobable"
4
+ require "interactify/contracts/call_wrapper"
5
+ require "interactify/contracts/failure"
6
+ require "interactify/contracts/setup"
7
+ require "interactify/dsl/organizer"
8
+
9
+ module Interactify
10
+ module Contracts
11
+ module Helpers
12
+ extend ActiveSupport::Concern
13
+
14
+ class_methods do
15
+ def expect(*attrs, filled: true)
16
+ Setup.expects(context: self, attrs:, filled:)
17
+ end
18
+
19
+ def promise(*attrs, filled: true, should_delegate: true)
20
+ Setup.promises(context: self, attrs:, filled:, should_delegate:)
21
+ end
22
+
23
+ def promising(*args)
24
+ Promising.validate(self, *args)
25
+ end
26
+
27
+ def promised_keys
28
+ _interactify_extract_keys(contract.promises)
29
+ end
30
+
31
+ def expected_keys
32
+ _interactify_extract_keys(contract.expectations)
33
+ end
34
+
35
+ def optional(*attrs)
36
+ @optional_attrs ||= []
37
+ @optional_attrs += attrs
38
+
39
+ delegate(*attrs, to: :context)
40
+ end
41
+
42
+ attr_reader :optional_attrs
43
+
44
+ private
45
+
46
+ # this is the most brittle part of the code, relying on
47
+ # interactor-contracts internals
48
+ # so extracted it to here so change is isolated
49
+ def _interactify_extract_keys(clauses)
50
+ clauses.instance_eval { @terms }.json&.rules&.keys
51
+ end
52
+ end
53
+
54
+ included do
55
+ c = Class.new(Contracts::Failure)
56
+ # example self is Whatever::SomeInteractor
57
+ # failure class: Whatever::SomeInteractor::InteractorContractFailure
58
+ const_set "InteractorContractFailure", c
59
+ prepend Contracts::CallWrapper
60
+ include Dsl::Organizer
61
+
62
+ on_breach do |breaches|
63
+ breaches = breaches.map { |b| { b.property => b.messages } }.inject(&:merge)
64
+
65
+ Interactify.trigger_contract_breach_hook(context, breaches)
66
+
67
+ if @_interactor_called_by_non_bang_method == true
68
+ context.fail! contract_failures: breaches
69
+ else
70
+ # e.g. raises
71
+ # SomeNamespace::SomeClass::ContractFailure, {whatever: 'is missing'}
72
+ # but also sending the context into Sentry
73
+ exception = c.new(breaches.to_json)
74
+ Interactify.trigger_before_raise_hook(exception)
75
+ raise exception
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "interactify/contracts/failure"
4
+
5
+ module Interactify
6
+ module Contracts
7
+ class MismatchingPromiseError < Contracts::Failure
8
+ def initialize(interactor, promising, promised_keys)
9
+ super <<~MESSAGE.chomp
10
+ #{interactor} does not promise:
11
+ #{promising.inspect}
12
+
13
+ Actual promises are:
14
+ #{promised_keys.inspect}
15
+ MESSAGE
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "interactify/contracts/mismatching_promise_error"
4
+
5
+ module Interactify
6
+ module Contracts
7
+ class Promising
8
+ attr_reader :interactor, :promising
9
+
10
+ def self.validate(interactor, *promising)
11
+ new(interactor, *promising).validate
12
+
13
+ interactor
14
+ end
15
+
16
+ def initialize(interactor, *promising)
17
+ @interactor = interactor
18
+ @promising = format_keys promising
19
+ end
20
+
21
+ def validate
22
+ return if promising == promised_keys
23
+
24
+ raise MismatchingPromiseError.new(interactor, promising, promised_keys)
25
+ end
26
+
27
+ def promised_keys
28
+ format_keys interactor.promised_keys
29
+ end
30
+
31
+ def format_keys(keys)
32
+ Array(keys).compact.map(&:to_sym).sort
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interactify
4
+ module Contracts
5
+ class Setup
6
+ def self.expects(context:, attrs:, filled:)
7
+ new(context:, attrs:, filled:).setup(:expects)
8
+ end
9
+
10
+ def self.promises(context:, attrs:, filled:, should_delegate:)
11
+ new(context:, attrs:, filled:, should_delegate:).setup(:promises)
12
+ end
13
+
14
+ def initialize(context:, attrs:, filled:, should_delegate: true)
15
+ @context = context
16
+ @attrs = attrs
17
+ @filled = filled
18
+ @should_delegate = should_delegate
19
+ end
20
+
21
+ def setup(meth)
22
+ this = self
23
+
24
+ @context.send(meth) do
25
+ this.setup_attrs self
26
+ end
27
+
28
+ @context.delegate(*@attrs, to: :context) if @should_delegate
29
+ end
30
+
31
+ def setup_attrs(contract)
32
+ @attrs.each do |attr|
33
+ field = contract.required(attr)
34
+ field.filled if @filled
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "interactify/dsl/unique_klass_name"
4
+
5
+ module Interactify
6
+ module Dsl
7
+ class EachChain
8
+ attr_reader :each_loop_klasses, :plural_resource_name, :evaluating_receiver
9
+
10
+ def self.attach_klass(evaluating_receiver, plural_resource_name, *each_loop_klasses)
11
+ iteratable = new(each_loop_klasses, plural_resource_name, evaluating_receiver)
12
+ iteratable.attach_klass
13
+ end
14
+
15
+ def initialize(each_loop_klasses, plural_resource_name, evaluating_receiver)
16
+ @each_loop_klasses = each_loop_klasses
17
+ @plural_resource_name = plural_resource_name
18
+ @evaluating_receiver = evaluating_receiver
19
+ end
20
+
21
+ # allows us to dynamically create an interactor chain
22
+ # that iterates over the packages and
23
+ # uses the passed in each_loop_klasses
24
+ # rubocop:disable all
25
+ def klass
26
+ this = self
27
+
28
+ Class.new do # class SomeNamespace::EachPackage
29
+ include Interactify # include Interactify
30
+
31
+ expects do # expects do
32
+ required(this.plural_resource_name) # required(:packages)
33
+ end # end
34
+
35
+ define_singleton_method(:source_location) do # def self.source_location
36
+ const_source_location this.evaluating_receiver.to_s # [file, line]
37
+ end # end
38
+
39
+ define_method(:run!) do # def run!
40
+ context.send(this.plural_resource_name).each_with_index do |resource, index|# context.packages.each_with_index do |package, index|
41
+ context[this.singular_resource_name] = resource # context.package = package
42
+ context[this.singular_resource_index_name] = index # context.package_index = index
43
+
44
+ klasses = Wrapper.wrap_many(self, this.each_loop_klasses)
45
+
46
+ klasses.each do |interactor| # [A, B, C].each do |interactor|
47
+ interactor.call!(context) # interactor.call!(context)
48
+ end # end
49
+ end # end
50
+
51
+ context[this.singular_resource_name] = nil # context.package = nil
52
+ context[this.singular_resource_index_name] = nil # context.package_index = nil
53
+
54
+ context # context
55
+ end # end
56
+
57
+ define_method(:inspect) do
58
+ "<#{this.namespace}::#{this.iterator_klass_name} iterates_over: #{this.each_loop_klasses.inspect}>"
59
+ end
60
+ end
61
+ end
62
+ # rubocop:enable all
63
+
64
+ def attach_klass
65
+ name = iterator_klass_name
66
+
67
+ namespace.const_set(name, klass)
68
+ namespace.const_get(name)
69
+ end
70
+
71
+ def namespace
72
+ evaluating_receiver
73
+ end
74
+
75
+ def iterator_klass_name
76
+ prefix = "Each#{singular_resource_name.to_s.camelize}"
77
+
78
+ UniqueKlassName.for(namespace, prefix)
79
+ end
80
+
81
+ def singular_resource_name
82
+ plural_resource_name.to_s.singularize.to_sym
83
+ end
84
+
85
+ def singular_resource_index_name
86
+ "#{singular_resource_name}_index".to_sym
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "interactify/dsl/unique_klass_name"
4
+ require "interactify/dsl/if_klass"
5
+
6
+ module Interactify
7
+ module Dsl
8
+ class IfInteractor
9
+ attr_reader :condition, :evaluating_receiver
10
+
11
+ def self.attach_klass(evaluating_receiver, condition, succcess_interactor, failure_interactor)
12
+ ifable = new(evaluating_receiver, condition, succcess_interactor, failure_interactor)
13
+ ifable.attach_klass
14
+ end
15
+
16
+ def initialize(evaluating_receiver, condition, succcess_arg, failure_arg)
17
+ @evaluating_receiver = evaluating_receiver
18
+ @condition = condition
19
+ @success_arg = succcess_arg
20
+ @failure_arg = failure_arg
21
+ end
22
+
23
+ def success_interactor
24
+ @success_interactor ||= build_chain(@success_arg, true)
25
+ end
26
+
27
+ def failure_interactor
28
+ @failure_interactor ||= build_chain(@failure_arg, false)
29
+ end
30
+
31
+ # allows us to dynamically create an interactor chain
32
+ # that iterates over the packages and
33
+ # uses the passed in each_loop_klasses
34
+ def klass
35
+ IfKlass.new(self).klass
36
+ end
37
+
38
+ # so we have something to attach subclasses to during building
39
+ # of the outer class, before we finalize the outer If class
40
+ def klass_basis
41
+ @klass_basis ||= Class.new do
42
+ include Interactify
43
+ end
44
+ end
45
+
46
+ def attach_klass
47
+ name = if_klass_name
48
+ namespace.const_set(name, klass)
49
+ namespace.const_get(name)
50
+ end
51
+
52
+ def namespace
53
+ evaluating_receiver
54
+ end
55
+
56
+ def if_klass_name
57
+ @if_klass_name ||=
58
+ begin
59
+ prefix = condition.is_a?(Proc) ? "Proc" : condition
60
+ prefix = "If#{prefix.to_s.camelize}"
61
+
62
+ UniqueKlassName.for(namespace, prefix)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def build_chain(arg, truthiness)
69
+ return if arg.nil?
70
+
71
+ case arg
72
+ when Array
73
+ name = "If#{condition.to_s.camelize}#{truthiness ? 'IsTruthy' : 'IsFalsey'}"
74
+ klass_basis.chain(name, *arg)
75
+ else
76
+ arg
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end