
Security News
Nx npm Packages Compromised in Supply Chain Attack Weaponizing AI CLI Tools
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.
Abstract syntax tree (AST) for domain-specific ruby mappers, based on the transproc gem.
No monkey-patching, no mutable instances. 100% mutant-covered.
Add this line to your application's Gemfile:
# Gemfile
gem "abstract_mapper"
Then execute:
bundle
Or add it manually:
gem install abstract_mapper
The gem provides the metalevel of abstraction for defining specific DSL for mappers.
All you need to provide your own mapper DSL is:
AbstractMapper::Branch
and AbstractMapper::Node
Let's suppose we need to provide a DSL for mappers, that can rename keys in array of tuples. The following example represents an oversimplified version of the faceter gem.
Every node should implement the #transproc
method that transforms some input data to the output.
When you need attributes, assign them using virtus method attribute
:
require "abstract_mapper"
module Faceter
# The node to define a transformation of every item of the array from input
class List < AbstractMapper::Branch
# The `List#super` is already composes subnodes. All you need is to define,
# how the list should apply that composition to every item of the list
#
# Here the transformation from the transproc gem is used
def transproc
Transproc::ArrayTransformations[:map_array, super]
end
end
# The node to define a renaming of keys in a tuple
class Rename < AbstractMapper::Node
attribute :keys
def transproc
Transproc::HashTransformations[:rename_keys, keys]
end
end
end
AbstractMapper defines 2 rules AbstractMapper::Rules::Sole
and AbstractMapper::Rules::Pair
. The first one is applicable to every single node to check if it can be optimized by itself, the second one takes two consecutive nodes and either return them unchanged, or merges them into more time-efficient node.
For every rule you need to define two methods:
#optimize?
that defines if the rule is applicable to given node (or pair of nodes)#optimize
that returns either array of changed nodes, or one node, or nothing when the node(s) should be removed from the treeUse #nodes
method to access nodes to be optimized. Base class AbstractMapper::Rules::Sole
also defines the #node
method, while AbstractMapper::Rules::Pair
defines #left
and #right
for the corresponding parts of the pair.
module Faceter
# The empty lists are useless, because they does nothing at all
class RemoveEmptyLists < AbstractMapper::Rules::Sole
def optimize?
node.is_a?(List) && node.empty?
end
def optimize
# returns nothing
end
end
# Two consecutive list branches are not a good solution, because they
# iterates twice via the same array of items in the mapped data.
#
# That's why when we meet two consecutive lists, we have to merge them
# into the one list, containing subnodes (entries) from both sources.
class CompactLists < AbstractMapper::Rules::Pair
def optimize?
nodes.map { |n| n.is_a? List }.reduce(:&)
end
def optimize
List.new { nodes.map(:entries).flatten }
end
end
# Two consecutive renames can be merged
class CompactRenames < AbstractMapper::Rules::Pair
def optimize?
nodes.map { |n| n.is_a? Rename }.reduce(:&)
end
def optimize
Rename.new nodes.map(&:attributes).reduce(:merge)
end
end
end
Now that both the nodes (transformers) and optimization rules are defined, its time to register them for the mapper.
You can coerce command argumets into node attributes. The coercer is expected to return a hash:
module Faceter
class Mapper < AbstractMapper
configure do
command :list, List
# `:foo, to: :bar` becomes `{ keys: { foo: :bar } }`
command :rename, Rename do |name, opts|
{ keys: { name => opts.fetch(:to) } }
end
rule RemoveEmptyLists
rule CompactLists
rule CompactRenames
end
end
end
Now we can create a concrete faceter-based mapper, using its DSL:
require "faceter"
class MyMapper < Faceter::Mapper
list do
rename :foo, to: :bar
end
list do
# this is useless, but we have a rule just for the case
end
list do
rename :baz, to: :qux
end
end
my_mapper = MyMapper.new
my_mapper.call [{ foo: :FOO, baz: :FOO }, { foo: :BAZ, baz: :BAZ }]
# => [{ bar: :FOO, qux: :FOO }, { bar: :BAZ, qux: :BAZ }]
All the rules are applied before initializing my_mapper
, so the AST will be the following:
my_mapper.tree
# => <Root [<List [<Rename(foo: :bar, baz: :qux)>]>]>
The gem defines a collection of conventional shared examples to make RSpec tests simple and verbose.
See the list of available examples and how to use them in a wiki page.
Many thanks to Piotr Solnica and all the rom and transproc contributors for the implementation of the rich data mapper DSL, and for the idea of even more abstract layer.
Tested under rubies compatible to MRI 1.9.3+.
Uses RSpec 3.0+ for testing and hexx-suit for dev/test tools collection.
git checkout -b my-new-feature
)git commit -am '[UPDATE] Add some feature'
)git push origin my-new-feature
)See the MIT LICENSE.
FAQs
Unknown package
We found that abstract_mapper demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.
Security News
CISA’s 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.
Security News
A clarification on our recent research investigating 60 malicious Ruby gems.