Class: Accessory::Accessor
- Inherits:
-
Object
- Object
- Accessory::Accessor
- Defined in:
- lib/accessory/accessor.rb
Overview
The parent class for accessors. Contains some shared behavior all accessors can rely on.
It doesn’t make sense to instantiate this class directly. Instantiate specific Accessor subclasses instead.
Implementing an Accessor
To implement an Accessor subclass, you must define at minimum these two methods (see the method docs of these methods for details):
You may also implement these two methods (again, see method docs for more info):
Direct Known Subclasses
Accessory::Accessors::AllAccessor, Accessory::Accessors::AttributeAccessor, Accessory::Accessors::BetweenEachAccessor, Accessory::Accessors::BetwixtAccessor, Accessory::Accessors::FilterAccessor, Accessory::Accessors::FirstAccessor, Accessory::Accessors::InstanceVariableAccessor, Accessory::Accessors::LastAccessor, Accessory::Accessors::SubscriptAccessor
Helpers collapse
-
#traverse_or_default(data) ⇒ Object
Safely traverses
data
, with useful defaults; simplifies implementations of #get and #get_and_update.
Callbacks collapse
-
#ensure_valid(traversal_result) ⇒ Object
Ensures that the predecessor accessor’s traversal result is one this accessor can operate on; called by #traverse_or_default.
-
#traverse(data) ⇒ Object
Traverses
data
; called by #traverse_or_default.
Instance Method Summary collapse
-
#get(data, &succ) ⇒ Object
Traverses
data
in some way, and feeds the result of the traversal to the next step in the accessor chain by yielding the traversal-result to the passed-in blocksucc
. -
#get_and_update(data, &succ) ⇒ Object
Traverses
data
in some way, and feeds the result of the traversal to the next step in the accessor chain by yielding the traversal-result to the passed-in blocksucc
.
Instance Method Details
#ensure_valid(traversal_result) ⇒ Object
Ensures that the predecessor accessor’s traversal result is one this accessor can operate on; called by #traverse_or_default.
This callback should validate that the traversal_result
is one that can be traversed by the traversal-method of this accessor. If it can, the traversal_result
should be returned unchanged. If it cannot, an object that can be traversed should be returned instead.
For example, if your accessor operates on Enumerable
values (like Accessory::Accessors::AllAccessor), then this method should validate that the traversal_result
is Enumerable
; and, if it isn’t, return something that is — e.g. an empty Array
.
This logic is used to replace invalid intermediate values (e.g. ‘nil`s and scalars) with containers during Lens#put_in et al.
207 208 209 210 211 |
# File 'lib/accessory/accessor.rb', line 207 def ensure_valid(traversal_result) lambda do raise NotImplementedError, "Accessor subclass #{self.class} must implement #ensure_valid to allow chain-predecessor to use #traverse_or_default" end end |
#get(data, &succ) ⇒ Object
Traverses data
in some way, and feeds the result of the traversal to the next step in the accessor chain by yielding the traversal-result to the passed-in block succ
. The result from the yield is the result coming from the end of the accessor chain. Usually, it should be returned as-is.
succ
can be yielded to multiple times, to run the rest of the accessor chain against multiple parts of data
. In this case, the yield results should be gathered up into some container object to be returned together.
The successor accessor will receive the yielded element as its data
.
After returning, the predecessor accessor will receive the result returned from #get as the result of its own yield
.
119 120 121 |
# File 'lib/accessory/accessor.rb', line 119 def get(data, &succ) raise NotImplementedError, "Accessor subclass #{self.class} must implement #get" end |
#get_and_update(data, &succ) ⇒ Object
Traverses data
in some way, and feeds the result of the traversal to the next step in the accessor chain by yielding the traversal-result to the passed-in block succ
.
The result of the yield will be a “modification command”, one of these two:
-
an *update command*
[get_result, new_value]
-
the symbol
:pop
In the *update command* case:
-
the
get_result
should be returned as-is (or gathered together into a container object if this accessor yields multiple times.) The data flow of the get_results should replicate the data flow of #get. -
the
new_value
should be used to replace or overwrite the result of this accessor’s traversal withindata
. For example, in Accessory::Accessors::SubscriptAccessor,data[key] = new_value
is executed.
In the :pop
command case:
-
the result of the traversal (before the yield) should be returned. This implies that any #get_and_update implementation must capture its traversal-results before feeding them into yield, in order to return them here.
-
the traversal-result should be removed from
data
. For example, in Accessory::Accessors::SubscriptAccessor,data.delete(key)
is executed.
The successor in the accessor chain will receive the yielded traversal-results as its own data
.
After returning, the predecessor accessor will receive the result returned from this method as the result of its own yield
. This implies that this method should almost always be implemented to return an update command, looking like one of the following:
# given [get_result, new_value]
[get_result, data_after_update]
# given :pop
[traversal_result, data_after_removal]
167 168 169 |
# File 'lib/accessory/accessor.rb', line 167 def get_and_update(data, &succ) raise NotImplementedError, "Accessor subclass #{self.class} must implement #get_and_update" end |
#traverse(data) ⇒ Object
Traverses data
; called by #traverse_or_default.
This method should traverse data
however your accessor does that, producing either one traversal-result or a container-object of gathered traversal-results.
This method can assume that data
is a valid receiver for the traversal it performs. #traverse_or_default takes care of feeding in a default data
in the case where the predecessor passed invalid data.
185 186 187 |
# File 'lib/accessory/accessor.rb', line 185 def traverse(data) raise NotImplementedError, "Accessor subclass #{self.class} must implement #traverse to use #traverse_or_default" end |
#traverse_or_default(data) ⇒ Object
Safely traverses data
, with useful defaults; simplifies implementations of #get and #get_and_update.
Rather than writing redundant traversal logic into both methods, you can implement the callback method #traverse to define your traversal, and then call traverse_or_default(data)
within your implementation to safely get a traversal-result to operate on.
The value returned by your #traverse callback will be validated by your calling the #ensure_valid callback of the _successor accessor_ in the Lens path. #ensure_valid will replace any value it considers invalid with a reasonable default. This means you don’t need to worry about what will happen if your traversal doesn’t find a value and so returns nil
. The successor’s #ensure_valid will replace that nil
with a value that works for it.
88 89 90 91 92 93 94 95 96 |
# File 'lib/accessory/accessor.rb', line 88 def traverse_or_default(data) traversal_result = traverse(data) || @default_value if @successor @successor.ensure_valid(traversal_result) else traversal_result end end |