Converting one ruby object into another with some rules.
Installation
Add this line to your application's Gemfile:
gem 'yaoc'
And then execute:
$ bundle
Or install it yourself as:
$ gem install yaoc
Usage
What does it do in a high level?
You say transform an object of class A into an object of class B:
mapper =Yaoc::ObjectMapper.new(B, A) # yes, first what you want, then where to get it
Then you define some rules how it should be done:
mapper.add_mapping do
fetcher :public_send
strategy :to_hash_mapping # default or :to_array_mapping
rule to: :role, from: :r_role
rule to: :firstname,
from: :fullname,
converter: ->(source, result){ Yaoc::TransformationCommand.fill_result_with_value(result, :firstname, source.fullname.split().first) },
reverse_converter: ->(source, result){ Yaoc::TransformationCommand.fill_result_with_value(result, :fullname, "#{source.firstname} #{source.lastname}") }
rule to: :lastname,
from: :fullname,
converter: ->(source, result){ Yaoc::TransformationCommand.fill_result_with_value(result, :lastname, source.fullname.split().last ) },
reverse_converter: ->(source, result){ result }
rule to: :id
end
After this you can transform an object of class A into an object of class B:
mapper.load(A.new)
Or reverse:
mapper.dump(B.new)
Depending on strategy
or reverse_strategy
the input object is first
transformed into a Hash or Array and after this passed to the class constructor.
You can also pass an existing object to load and dump:
b = B.new
mapper.load(A.new, b)
a = A.new
mapper.dump(B.new, a)
In this case setters with matching name to the hash key are invoked with the hash value as value.
The resulting classes have hash enabled constructors?
require 'yaoc'
include Yaoc::Helper
User = StructHE(:id, :firstname, :lastname, :role)
OldUser = StructHE(:id, :fullname, :r_role)
mapper = Yaoc::ObjectMapper.new(User, OldUser).tap do |mapper|
mapper.add_mapping do
fetcher :public_send
rule to: :role, from: :r_role
rule to: :firstname,
from: :fullname,
converter: ->(source, result){ Yaoc::TransformationCommand.fill_result_with_value(result, :firstname, source.fullname.split().first) },
reverse_converter: ->(source, result){ Yaoc::TransformationCommand.fill_result_with_value(result, :fullname, "#{source.firstname} #{source.lastname}") }
rule to: :lastname,
from: :fullname,
converter: ->(source, result){ Yaoc::TransformationCommand.fill_result_with_value(result, :lastname, source.fullname.split().last ) },
reverse_converter: ->(source, result){ result }
rule to: :id
end
end
old_user = OldUser.new({id: 1, fullname: "myfirst mysecond", r_role: "admin" })
new_user = mapper.load(old_user)
puts old_user
puts new_user
new_user.firstname = "no"
new_user.lastname = "name"
puts mapper.dump(new_user)
#<struct OldUser id=1, fullname="myfirst mysecond", r_role="admin">
#<struct User id=1, firstname="myfirst", lastname="mysecond", role="admin">
#<struct OldUser id=1, fullname="no name", r_role="admin">
The resulting classes have no hash enabled constructor?
require 'yaoc'
include Yaoc::Helper
OldUser2 = Struct.new(:id, :fullname, :r_role)
User2 = Struct.new(:id, :firstname, :lastname, :role)
reverse_source = ->(attrs){
OldUser2.new.tap do |old_user|
attrs.each_pair do |key, value|
old_user.public_send "#{key}=", value
end
end
}
source = ->(attrs){
User2.new.tap do |old_user|
attrs.each_pair do |key, value|
old_user.public_send "#{key}=", value
end
end
}
mapper = Yaoc::ObjectMapper.new(source, reverse_source).tap do |mapper|
mapper.add_mapping do
fetcher :public_send
rule to: :role, from: :r_role
rule to: :firstname,
from: :fullname,
converter: ->(source, result){ Yaoc::TransformationCommand.fill_result_with_value(result, :firstname, source.fullname.split().first ) },
reverse_converter: ->(source, result){ Yaoc::TransformationCommand.fill_result_with_value(result, :fullname, "#{source.firstname} #{source.lastname}") }
rule to: :lastname,
from: :fullname,
converter: ->(source, result){ Yaoc::TransformationCommand.fill_result_with_value(result, :lastname, source.fullname.split().last) },
reverse_converter: ->(source, result){ result }
rule to: :id
end
end
old_user2 = OldUser2.new(1, "myfirst mysecond", "admin" )
new_user2 = mapper.load(old_user2)
puts old_user2
puts new_user2
new_user2.firstname = "no"
new_user2.lastname = "name"
puts mapper.dump(new_user2)
#<struct OldUser2 id=1, fullname="myfirst mysecond", r_role="admin">
#<struct User2 id=1, firstname="myfirst", lastname="mysecond", role="admin">
#<struct OldUser2 id=1, fullname="no name", r_role="admin">
But my classes have positional constructor, what now?
require 'yaoc'
include Yaoc::Helper
puts "\n" * 5
OldUser3 = Struct.new(:id, :fullname, :r_role)
User3 = Struct.new(:id, :firstname, :lastname, :role)
# alternative to proc for converter
converter = Yaoc::TransformationCommand.create(to: 1,
from: :fullname,
deferred: false,
fetcher_proc: ->(source, fetcher, from){source.fullname.split().first} )
reverse_converter = Yaoc::TransformationCommand.create(to: 1,
from: :first_and_lastname,
deferred: false,
fetcher_proc: ->(source, fetcher, from){ "#{source.firstname} #{source.lastname}"} )
mapper = Yaoc::ObjectMapper.new(User3, OldUser3).tap do |mapper|
mapper.add_mapping do
fetcher :public_send
strategy :to_array_mapping
reverse_strategy :to_array_mapping
rule to: 0, from: :id,
reverse_to: 0, reverse_from: :id
rule to: 1,
from: :fullname,
converter: converter,
reverse_converter: reverse_converter
rule to: 2,
from: :fullname,
converter: ->(source, result){ result[2] = source.fullname.split().last },
reverse_converter: ->(source, result){ result }
rule to: 3, from: :r_role,
reverse_to: 2, reverse_from: :role
end
end
old_user3 = OldUser3.new(1, "myfirst mysecond", "admin" )
new_user3 = mapper.load(old_user3)
puts old_user3
puts new_user3
new_user3.firstname = "no"
new_user3.lastname = "name"
puts mapper.dump(new_user3)
#<struct OldUser3 id=1, fullname="myfirst mysecond", r_role="admin">
#<struct User3 id=1, firstname="myfirst", lastname="mysecond", role="admin">
#<struct OldUser3 id=1, fullname="no name", r_role="admin">
And how to use it with compositions?
require 'yaoc'
include Yaoc::Helper
puts "\n" * 5
User4 = StructHE(:id, :firstname, :lastname, :roles)
OldUser4 = StructHE(:o_id, :o_firstname, :o_lastname, :o_roles)
Role = StructHE(:id, :name)
OldRole = StructHE(:o_id, :o_name)
Yaoc::ObjectMapper.new(Role, OldRole).tap do |mapper|
mapper.add_mapping do
register_as :role_mapper
fetcher :public_send
rule to: :id, from: :o_id
rule to: :name, from: :o_name
end
end
user_mapper = Yaoc::ObjectMapper.new(User4, OldUser4).tap do |mapper|
mapper.add_mapping do
fetcher :public_send
rule to: [:id, :firstname, :lastname],
from: [:o_id, :o_firstname, :o_lastname]
rule to: :roles,
from: :o_roles,
object_converter: :role_mapper,
is_collection: true
end
end
old_user4 = OldUser4.new(o_id: 1,
o_firstname: "firstname",
o_lastname:"lastname",
o_roles: [OldRole.new(o_id: 1, o_name: "admin"), OldRole.new(o_id: 2, o_name: "guest")] )
new_user4 = user_mapper.load(old_user4)
puts old_user4
puts new_user4
puts user_mapper.dump(new_user4)
#<struct OldUser4 o_id=1, o_firstname="firstname", o_lastname="lastname",
# o_roles=[#<struct OldRole o_id=1, o_name="admin">, #<struct OldRole o_id=2, o_name="guest">]>
#<struct User4 id=1, firstname="firstname", lastname="lastname",
# roles=[#<struct Role id=1, name="admin">, #<struct Role id=2, name="guest">]>
#<struct OldUser4 o_id=1, o_firstname="firstname", o_lastname="lastname",
# o_roles=[#<struct OldRole o_id=1, o_name="admin">, #<struct OldRole o_id=2, o_name="guest">]>
And how can I add values to existing objects?
require 'yaoc'
include Yaoc::Helper
puts "\n" * 5
OldUser5 = StructHE(:id, :name)
RoleThing = StructHE(:id, :role)
User5 = StructHE(:id, :name, :role)
user_mapper = Yaoc::ObjectMapper.new(User5, OldUser5).tap do |mapper|
mapper.add_mapping do
fetcher :public_send
rule to: [:id, :name]
end
end
role_mapper = Yaoc::ObjectMapper.new(User5, RoleThing).tap do |mapper|
mapper.add_mapping do
fetcher :public_send
rule to: [:role]
end
end
old_role = RoleThing.new(id: 1, role: "my_role")
old_user5 = OldUser5.new(id: 1, name: "my fullname")
new_user5 = user_mapper.load(old_user5)
role_mapper.load(old_role, new_user5)
# OR
#
# mapper_chain = Yaoc::ManyToOneMapperChain.new(user_mapper, role_mapper)
# new_user5 = mapper_chain.load_all([old_user5, old_role])
puts old_user5
puts old_role
puts new_user5
#<struct OldUser5 id=1, name="my fullname">
#<struct RoleThing id=1, role="my_role">
#<struct User5 id=1, name="my fullname", role="my_role">
How can I lazy load some expensive to convert attributes?
require 'yaoc'
include Yaoc::Helper
puts "\n" * 5
OldUser6 = StructHE(:id) do
def names=(new_names)
@names = new_names
end
def names
puts 'some expensive operation in progress ...'
sleep 10
@names
end
end
User6 = StructHE(:id, :names)
user_mapper = Yaoc::ObjectMapper.new(User6, OldUser6).tap do |mapper|
mapper.add_mapping do
fetcher :public_send
rule to: [:id, :names],
lazy_loading: [false, true]
end
end
old_user6 = OldUser6.new(id: 'my_id_1', names: ['one', 'two', 'three', 'four'])
new_user6 = user_mapper.load(old_user6)
puts new_user6.id.inspect
puts new_user6.names.inspect
puts new_user6
puts "\n" * 5
# "my_id_1"
# some expensive operation in progress ...
# ["one", "two", "three", "four"]
#<struct User6 id="my_id_1", names=["one", "two", "three", "four"]>
Contributing
- Fork it ( http://github.com/slowjack2k/yaoc/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request