Skip to content
Transform Ruby objects in functional style
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
lib Bump version 1.0.3 Dec 1, 2018
rakelib Clean rubocop warnings May 21, 2015
spec
.gitignore Add /vendor/ to .gitignore Mar 24, 2015
.rspec Avoid circular require and enable warnings Jun 1, 2015
.rubocop.yml RegexpLiteral/MaxSlashes option is no longer supported May 21, 2015
.rubocop_todo.yml rubocop Apr 16, 2015
.travis.yml Update ruby versions Dec 1, 2018
CHANGELOG.md Update CHANGELOG [skip ci] Dec 1, 2018
Gemfile Mutant requires Ruby 2.5+ Dec 1, 2018
Guardfile rubocop Apr 16, 2015
LICENSE.txt
README.md Remove link to Gemnasium [skip ci] Dec 1, 2018
Rakefile Add gem tasks [skip ci] Dec 1, 2018
transproc.gemspec Set minimal ruby version to 2.3 Dec 1, 2018

README.md

Transproc

Gem Version Build Status Code Climate Test Coverage Inline docs

Transproc is a small library that allows you to compose procs into a functional pipeline using left-to-right function composition.

The approach came from Functional Programming, where simple functions are composed into more complex functions in order to transform some data. It works like |> in Elixir or >> in F#.

transproc provides a mechanism to define and compose transformations, along with a number of built-in transformations.

It's currently used as the data mapping backend in Ruby Object Mapper.

Installation

Add this line to your application's Gemfile:

gem 'transproc'

And then execute:

$ bundle

Or install it yourself as:

$ gem install transproc

Basics

Simple transformations are defined as easy as:

increment = Transproc::Function.new(-> (data) { data + 1 })
increment[1] # => 2

It's easy to compose transformations:

to_string = Transproc::Function.new(:to_s.to_proc)
(increment >> to_string)[1] => '2'

It's easy to pass additional arguments to transformations:

append = Transproc::Function.new(-> (value, suffix) { value + suffix })
append_bar = append.with('_bar')
append_bar['foo'] # => foo_bar

Or even accept another transformation as an argument:

map_array = Transproc::Function.new(-> (array, fn) { array.map(&fn) })
map_array.with(to_string).call([1, 2, 3]) # => ['1', '2', '3']

To improve this low-level definition, you can use class methods with Transproc::Registry:

M = Module.new do
  extend Transproc::Registry

  def self.to_string(value)
    value.to_s
  end

  def self.map_array(array, fn)
    array.map(&fn)
  end
end
M[:map_array, M[:to_string]].([1, 2, 3]) # => ['1', '2', '3']

Built-in transformations

transproc comes with a lot of built-in functions. They come in the form of modules with class methods, which you can import into a registry:

You can import everything with:

module T
  extend Transproc::Registry

  import Transproc::Coercions
  import Transproc::ArrayTransformations
  import Transproc::HashTransformations
  import Transproc::ClassTransformations
  import Transproc::ProcTransformations
  import Transproc::Conditional
  import Transproc::Recursion
end
T[:to_string].call(:abc) # => 'abc'

Or import selectively with:

module T
  extend Transproc::Registry

  import :to_string, from: Transproc::Coercions, as: :stringify
end
T[:stringify].call(:abc) # => 'abc'
T[:to_string].call(:abc)
# => Transproc::FunctionNotFoundError: No registered function T[:to_string]

Transformer

Transformer is a class-level DSL for composing transformation pipelines, for example:

T = Class.new(Transproc::Transformer) do
  map_array do
    symbolize_keys
    rename_keys user_name: :name
    nest :address, [:city, :street, :zipcode]
  end
end

T.new.call(
  [
    { 'user_name' => 'Jane',
      'city' => 'NYC',
      'street' => 'Street 1',
      'zipcode' => '123'
    }
  ]
)
# => [{:name=>"Jane", :address=>{:city=>"NYC", :street=>"Street 1", :zipcode=>"123"}}]

It converts every method call to its corresponding transformation, and joins these transformations into a transformation pipeline (a transproc).

Transproc Example Usage

require 'json'
require 'transproc/all'

# create your own local registry for transformation functions
module Functions
  extend Transproc::Registry
end

# import necessary functions from other transprocs...
module Functions
  # import all singleton methods from a module/class
  import Transproc::HashTransformations
  import Transproc::ArrayTransformations
end

# ...or from any external library
require 'inflecto'
module Functions
  # import only necessary singleton methods from a module/class
  # and rename them locally
  import :camelize, from: Inflecto, as: :camel_case
end

def t(*args)
  Functions[*args]
end

# use imported transformation
transformation = t(:camel_case)

transformation.call 'i_am_a_camel'
# => "IAmACamel"

transformation = t(:map_array, (
  t(:symbolize_keys).>> t(:rename_keys, user_name: :user)
  )).>> t(:wrap, :address, [:city, :street, :zipcode])

transformation.call(
  [
    { 'user_name' => 'Jane',
      'city' => 'NYC',
      'street' => 'Street 1',
      'zipcode' => '123' }
  ]
)
# => [{:user=>"Jane", :address=>{:city=>"NYC", :street=>"Street 1", :zipcode=>"123"}}]

# define your own composable transformation easily
transformation = t(-> v { JSON.dump(v) })

transformation.call(name: 'Jane')
# => "{\"name\":\"Jane\"}"

# ...or add it to registered functions via singleton method of the registry
module Functions
  # ...

  def self.load_json(v)
    JSON.load(v)
  end
end

# ...or add it to registered functions via .register method
Functions.register(:load_json) { |v| JSON.load(v) }

transformation = t(:load_json) >> t(:map_array, t(:symbolize_keys))

transformation.call('[{"name":"Jane"}]')
# => [{ :name => "Jane" }]

Credits

This project is inspired by the work of following people:

Contributing

  1. Fork it ( https://github.com/solnic/transproc/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request
You can’t perform that action at this time.