Class: Accessory::Lens
- Inherits:
-
Object
- Object
- Accessory::Lens
- Includes:
- Access::FluentHelpers
- Defined in:
- lib/accessory/lens.rb,
lib/accessory/access.rb,
lib/accessory/bound_lens.rb
Overview
A Lens is a “free-floating” lens (i.e. not bound to a subject document.) It serves as a container for Accessor instances, and represents the traversal path one would take to get from a hypothetical subject document, to a data value nested somewhere within it.
A Lens can be used directly to traverse documents, using #get_in, #put_in, #pop_in, etc. These methods take an explicit subject document to traverse, rather than requiring that the Lens be bound to a document first.
As such, a Lens is reusable. A common use of a Lens is to access the same deeply-nested traversal position within a large collection of subject documents, e.g.:
= Lens[:foo, :bar, :baz]
docs.map{ |doc| .get_in(doc) }
A Lens can also be bound to a specific subject document to create a BoundLens. See BoundLens.on.
Lenses are created frozen. Methods that “extend” a Lens actually create and return new derived Lenses.
Class Method Summary collapse
-
.[](*accessors) ⇒ Lens
Returns a Lens containing the specified
accessors. -
.empty ⇒ Lens
Returns the empty (identity) Lens.
Instance Method Summary collapse
-
#+(other) ⇒ Lens
(also: #/)
Returns a new Lens resulting from concatenating
otherto the end of the receiver. -
#get_and_update_in(subject, &mutator_fn) ⇒ Array
Traverses
subjectusing the chain of accessors held in this Lens, modifying the final value at the end of the traversal chain using the passedmutator_fn, and returning the original targeted value(s) pre-modification. -
#get_in(subject) ⇒ Object
Traverses
subjectusing the chain of accessors held in this Lens, returning the discovered value. -
#on(subject) ⇒ BoundLens
Returns a new BoundLens wrapping this Lens, bound to the specified
subject. -
#pop_in(subject) ⇒ Object
Traverses
subjectusing the chain of accessors held in this Lens, removing the final value at the end of the traversal chain from its position within its parent container. -
#put_in(subject, new_value) ⇒ Object
Traverses
subjectusing the chain of accessors held in this Lens, replacing the final value at the end of the traversal chain withnew_value. -
#then(accessor) ⇒ Lens
Returns a new Lens resulting from appending
accessorto the receiver. -
#update_in(subject, &new_value_fn) ⇒ Array
Traverses
subjectusing the chain of accessors held in this Lens, replacing the final value at the end of the traversal chain with the result from the passednew_value_fn.
Methods included from Access::FluentHelpers
#[], #after_last, #all, #attr, #before_first, #between_each, #betwixt, #filter, #first, #ivar, #last, #subscript
Class Method Details
.[](*accessors) ⇒ Lens
Returns a Accessory::Lens containing the specified accessors.
39 40 41 |
# File 'lib/accessory/lens.rb', line 39 def self.[](*accessors) new(accessors).freeze end |
.empty ⇒ Lens
Returns the empty (identity) Lens.
33 34 35 |
# File 'lib/accessory/lens.rb', line 33 def self.empty @empty_lens ||= (new([]).freeze) end |
Instance Method Details
#+(other) ⇒ Lens Also known as: /
Returns a new Accessory::Lens resulting from concatenating other to the end of the receiver.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/accessory/lens.rb', line 90 def +(other) parts = case other when Accessory::Lens other.to_a when Array other else [other] end d = self.dup d.instance_eval do for part in parts append_accessor!(part) end end d.freeze end |
#get_and_update_in(subject, &mutator_fn) ⇒ Array
Traverses subject using the chain of accessors held in this Lens, modifying the final value at the end of the traversal chain using the passed mutator_fn, and returning the original targeted value(s) pre-modification.
mutator_fn must return one of two data “shapes”:
-
a two-element
Array, representing:-
the value to surface as the “get” value of the traversal
-
the new value to replace at the traversal-position
-
-
the Symbol
:pop— which will remove the value from its parent, and return it as-is.
Equivalent in Elixir: Kernel.get_and_update_in/3
146 147 148 149 150 151 152 |
# File 'lib/accessory/lens.rb', line 146 def get_and_update_in(subject, &mutator_fn) if @parts.empty? subject else get_and_update_in_step(subject, @parts, mutator_fn) end end |
#get_in(subject) ⇒ Object
Traverses subject using the chain of accessors held in this Lens, returning the discovered value.
Equivalent in Elixir: Kernel.get_in/2
118 119 120 121 122 123 124 |
# File 'lib/accessory/lens.rb', line 118 def get_in(subject) if @parts.empty? subject else get_in_step(subject, @parts) end end |
#on(subject) ⇒ BoundLens
Returns a new BoundLens wrapping this Lens, bound to the specified subject.
136 137 138 |
# File 'lib/accessory/bound_lens.rb', line 136 def on(subject) Accessory::BoundLens.on(subject, self) end |
#pop_in(subject) ⇒ Object
Traverses subject using the chain of accessors held in this Lens, removing the final value at the end of the traversal chain from its position within its parent container.
Equivalent in Elixir: Kernel.pop_in/2
193 194 195 |
# File 'lib/accessory/lens.rb', line 193 def pop_in(subject) self.get_and_update_in(subject){ :pop } end |
#put_in(subject, new_value) ⇒ Object
Traverses subject using the chain of accessors held in this Lens, replacing the final value at the end of the traversal chain with new_value.
Equivalent in Elixir: Kernel.put_in/3
180 181 182 183 |
# File 'lib/accessory/lens.rb', line 180 def put_in(subject, new_value) _, new_data = self.get_and_update_in(subject){ [nil, new_value] } new_data end |
#then(accessor) ⇒ Lens
Returns a new Accessory::Lens resulting from appending accessor to the receiver.
77 78 79 80 81 82 83 84 |
# File 'lib/accessory/lens.rb', line 77 def then(accessor) d = self.dup d.instance_eval do @parts = @parts.dup append_accessor!(accessor) end d.freeze end |
#update_in(subject, &new_value_fn) ⇒ Array
Traverses subject using the chain of accessors held in this Lens, replacing the final value at the end of the traversal chain with the result from the passed new_value_fn.
Equivalent in Elixir: Kernel.update_in/3
166 167 168 169 |
# File 'lib/accessory/lens.rb', line 166 def update_in(subject, &new_value_fn) _, new_data = self.get_and_update_in(subject){ |v| [nil, new_value_fn.call(v)] } new_data end |