Skip to content

Commit

Permalink
Improve code with with new ruby features
Browse files Browse the repository at this point in the history
  • Loading branch information
pabloh committed Aug 7, 2024
1 parent 3ac501f commit 3e04115
Show file tree
Hide file tree
Showing 10 changed files with 78 additions and 122 deletions.
90 changes: 37 additions & 53 deletions lib/pathway.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class DSL

class Error
attr_reader :type, :message, :details

singleton_class.send :attr_accessor, :default_messages

@default_messages = {}
Expand All @@ -44,13 +45,8 @@ def initialize(type:, message: nil, details: nil)
@details = details || {}
end

def deconstruct
[type, message, details]
end

def deconstruct_keys(_)
{ type: type, message: message, details: details }
end
def deconstruct = [type, message, details]
def deconstruct_keys(_) = { type:, message:, details: }

private

Expand All @@ -61,64 +57,58 @@ def default_message_for(type)

class State
extend Forwardable
delegate %i([] []= fetch store include? values_at deconstruct_keys) => :@hash

def initialize(operation, values = {})
@hash = operation.context.merge(values)
@result_key = operation.result_key
end

delegate %i([] []= fetch store include? values_at deconstruct_keys) => :@hash

def update(kargs)
@hash.update(kargs)
self
end

def result
@hash[@result_key]
end
def result = @hash[@result_key]
def to_hash = @hash

def to_hash
@hash
end
def use(&bl)
raise ArgumentError, 'a block must be provided' if !block_given?

def unwrap(&bl)
raise 'a block must be provided' if !block_given?
params = bl.parameters
unless params.all? { |(type,_)| [:block, :key, :keyreq, :keyrest].member?(type) }
raise 'only keyword arguments are supported for unwraping'
end

if params.any? {|(type,_)| type == :keyrest }
if !params.all? { _1 in [:block|:key|:keyreq|:keyrest, _] }
raise ArgumentError, 'only keyword arguments are supported'
elsif params in [*, [:keyrest, _], *]
bl.call(**to_hash)
else
keys = params.select {|(type,_)| type == :key || type == :keyreq }.map(&:last)
keys = params.select { _1 in [:key|:keyreq, _] }.map(&:last)
bl.call(**to_hash.slice(*keys))
end
end

alias_method :to_h, :to_hash
alias_method :u, :unwrap
alias_method :u, :use
alias_method :unwrap, :use
end

module Plugins
module Base
module ClassMethods
attr_accessor :result_key
alias :result_at :result_key=

alias_method :result_at, :result_key=

def process(&bl)
dsl = self::DSL
define_method(:call) do |input|
dsl.new(State.new(self, input: input), self)
dsl.new(State.new(self, input:), self)
.run(&bl)
.then(&:result)
end
end

def call(ctx,...)
new(ctx).call(...)
end
def call(ctx,...) = new(ctx).call(...)

def inherited(subclass)
super
Expand All @@ -132,18 +122,16 @@ module InstanceMethods
delegate :result_key => 'self.class'
delegate %i[result success failure] => Result

alias :wrap :result
alias_method :wrap, :result

def call(*)
fail 'must implement at subclass'
end
def call(*) = raise 'must implement at subclass'

def error(type, message: nil, details: nil)
failure(Error.new(type: type, message: message, details: details))
failure(Error.new(type:, message:, details:))
end

def wrap_if_present(value, type: :not_found, message: nil, details: {})
value.nil? ? error(type, message: message, details: details) : success(value)
value.nil? ? error(type, message:, details:) : success(value)
end
end

Expand All @@ -165,60 +153,56 @@ def run(&bl)
# Execute step and preserve the former state
def step(callable,...)
bl = _callable(callable)

@result = @result.tee { |state| bl.call(state,...) }
end

# Execute step and modify the former state setting the key
def set(callable, *args, to: @operation.result_key)
def set(callable, *args, to: @operation.result_key, **kwargs)
bl = _callable(callable)

@result = @result.then do |state|
wrap(bl.call(state, *args))
wrap(bl.call(state, *args, **kwargs))
.then { |value| state.update(to => value) }
end
end

# Execute step and replace the current state completely
def map(callable)
def map(callable,...)
bl = _callable(callable)
@result = @result.then(bl)
@result = @result.then { |state| bl.call(state,...) }
end

def around(wrapper, &steps)
def around(strategy, &nested_block)
@result.then do |state|
seq = -> (dsl = self) { @result = dsl.run(&steps) }
_callable(wrapper).call(seq, state)
runner = ->(dsl = self) { @result = dsl.run(&nested_block) }

_callable(strategy).call(runner, state)
end
end

def if_true(cond, &steps)
def if_true(cond, &nested_block)
cond = _callable(cond)
around(-> seq, state {
seq.call if cond.call(state)
}, &steps)
around(->(runner, state) { runner.call if cond.call(state) }, &nested_block)
end

def if_false(cond, &steps)
def if_false(cond, &nested_block)
cond = _callable(cond)
if_true(-> state { !cond.call(state) }, &steps)
if_true(->(state) { !cond.call(state) }, &nested_block)
end

alias_method :sequence, :around
alias_method :guard, :if_true

private

def wrap(obj)
Result.result(obj)
end
def wrap(obj) = Result.result(obj)

def _callable(callable)
case callable
when Proc
-> *args, **kwargs { @operation.instance_exec(*args, **kwargs, &callable) }
->(*args, **kwargs) { @operation.instance_exec(*args, **kwargs, &callable) }
when Symbol
-> *args, **kwargs { @operation.send(callable, *args, **kwargs) }
->(*args, **kwargs) { @operation.send(callable, *args, **kwargs) }
else
callable
end
Expand Down
2 changes: 1 addition & 1 deletion lib/pathway/plugins/dry_validation/v0_11.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ module InstanceMethods
extend Forwardable

delegate %i[build_form form_options auto_wire_options] => 'self.class'
alias :form :build_form
alias_method :form, :build_form

def validate(state, with: nil)
if auto_wire_options && form_options.any?
Expand Down
2 changes: 1 addition & 1 deletion lib/pathway/plugins/dry_validation/v0_12.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ module InstanceMethods
extend Forwardable

delegate %i[build_form form_options auto_wire_options] => 'self.class'
alias :form :build_form
alias_method :form, :build_form

def validate(state, with: nil)
if auto_wire_options && form_options.any?
Expand Down
2 changes: 1 addition & 1 deletion lib/pathway/plugins/dry_validation/v1_0.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ module InstanceMethods
extend Forwardable

delegate %i[build_contract contract_options auto_wire_options] => 'self.class'
alias :contract :build_contract
alias_method :contract, :build_contract

def validate(state, with: nil)
if auto_wire_options && contract_options.any?
Expand Down
22 changes: 11 additions & 11 deletions lib/pathway/plugins/sequel_models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@ module Plugins
module SequelModels
module DSLMethods
def transaction(step_name = nil, &bl)
fail 'must provide a step or a block but not both' if !step_name.nil? == block_given?
raise 'must provide a step or a block but not both' if !step_name.nil? == block_given?

if step_name
transaction { step step_name }
else
around(-> steps, _ {
around(->(runner, _) {
db.transaction(savepoint: true) do
raise Sequel::Rollback if steps.call.failure?
raise Sequel::Rollback if runner.call.failure?
end
}, &bl)
end
end

def after_commit(step_name = nil, &bl)
fail 'must provide a step or a block but not both' if !step_name.nil? == block_given?
raise 'must provide a step or a block but not both' if !step_name.nil? == block_given?

if step_name
after_commit { step step_name }
else
around(-> steps, state {
dsl = self.class::DSL.new(State.new(self, state.to_h.dup), self)
around(->(runner, state) {
dsl_copy = self.class::DSL.new(State.new(self, state.to_h.dup), self)

db.after_commit do
steps.call(dsl)
runner.call(dsl_copy)
end
}, &bl)
end
Expand All @@ -41,10 +41,10 @@ module ClassMethods
attr_accessor :model_class, :search_field, :model_not_found

def model(model_class, search_by: model_class.primary_key, set_result_key: true, set_context_param: true, error_message: nil)
self.model_class = model_class
self.search_field = search_by
self.result_key = Inflector.underscore(Inflector.demodulize(model_class.name)).to_sym if set_result_key
self.model_not_found = error_message || "#{Inflector.humanize(Inflector.underscore(Inflector.demodulize(model_class.name)))} not found".freeze
self.model_class = model_class
self.search_field = search_by
self.result_key = Inflector.underscore(Inflector.demodulize(model_class.name)).to_sym if set_result_key
self.model_not_found = error_message || "#{Inflector.humanize(Inflector.underscore(Inflector.demodulize(model_class.name)))} not found".freeze

self.context(result_key => Contextualizer::OPTIONAL) if set_result_key && set_context_param
end
Expand Down
4 changes: 1 addition & 3 deletions lib/pathway/plugins/simple_auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ def authorize_with(*objs)
authorized?(*objs) ? wrap(objs) : error(:forbidden)
end

def authorized?(*)
true
end
def authorized?(*) = true
end
end
end
Expand Down
58 changes: 16 additions & 42 deletions lib/pathway/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,8 @@ class Result
attr_reader :value, :error

class Success < Result
def initialize(value)
@value = value
end

def success?
true
end
def initialize(value)= @value = value
def success? = true

def then(bl=nil)
result(block_given? ? yield(value): bl.call(value))
Expand All @@ -25,40 +20,35 @@ def tee(bl=nil, &block)

private

alias :value_for_deconstruct :value
alias_method :value_for_deconstruct, :value
end

class Failure < Result
def initialize(error)
@error = error
end

def success?
false
end

def then(_=nil)
self
end

def tee(_=nil)
self
end
def initialize(error)= @error = error
def success? = false
def then(_=nil) = self
def tee(_=nil) = self

private

alias :value_for_deconstruct :error
alias_method :value_for_deconstruct, :error
end

module Mixin
Success = Result::Success
Failure = Result::Failure
end

def deconstruct
[value_for_deconstruct]
def self.success(value) = Success.new(value)
def self.failure(error) = Failure.new(error)

def self.result(object)
object.is_a?(Result) ? object : success(object)
end

def failure? = !success?
def deconstruct = [value_for_deconstruct]

def deconstruct_keys(keys)
if value_for_deconstruct.respond_to?(:deconstruct_keys)
value_for_deconstruct.deconstruct_keys(keys)
Expand All @@ -67,22 +57,6 @@ def deconstruct_keys(keys)
end
end

def failure?
!success?
end

def self.success(value)
Success.new(value)
end

def self.failure(error)
Failure.new(error)
end

def self.result(object)
object.is_a?(Result) ? object : success(object)
end

delegate :result => 'self.class'
end
end
Loading

0 comments on commit 3e04115

Please sign in to comment.