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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -1
  4. data/.travis.yml +19 -13
  5. data/{Changelog.md → CHANGELOG.md} +6 -0
  6. data/Gemfile +23 -10
  7. data/README.md +17 -12
  8. data/Rakefile +12 -4
  9. data/lib/rom-mapper.rb +6 -15
  10. data/lib/rom/header.rb +195 -0
  11. data/lib/rom/header/attribute.rb +184 -0
  12. data/lib/rom/mapper.rb +63 -100
  13. data/lib/rom/mapper/attribute_dsl.rb +477 -0
  14. data/lib/rom/mapper/dsl.rb +120 -0
  15. data/lib/rom/mapper/model_dsl.rb +55 -0
  16. data/lib/rom/mapper/version.rb +3 -7
  17. data/lib/rom/model_builder.rb +99 -0
  18. data/lib/rom/processor.rb +28 -0
  19. data/lib/rom/processor/transproc.rb +388 -0
  20. data/rakelib/benchmark.rake +15 -0
  21. data/rakelib/mutant.rake +16 -0
  22. data/rakelib/rubocop.rake +18 -0
  23. data/rom-mapper.gemspec +7 -6
  24. data/spec/spec_helper.rb +32 -33
  25. data/spec/support/constant_leak_finder.rb +14 -0
  26. data/spec/support/mutant.rb +10 -0
  27. data/spec/unit/rom/mapper/dsl_spec.rb +467 -0
  28. data/spec/unit/rom/mapper_spec.rb +83 -0
  29. data/spec/unit/rom/processor/transproc_spec.rb +448 -0
  30. metadata +68 -89
  31. data/.ruby-version +0 -1
  32. data/Gemfile.devtools +0 -55
  33. data/config/devtools.yml +0 -2
  34. data/config/flay.yml +0 -3
  35. data/config/flog.yml +0 -2
  36. data/config/mutant.yml +0 -3
  37. data/config/reek.yml +0 -103
  38. data/config/rubocop.yml +0 -45
  39. data/lib/rom/mapper/attribute.rb +0 -31
  40. data/lib/rom/mapper/dumper.rb +0 -27
  41. data/lib/rom/mapper/loader.rb +0 -22
  42. data/lib/rom/mapper/loader/allocator.rb +0 -32
  43. data/lib/rom/mapper/loader/attribute_writer.rb +0 -23
  44. data/lib/rom/mapper/loader/object_builder.rb +0 -28
  45. data/spec/shared/unit/loader_call.rb +0 -13
  46. data/spec/shared/unit/loader_identity.rb +0 -13
  47. data/spec/shared/unit/mapper_context.rb +0 -13
  48. data/spec/unit/rom/mapper/call_spec.rb +0 -32
  49. data/spec/unit/rom/mapper/class_methods/build_spec.rb +0 -64
  50. data/spec/unit/rom/mapper/dump_spec.rb +0 -15
  51. data/spec/unit/rom/mapper/dumper/call_spec.rb +0 -29
  52. data/spec/unit/rom/mapper/dumper/identity_spec.rb +0 -28
  53. data/spec/unit/rom/mapper/header/each_spec.rb +0 -28
  54. data/spec/unit/rom/mapper/header/element_reader_spec.rb +0 -25
  55. data/spec/unit/rom/mapper/header/keys_spec.rb +0 -32
  56. data/spec/unit/rom/mapper/identity_from_tuple_spec.rb +0 -15
  57. data/spec/unit/rom/mapper/identity_spec.rb +0 -15
  58. data/spec/unit/rom/mapper/load_spec.rb +0 -15
  59. data/spec/unit/rom/mapper/loader/allocator/call_spec.rb +0 -7
  60. data/spec/unit/rom/mapper/loader/allocator/identity_spec.rb +0 -7
  61. data/spec/unit/rom/mapper/loader/attribute_writer/call_spec.rb +0 -7
  62. data/spec/unit/rom/mapper/loader/attribute_writer/identity_spec.rb +0 -7
  63. data/spec/unit/rom/mapper/loader/object_builder/call_spec.rb +0 -7
  64. data/spec/unit/rom/mapper/loader/object_builder/identity_spec.rb +0 -7
  65. data/spec/unit/rom/mapper/model_spec.rb +0 -11
  66. data/spec/unit/rom/mapper/new_object_spec.rb +0 -14
@@ -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
@@ -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
@@ -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