rom-mapper 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +1 -1
- data/.travis.yml +19 -13
- data/{Changelog.md → CHANGELOG.md} +6 -0
- data/Gemfile +23 -10
- data/README.md +17 -12
- data/Rakefile +12 -4
- data/lib/rom-mapper.rb +6 -15
- data/lib/rom/header.rb +195 -0
- data/lib/rom/header/attribute.rb +184 -0
- data/lib/rom/mapper.rb +63 -100
- data/lib/rom/mapper/attribute_dsl.rb +477 -0
- data/lib/rom/mapper/dsl.rb +120 -0
- data/lib/rom/mapper/model_dsl.rb +55 -0
- data/lib/rom/mapper/version.rb +3 -7
- data/lib/rom/model_builder.rb +99 -0
- data/lib/rom/processor.rb +28 -0
- data/lib/rom/processor/transproc.rb +388 -0
- data/rakelib/benchmark.rake +15 -0
- data/rakelib/mutant.rake +16 -0
- data/rakelib/rubocop.rake +18 -0
- data/rom-mapper.gemspec +7 -6
- data/spec/spec_helper.rb +32 -33
- data/spec/support/constant_leak_finder.rb +14 -0
- data/spec/support/mutant.rb +10 -0
- data/spec/unit/rom/mapper/dsl_spec.rb +467 -0
- data/spec/unit/rom/mapper_spec.rb +83 -0
- data/spec/unit/rom/processor/transproc_spec.rb +448 -0
- metadata +68 -89
- data/.ruby-version +0 -1
- data/Gemfile.devtools +0 -55
- data/config/devtools.yml +0 -2
- data/config/flay.yml +0 -3
- data/config/flog.yml +0 -2
- data/config/mutant.yml +0 -3
- data/config/reek.yml +0 -103
- data/config/rubocop.yml +0 -45
- data/lib/rom/mapper/attribute.rb +0 -31
- data/lib/rom/mapper/dumper.rb +0 -27
- data/lib/rom/mapper/loader.rb +0 -22
- data/lib/rom/mapper/loader/allocator.rb +0 -32
- data/lib/rom/mapper/loader/attribute_writer.rb +0 -23
- data/lib/rom/mapper/loader/object_builder.rb +0 -28
- data/spec/shared/unit/loader_call.rb +0 -13
- data/spec/shared/unit/loader_identity.rb +0 -13
- data/spec/shared/unit/mapper_context.rb +0 -13
- data/spec/unit/rom/mapper/call_spec.rb +0 -32
- data/spec/unit/rom/mapper/class_methods/build_spec.rb +0 -64
- data/spec/unit/rom/mapper/dump_spec.rb +0 -15
- data/spec/unit/rom/mapper/dumper/call_spec.rb +0 -29
- data/spec/unit/rom/mapper/dumper/identity_spec.rb +0 -28
- data/spec/unit/rom/mapper/header/each_spec.rb +0 -28
- data/spec/unit/rom/mapper/header/element_reader_spec.rb +0 -25
- data/spec/unit/rom/mapper/header/keys_spec.rb +0 -32
- data/spec/unit/rom/mapper/identity_from_tuple_spec.rb +0 -15
- data/spec/unit/rom/mapper/identity_spec.rb +0 -15
- data/spec/unit/rom/mapper/load_spec.rb +0 -15
- data/spec/unit/rom/mapper/loader/allocator/call_spec.rb +0 -7
- data/spec/unit/rom/mapper/loader/allocator/identity_spec.rb +0 -7
- data/spec/unit/rom/mapper/loader/attribute_writer/call_spec.rb +0 -7
- data/spec/unit/rom/mapper/loader/attribute_writer/identity_spec.rb +0 -7
- data/spec/unit/rom/mapper/loader/object_builder/call_spec.rb +0 -7
- data/spec/unit/rom/mapper/loader/object_builder/identity_spec.rb +0 -7
- data/spec/unit/rom/mapper/model_spec.rb +0 -11
- data/spec/unit/rom/mapper/new_object_spec.rb +0 -14
data/lib/rom/mapper/attribute.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module ROM
|
4
|
-
class Mapper
|
5
|
-
|
6
|
-
# Represents a mapping attribute
|
7
|
-
#
|
8
|
-
# @private
|
9
|
-
class Attribute < Struct.new(:name, :field)
|
10
|
-
include Adamantium, Equalizer.new(:name, :field)
|
11
|
-
|
12
|
-
# @api private
|
13
|
-
def self.coerce(input, mapping = nil)
|
14
|
-
field = Axiom::Attribute.coerce(input)
|
15
|
-
new(mapping || field.name, field)
|
16
|
-
end
|
17
|
-
|
18
|
-
# @api private
|
19
|
-
def mapping
|
20
|
-
{ tuple_key => name }
|
21
|
-
end
|
22
|
-
|
23
|
-
# @api private
|
24
|
-
def tuple_key
|
25
|
-
field.name
|
26
|
-
end
|
27
|
-
|
28
|
-
end # Attribute
|
29
|
-
|
30
|
-
end # Mapper
|
31
|
-
end # ROM
|
data/lib/rom/mapper/dumper.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module ROM
|
4
|
-
class Mapper
|
5
|
-
|
6
|
-
# Dumps an object back into a tuple
|
7
|
-
#
|
8
|
-
# @private
|
9
|
-
class Dumper
|
10
|
-
include Concord::Public.new(:header), Adamantium
|
11
|
-
|
12
|
-
# @api private
|
13
|
-
def call(object)
|
14
|
-
header.each_with_object([]) { |attribute, tuple|
|
15
|
-
tuple << object.send(attribute.name)
|
16
|
-
}
|
17
|
-
end
|
18
|
-
|
19
|
-
# @api private
|
20
|
-
def identity(object)
|
21
|
-
header.keys.map { |key| object.send("#{key.name}") }
|
22
|
-
end
|
23
|
-
|
24
|
-
end # Dumper
|
25
|
-
|
26
|
-
end # Mapper
|
27
|
-
end # ROM
|
data/lib/rom/mapper/loader.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module ROM
|
4
|
-
class Mapper
|
5
|
-
|
6
|
-
# Abstract loader class
|
7
|
-
#
|
8
|
-
# @private
|
9
|
-
class Loader
|
10
|
-
include Concord::Public.new(:header, :model), Adamantium, AbstractType
|
11
|
-
|
12
|
-
abstract_method :call
|
13
|
-
|
14
|
-
# @api public
|
15
|
-
def identity(tuple)
|
16
|
-
header.keys.map { |key| tuple[key.name] }
|
17
|
-
end
|
18
|
-
|
19
|
-
end # Loader
|
20
|
-
|
21
|
-
end # Mapper
|
22
|
-
end # ROM
|
@@ -1,32 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module ROM
|
4
|
-
class Mapper
|
5
|
-
class Loader
|
6
|
-
|
7
|
-
# Loader class which doesn't call initialize
|
8
|
-
#
|
9
|
-
# @private
|
10
|
-
class Allocator < self
|
11
|
-
|
12
|
-
# @api private
|
13
|
-
def call(tuple)
|
14
|
-
allocate { |attribute, object|
|
15
|
-
object.instance_variable_set(
|
16
|
-
"@#{attribute.name}", tuple[attribute.name]
|
17
|
-
)
|
18
|
-
}
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
# @api private
|
24
|
-
def allocate(&block)
|
25
|
-
header.each_with_object(model.allocate, &block)
|
26
|
-
end
|
27
|
-
|
28
|
-
end # Allocator
|
29
|
-
|
30
|
-
end # Loader
|
31
|
-
end # Mapper
|
32
|
-
end # ROM
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module ROM
|
4
|
-
class Mapper
|
5
|
-
class Loader
|
6
|
-
|
7
|
-
# Special type of Allocator loader which uses attribute writers
|
8
|
-
#
|
9
|
-
# @private
|
10
|
-
class AttributeWriter < Allocator
|
11
|
-
|
12
|
-
# @api private
|
13
|
-
def call(tuple)
|
14
|
-
allocate { |attribute, object|
|
15
|
-
object.public_send("#{attribute.name}=", tuple[attribute.name])
|
16
|
-
}
|
17
|
-
end
|
18
|
-
|
19
|
-
end # AttributeWriter
|
20
|
-
|
21
|
-
end # Loader
|
22
|
-
end # Mapper
|
23
|
-
end # ROM
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module ROM
|
4
|
-
class Mapper
|
5
|
-
class Loader
|
6
|
-
|
7
|
-
# Loader class that calls initialize
|
8
|
-
#
|
9
|
-
# @private
|
10
|
-
class ObjectBuilder < self
|
11
|
-
|
12
|
-
# @api private
|
13
|
-
def call(tuple)
|
14
|
-
model.new(attributes(tuple))
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
# @api private
|
20
|
-
def attributes(tuple)
|
21
|
-
Hash[header.attribute_names.map { |name| [name, tuple[name]] }]
|
22
|
-
end
|
23
|
-
|
24
|
-
end # ObjectBuilder
|
25
|
-
|
26
|
-
end # Loader
|
27
|
-
end # Mapper
|
28
|
-
end # ROM
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
shared_examples_for 'Mapper::Loader#call' do
|
4
|
-
subject(:loader) { described_class.new(header, model) }
|
5
|
-
|
6
|
-
let(:header) { Mapper::Header.build([[:id, Integer], [:name, String]]) }
|
7
|
-
let(:tuple) { Hash[id: 1, name: 'Jane', something: 'foo'] }
|
8
|
-
let(:model) { mock_model(:id, :name) }
|
9
|
-
|
10
|
-
it 'returns loaded object' do
|
11
|
-
expect(loader.call(tuple)).to eq(model.new(id: 1, name: 'Jane'))
|
12
|
-
end
|
13
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
shared_examples_for 'Mapper::Loader#identity' do
|
4
|
-
subject(:loader) { described_class.new(header, model) }
|
5
|
-
|
6
|
-
let(:header) { Mapper::Header.build([[:id, Integer], [:name, String]], keys: [:id]) }
|
7
|
-
let(:tuple) { Hash[id: 1, name: 'Jane'] }
|
8
|
-
let(:model) { mock_model(:id, :name) }
|
9
|
-
|
10
|
-
it "returns object's identity" do
|
11
|
-
expect(loader.identity(tuple)).to eq([1])
|
12
|
-
end
|
13
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
shared_context 'Mapper' do
|
4
|
-
let(:mapper) { described_class.new(header, loader, dumper) }
|
5
|
-
|
6
|
-
let(:header) { fake(:header) { Mapper::Header } }
|
7
|
-
let(:loader) { fake(:loader, model: model) { Mapper::Loader } }
|
8
|
-
let(:dumper) { fake(:dumper) { Mapper::Dumper } }
|
9
|
-
let(:data) { [1, 'Jane'] }
|
10
|
-
let(:tuple) { Hash[id: 1, name: 'Jane'] }
|
11
|
-
let(:object) { model.new(id: 1, name: 'Jane') }
|
12
|
-
let(:model) { mock_model(:id, :name) }
|
13
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe Mapper, '#call' do
|
6
|
-
subject { object.call(relation) }
|
7
|
-
|
8
|
-
let(:object) {
|
9
|
-
Mapper.build(header, model)
|
10
|
-
}
|
11
|
-
|
12
|
-
let(:header) {
|
13
|
-
Mapper::Header.build(
|
14
|
-
[[:user_id, Integer], [:user_name, String], [:age, Integer]],
|
15
|
-
map: { user_id: :id, user_name: :name }
|
16
|
-
)
|
17
|
-
}
|
18
|
-
|
19
|
-
let(:model) {
|
20
|
-
OpenStruct
|
21
|
-
}
|
22
|
-
|
23
|
-
let(:relation) {
|
24
|
-
Axiom::Relation::Base.new(:users, [
|
25
|
-
[:user_id, Integer], [:user_name, String], [:email, String], [:age, Integer]
|
26
|
-
])
|
27
|
-
}
|
28
|
-
|
29
|
-
it 'renames relation' do
|
30
|
-
expect(subject.header.map(&:name)).to eq([:id, :name, :age])
|
31
|
-
end
|
32
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe Mapper, '.build' do
|
6
|
-
subject { described_class.build(header, model, options) }
|
7
|
-
|
8
|
-
let(:model) { mock_model(:name) }
|
9
|
-
let(:attributes) { [[:user_name, String]] }
|
10
|
-
let(:options) { Hash[map: { user_name: :name }] }
|
11
|
-
|
12
|
-
describe 'when header is a primitive' do
|
13
|
-
let(:header) { attributes }
|
14
|
-
|
15
|
-
its(:model) { should be(model) }
|
16
|
-
its(:loader) { should be_instance_of(Mapper::LOADERS[:allocator]) }
|
17
|
-
its(:dumper) { should be_instance_of(Mapper::Dumper) }
|
18
|
-
|
19
|
-
it 'builds correct header' do
|
20
|
-
expect(subject.header.mapping).to eql(options[:map])
|
21
|
-
end
|
22
|
-
|
23
|
-
let(:object) { model.new(name: 'Jane') }
|
24
|
-
let(:params) { Hash[name: 'Jane'] }
|
25
|
-
|
26
|
-
specify do
|
27
|
-
expect(subject.load(params)).to eq(object)
|
28
|
-
end
|
29
|
-
|
30
|
-
specify do
|
31
|
-
expect(subject.dump(object)).to eq(params.values)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
describe 'when header is a mapper header instance' do
|
36
|
-
let(:header) { Mapper::Header.build(attributes) }
|
37
|
-
let(:options) { Hash.new }
|
38
|
-
|
39
|
-
its(:model) { should be(model) }
|
40
|
-
its(:loader) { should be_instance_of(Mapper::LOADERS[:allocator]) }
|
41
|
-
its(:dumper) { should be_instance_of(Mapper::Dumper) }
|
42
|
-
its(:header) { should be(header) }
|
43
|
-
end
|
44
|
-
|
45
|
-
describe 'when options has custom loader' do
|
46
|
-
let(:header) { Mapper::Header.build(attributes) }
|
47
|
-
let(:options) { Hash[loader: :object_builder] }
|
48
|
-
|
49
|
-
its(:model) { should be(model) }
|
50
|
-
its(:loader) { should be_instance_of(Mapper::LOADERS[:object_builder]) }
|
51
|
-
its(:dumper) { should be_instance_of(Mapper::Dumper) }
|
52
|
-
its(:header) { should be(header) }
|
53
|
-
end
|
54
|
-
|
55
|
-
describe 'loader is set to :attribute_writer' do
|
56
|
-
let(:header) { Mapper::Header.build(attributes) }
|
57
|
-
let(:options) { Hash[loader: :attribute_writer] }
|
58
|
-
|
59
|
-
its(:model) { should be(model) }
|
60
|
-
its(:loader) { should be_instance_of(Mapper::LOADERS[:attribute_writer]) }
|
61
|
-
its(:dumper) { should be_instance_of(Mapper::Dumper) }
|
62
|
-
its(:header) { should be(header) }
|
63
|
-
end
|
64
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe Mapper, '#dump' do
|
6
|
-
include_context 'Mapper'
|
7
|
-
|
8
|
-
it 'dumps the object into data tuple' do
|
9
|
-
stub(dumper).call(object) { data }
|
10
|
-
|
11
|
-
expect(mapper.dump(object)).to be(data)
|
12
|
-
|
13
|
-
dumper.should have_received.call(object)
|
14
|
-
end
|
15
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe Mapper::Dumper, '#call' do
|
6
|
-
subject(:dumper) { described_class.new(header) }
|
7
|
-
|
8
|
-
let(:header) { Mapper::Header.build([[:uid, Integer], [:name, String]], map: { uid: :id }, keys: [:uid]) }
|
9
|
-
let(:data) { Hash[id: 1, name: 'Jane'] }
|
10
|
-
let(:model) { mock_model(:id, :name) }
|
11
|
-
let(:object) { model.new(data) }
|
12
|
-
|
13
|
-
context 'with public attribute readers' do
|
14
|
-
it 'returns dumped tuple' do
|
15
|
-
expect(dumper.call(object)).to eq([1, 'Jane'])
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
context 'with private attribute readers' do
|
20
|
-
before do
|
21
|
-
model.send(:private, :id)
|
22
|
-
model.send(:private, :name)
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'returns dumped tuple' do
|
26
|
-
expect(dumper.call(object)).to eq([1, 'Jane'])
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe Mapper::Dumper, '#identity' do
|
6
|
-
subject(:dumper) { described_class.new(header) }
|
7
|
-
|
8
|
-
let(:header) { Mapper::Header.build([[:uid, Integer], [:name, String]], map: { uid: :id }, keys: [:uid]) }
|
9
|
-
let(:data) { Hash[id: 1, name: 'Jane'] }
|
10
|
-
let(:model) { mock_model(:id, :name) }
|
11
|
-
let(:object) { model.new(data) }
|
12
|
-
|
13
|
-
context 'with public attribute readers' do
|
14
|
-
it "returns object's identity" do
|
15
|
-
expect(dumper.identity(object)).to eq([1])
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
context 'with private attribute readers' do
|
20
|
-
before do
|
21
|
-
model.send(:private, :id)
|
22
|
-
end
|
23
|
-
|
24
|
-
it "returns object's identity" do
|
25
|
-
expect(dumper.identity(object)).to eq([1])
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe Mapper::Header, '#each' do
|
6
|
-
let(:object) { Mapper::Header.build(attributes) }
|
7
|
-
let(:attributes) { [[:id, Integer], [:name, String]] }
|
8
|
-
let(:id) { object[:id] }
|
9
|
-
let(:name) { object[:name] }
|
10
|
-
|
11
|
-
context 'with a block' do
|
12
|
-
subject { object.each { |attribute| result << attribute } }
|
13
|
-
|
14
|
-
let(:result) { [] }
|
15
|
-
|
16
|
-
it { should be(object) }
|
17
|
-
|
18
|
-
specify do
|
19
|
-
expect { subject }.to change { result }.from([]).to([id, name])
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
context 'without a block' do
|
24
|
-
subject { object.each }
|
25
|
-
|
26
|
-
it { should be_instance_of(Enumerator) }
|
27
|
-
end
|
28
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe Mapper::Header, '#[]' do
|
6
|
-
subject { object[name] }
|
7
|
-
|
8
|
-
let(:object) { Mapper::Header.build(attributes) }
|
9
|
-
let(:attributes) { [[:id, Integer]] }
|
10
|
-
|
11
|
-
context 'when attribute exists' do
|
12
|
-
let(:name) { :id }
|
13
|
-
let(:id) { Mapper::Attribute.coerce(attributes.first) }
|
14
|
-
|
15
|
-
it { should eql(id) }
|
16
|
-
end
|
17
|
-
|
18
|
-
context 'when attribute does not exist' do
|
19
|
-
let(:name) { :not_here }
|
20
|
-
|
21
|
-
specify do
|
22
|
-
expect { subject }.to raise_error(KeyError)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|