Skip to content
This repository has been archived by the owner on Oct 6, 2019. It is now read-only.

Include mapper logic into rom-mapper #42

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ source 'https://rubygems.org'
gemspec

group :test do
gem 'rom'
gem 'dry-equalizer'
gem 'dry-struct'
gem 'anima', '~> 0.3.0'
gem 'virtus'
gem 'inflecto', '~> 0.0', '>= 0.0.2'
Expand Down
1 change: 1 addition & 0 deletions lib/rom/mapper.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'dry/core/constants'
require 'rom/mapper/dsl'
require 'rom/mapper/configuration_plugin'

module ROM
# Mapper is a simple object that uses transformers to load relations
Expand Down
36 changes: 36 additions & 0 deletions lib/rom/mapper/builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require 'dry/core/class_builder'

module ROM
class Mapper
# Setup DSL-specific mapper extensions
#
# @private
class Builder
# Generate a mapper subclass
#
# This is used by Setup#mappers DSL
#
# @api private
def self.build_class(name, mapper_registry, options = EMPTY_HASH, &block)
class_name = "ROM::Mapper[#{name}]"

parent = options[:parent]
inherit_header = options.fetch(:inherit_header) { ROM::Mapper.inherit_header }

parent_class =
if parent
mapper_registry.detect { |klass| klass.relation == parent }
else
ROM::Mapper
end

Dry::Core::ClassBuilder.new(name: class_name, parent: parent_class).call do |klass|
klass.relation(name)
klass.inherit_header(inherit_header)

klass.class_eval(&block) if block
end
end
end
end
end
23 changes: 23 additions & 0 deletions lib/rom/mapper/configuration_plugin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require 'rom/mapper/mapper_dsl'

module ROM
class Mapper
# Model DSL allows setting a model class
#
# @private
module ConfigurationPlugin
# Mapper definition DSL used by Setup DSL
#
# @private

def self.apply(configuration, options = {})
configuration.class.class_eval do
def mappers(&block)
register_mapper(*MapperDSL.new(self, mapper_classes, block).mapper_classes)
end
end
configuration
end
end
end
end
43 changes: 43 additions & 0 deletions lib/rom/mapper/mapper_dsl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require 'rom/mapper/builder'

module ROM
class Mapper
# Mapper definition DSL used by Setup DSL
#
# @private
class MapperDSL
attr_reader :configuration, :mapper_classes, :defined_mappers

# @api private
def initialize(configuration, mapper_classes, block)
@configuration = configuration
@mapper_classes = mapper_classes
@defined_mappers = []

instance_exec(&block)

@mapper_classes = @defined_mappers
end

# Define a mapper class
#
# @param [Symbol] name of the mapper
# @param [Hash] options
#
# @return [Class]
#
# @api public
def define(name, options = EMPTY_HASH, &block)
@defined_mappers << Builder.build_class(name, (@mapper_classes + @defined_mappers), options, &block)
self
end

# TODO
#
# @api public
def register(relation, mappers)
configuration.register_mapper(relation => mappers)
end
end
end
end
117 changes: 117 additions & 0 deletions spec/integration/combine_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
require 'spec_helper'

RSpec.describe 'Mapper definition DSL' do
include_context 'container'
include_context 'users and tasks'

describe 'combine' do
before do
configuration.relation(:tasks) do
def for_users(users)
names = users.map { |user| user[:name] }
restrict { |task| names.include?(task[:name]) }
end

def tags(_tasks)
[{ name: 'blue', task: 'be cool' }]
end
end

configuration.relation(:users) do
def addresses(_users)
[{ city: 'NYC', user: 'Jane' }, { city: 'Boston', user: 'Joe' }]
end

def books(_users)
[{ title: 'Book One', user: 'Jane' }, { title: 'Book Two', user: 'Joe' }]
end
end

configuration.mappers do
define(:users) do
register_as :entity

model name: 'Test::User'

attribute :name
attribute :email

combine :tasks, on: { name: :name } do
model name: 'Test::Task'

attribute :title

wrap :meta do
attribute :user, from: :name
attribute :priority
end

combine :tags, on: { title: :task } do
model name: 'Test::Tag'

attribute :name
end
end

combine :address, on: { name: :user }, type: :hash do
model name: 'Test::Address'

attribute :city
end

combine :book, on: { name: :user }, type: :hash
end
end
end

let(:users) { container.relation(:users) }
let(:tasks) { container.relation(:tasks) }

let(:joe) {
Test::User.new(
name: 'Joe',
email: '[email protected]',
tasks: [
Test::Task.new(title: 'be nice', meta: { user: 'Joe', priority: 1 },
tags: []),
Test::Task.new(title: 'sleep well', meta: { user: 'Joe', priority: 2 },
tags: [])
],
address: Test::Address.new(city: 'Boston'),
book: { title: 'Book Two', user: 'Joe' }
)
}

let(:jane) {
Test::User.new(
name: 'Jane',
email: '[email protected]',
tasks: [
Test::Task.new(
title: 'be cool',
meta: { user: 'Jane', priority: 2 },
tags: [Test::Tag.new(name: 'blue')]
)
],
address: Test::Address.new(city: 'NYC'),
book: { title: 'Book One', user: 'Jane' }
)
}

it 'works' do
container

Test::User.send(:include, Dry::Equalizer(:name, :email, :tasks, :address, :book))
Test::Task.send(:include, Dry::Equalizer(:title, :meta))
Test::Address.send(:include, Dry::Equalizer(:city))

result = users.combine(
tasks.for_users.combine(tasks.tags),
users.addresses,
users.books
).as(:entity)

expect(result).to match_array([joe, jane])
end
end
end
43 changes: 43 additions & 0 deletions spec/integration/deep_embedded_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require 'spec_helper'

RSpec.describe 'Mappers / deeply embedded tuples' do
include_context 'container'

it 'allows mapping embedded tuples' do
configuration.relation(:users)

configuration.mappers do
define(:users) do
model name: 'Test::User'

attribute :name, from: 'name'

embedded :tasks, from: 'tasks' do
attribute :title, from: 'title'

embedded :priority, from: 'priority', type: :hash do
attribute :value, from: 'value'
attribute :desc, from: 'desc'
end
end
end
end

container.relations.users << {
'name' => 'Jane',
'tasks' => [
{ 'title' => 'Task One', 'priority' => { 'value' => 1, 'desc' => 'high' } },
{ 'title' => 'Task Two', 'priority' => { 'value' => 3, 'desc' => 'low' } }
]
}

jane = container.relation(:users).as(:users).first

expect(jane.name).to eql('Jane')

expect(jane.tasks).to eql([
{ title: 'Task One', priority: { value: 1, desc: 'high' } },
{ title: 'Task Two', priority: { value: 3, desc: 'low' } }
])
end
end
Loading