Module: Plumb::Composable
- Includes:
- Callable
- Included in:
- And, AnyClass, ArrayClass, Build, Node, Deferred, HashClass, HashMap, HashMap::FilteredHashMap, InterfaceClass, MatchClass, Metadata, Not, Or, Pipeline, Pipeline::AroundStep, Policy, Schema, StaticClass, Step, StreamClass, TaggedHash, Transform, TupleClass, Types::Data, ValueClass
- Defined in:
- lib/plumb/composable.rb
Overview
Composable mixes in composition methods to classes. such as #>>, #|, #not, and others. Any Composable class can participate in Plumb compositions. A host object only needs to implement the Step interface ‘call(Result::Valid) => Result::Valid | Result::Invalid`
Defined Under Namespace
Classes: Node
Class Method Summary collapse
-
.included(base) ⇒ Object
This only runs when including Composable, not extending classes with it.
-
.wrap(callable) ⇒ Composable
Wrap an object in a Composable instance.
Instance Method Summary collapse
-
#>>(other) ⇒ And
Chain two composable objects together.
- #[](val) ⇒ Object
-
#as_node(node_name, metadata = BLANK_HASH) ⇒ Node
Wrap a Step in a node with a custom #node_name which is expected by visitors.
-
#build(cns, factory_method = :new, &block) ⇒ And
Compose a step that instantiates a class.
-
#check(errors = 'did not pass the check', &block) ⇒ And
Pass the value through an arbitrary validation.
-
#children ⇒ Array<Composable>
Visitors expect a #node_name and #children interface.
-
#defer(definition = nil, &block) ⇒ Object
A helper to wrap a block in a Step that will defer execution.
-
#generate(generator = nil, &block) ⇒ And
Return the output of a block or #call interface, regardless of input.
-
#invalid(errors: nil) ⇒ Not
Like #not, but with a custom error message.
-
#invoke(*args, &block) ⇒ Step
Build a step that will invoke one or more methods on the value.
-
#match(*args) ⇒ And
Alias of ‘#[]` Match a value using `#===`.
-
#metadata(data = Undefined) ⇒ Hash, And
Return a new Step with added metadata, or build step metadata if no argument is provided.
-
#not(other = self) ⇒ Not
Negate the result of a step.
-
#pipeline(&block) ⇒ Pipeline
Build a Plumb::Pipeline with this object as the starting step.
-
#policy(*args, &blk) ⇒ Step
Register a policy for this step.
-
#static(value) ⇒ And
Always return a static value, regardless of the input.
- #to_json_schema(root: false) ⇒ Hash
- #to_s ⇒ Object
-
#transform(target_type, callable = nil, &block) ⇒ And
Transform value.
-
#value(val) ⇒ Object
Match a value using ‘#==` Normally you’ll build matchers via “#[]‘, which uses `#===`.
-
#|(other) ⇒ Or
Chain two composable objects together as a disjunction (“or”).
Methods included from Callable
Class Method Details
.included(base) ⇒ Object
This only runs when including Composable, not extending classes with it.
116 117 118 119 |
# File 'lib/plumb/composable.rb', line 116 def self.included(base) base.send(:include, Naming) base.send(:include, Equality) end |
.wrap(callable) ⇒ Composable
Wrap an object in a Composable instance. Anything that includes Composable is a noop. A Hash is assumed to be a HashClass schema. An Array with zero or 1 element is assumed to be an ArrayClass. Any ‘#call(Result) => Result` interface is wrapped in a Step. Anything else is assumed to be something you want to match against via `#===`.
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/plumb/composable.rb', line 135 def self.wrap(callable) if callable.is_a?(Composable) callable elsif callable.is_a?(::Hash) HashClass.new(schema: callable) elsif callable.is_a?(::Array) element_type = case callable.size when 0 Types::Any when 1 callable.first else raise ArgumentError, '[element_type] syntax allows a single element type' end Types::Array[element_type] elsif callable.respond_to?(:call) Step.new(callable) else MatchClass.new(callable) end end |
Instance Method Details
#>>(other) ⇒ And
Chain two composable objects together. A.K.A “and” or “sequence”
175 176 177 |
# File 'lib/plumb/composable.rb', line 175 def >>(other) And.new(self, Composable.wrap(other)) end |
#[](val) ⇒ Object
270 |
# File 'lib/plumb/composable.rb', line 270 def [](val) = match(val) |
#as_node(node_name, metadata = BLANK_HASH) ⇒ Node
Wrap a Step in a node with a custom #node_name which is expected by visitors. So that we can define special visitors for certain compositions. Ex. Types::Boolean is a compoition of Types::True | Types::False, but we want to treat it as a single node.
296 297 298 |
# File 'lib/plumb/composable.rb', line 296 def as_node(node_name, = BLANK_HASH) Node.new(node_name, self, ) end |
#build(cns, factory_method = :new, &block) ⇒ And
345 346 347 |
# File 'lib/plumb/composable.rb', line 345 def build(cns, factory_method = :new, &block) self >> Build.new(cns, factory_method:, &block) end |
#check(errors = 'did not pass the check', &block) ⇒ And
Pass the value through an arbitrary validation
206 207 208 |
# File 'lib/plumb/composable.rb', line 206 def check(errors = 'did not pass the check', &block) self >> MatchClass.new(block, error: errors, label: errors) end |
#children ⇒ Array<Composable>
Visitors expect a #node_name and #children interface.
330 |
# File 'lib/plumb/composable.rb', line 330 def children = BLANK_ARRAY |
#defer(definition = nil, &block) ⇒ Object
A helper to wrap a block in a Step that will defer execution. This so that types can be used recursively in compositions.
164 165 166 |
# File 'lib/plumb/composable.rb', line 164 def defer(definition = nil, &block) Deferred.new(definition || block) end |
#generate(generator = nil, &block) ⇒ And
Return the output of a block or #call interface, regardless of input. The block will be called to get the value, on every invocation.
376 377 378 379 380 381 |
# File 'lib/plumb/composable.rb', line 376 def generate(generator = nil, &block) generator ||= block raise ArgumentError, 'expected a generator' unless generator.respond_to?(:call) Step.new(->(r) { r.valid(generator.call) }, 'generator') >> self end |
#invalid(errors: nil) ⇒ Not
Like #not, but with a custom error message.
241 242 243 |
# File 'lib/plumb/composable.rb', line 241 def invalid(errors: nil) Not.new(self, errors:) end |
#invoke(*args, &block) ⇒ Step
Build a step that will invoke one or more methods on the value. Ex 1: Types::String.invoke(:downcase) Ex 2: Types::Array.invoke(:[], 1) Ex 3 chain of methods: Types::String.invoke([:downcase, :to_sym])
411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'lib/plumb/composable.rb', line 411 def invoke(*args, &block) case args in [::Symbol => method_name, *rest] self >> Step.new( ->(result) { result.valid(result.value.public_send(method_name, *rest, &block)) }, [method_name.inspect, rest.inspect].join(' ') ) in [Array => methods] if methods.all? { |m| m.is_a?(Symbol) } methods.reduce(self) { |step, method| step.invoke(method) } else raise ArgumentError, "expected a symbol or array of symbols, got #{args.inspect}" end end |
#match(*args) ⇒ And
Alias of ‘#[]` Match a value using `#===`
266 267 268 |
# File 'lib/plumb/composable.rb', line 266 def match(*args) self >> MatchClass.new(*args) end |
#metadata(data = Undefined) ⇒ Hash, And
Return a new Step with added metadata, or build step metadata if no argument is provided.
217 218 219 220 221 222 223 |
# File 'lib/plumb/composable.rb', line 217 def (data = Undefined) if data == Undefined MetadataVisitor.call(self) else self >> Metadata.new(data) end end |
#not(other = self) ⇒ Not
Negate the result of a step. Ie. if the step is valid, it will be invalid, and vice versa.
233 234 235 |
# File 'lib/plumb/composable.rb', line 233 def not(other = self) Not.new(other) end |
#pipeline(&block) ⇒ Pipeline
Build a Plumb::Pipeline with this object as the starting step. end
392 393 394 |
# File 'lib/plumb/composable.rb', line 392 def pipeline(&block) Pipeline.new(type: self, &block) end |
#policy(*args, &blk) ⇒ Step
Register a policy for this step. Mode 1.a: #policy(:name, arg) a single policy with an argument Mode 1.b: #policy(:name) a single policy without an argument Mode 2: #policy(p1: value, p2: value) multiple policies with arguments The latter mode will be expanded to multiple #policy calls.
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/plumb/composable.rb', line 306 def policy(*args, &blk) case args in [::Symbol => name, *rest] # #policy(:name, arg) types = Array([:type]).uniq = [self] arg = Undefined if rest.size.positive? << rest.first arg = rest.first end block = Plumb.policies.get(types, name) pol = block.call(*, &blk) Policy.new(name, arg, pol) in [::Hash => opts] # #policy(p1: value, p2: value) opts.reduce(self) { |step, (name, value)| step.policy(name, value) } else raise ArgumentError, "expected a symbol or hash, got #{args.inspect}" end end |
#static(value) ⇒ And
Always return a static value, regardless of the input.
358 359 360 361 362 363 364 365 366 |
# File 'lib/plumb/composable.rb', line 358 def static(value) my_type = Array([:type]).first unless my_type.nil? || value.instance_of?(my_type) raise ArgumentError, "can't set a static #{value.class} value for a #{my_type} step" end StaticClass.new(value) >> self end |
#to_json_schema(root: false) ⇒ Hash
402 403 404 |
# File 'lib/plumb/composable.rb', line 402 def to_json_schema(root: false) JSONSchemaVisitor.call(self, root:) end |
#to_s ⇒ Object
396 397 398 |
# File 'lib/plumb/composable.rb', line 396 def to_s inspect end |
#transform(target_type, callable = nil, &block) ⇒ And
Transform value. Requires specifying the resulting type of the value after transformation.
195 196 197 |
# File 'lib/plumb/composable.rb', line 195 def transform(target_type, callable = nil, &block) self >> Transform.new(target_type, callable || block) end |
#value(val) ⇒ Object
Match a value using ‘#==` Normally you’ll build matchers via “#[]‘, which uses `#===`. Use this if you want to match against concrete instances of things that respond to `#===`
255 256 257 |
# File 'lib/plumb/composable.rb', line 255 def value(val) self >> ValueClass.new(val) end |
#|(other) ⇒ Or
Chain two composable objects together as a disjunction (“or”).
183 184 185 |
# File 'lib/plumb/composable.rb', line 183 def |(other) Or.new(self, Composable.wrap(other)) end |