rom 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. data/.rspec +2 -0
  2. data/.travis.yml +22 -0
  3. data/Changelog.md +16 -0
  4. data/Gemfile +13 -6
  5. data/Gemfile.devtools +71 -0
  6. data/Guardfile +19 -0
  7. data/LICENSE +1 -1
  8. data/README.md +52 -30
  9. data/Rakefile +3 -0
  10. data/config/devtools.yml +4 -0
  11. data/config/flay.yml +3 -0
  12. data/config/flog.yml +2 -0
  13. data/config/mutant.yml +5 -0
  14. data/config/reek.yml +103 -0
  15. data/config/rubocop.yml +62 -0
  16. data/config/yardstick.yml +2 -0
  17. data/lib/rom.rb +13 -5
  18. data/lib/rom/constants.rb +16 -0
  19. data/lib/rom/environment.rb +105 -0
  20. data/lib/rom/environment/builder.rb +71 -0
  21. data/lib/rom/mapper.rb +176 -0
  22. data/lib/rom/mapper/attribute.rb +108 -0
  23. data/lib/rom/mapper/builder.rb +58 -0
  24. data/lib/rom/mapper/builder/definition.rb +162 -0
  25. data/lib/rom/mapper/header.rb +103 -0
  26. data/lib/rom/mapper/loader_builder.rb +26 -0
  27. data/lib/rom/relation.rb +375 -0
  28. data/lib/rom/repository.rb +71 -0
  29. data/lib/rom/schema.rb +21 -0
  30. data/lib/rom/schema/builder.rb +59 -0
  31. data/lib/rom/schema/definition.rb +84 -0
  32. data/lib/rom/schema/definition/relation.rb +80 -0
  33. data/lib/rom/schema/definition/relation/base.rb +27 -0
  34. data/lib/rom/session.rb +111 -0
  35. data/lib/rom/session/environment.rb +67 -0
  36. data/lib/rom/session/identity_map.rb +43 -0
  37. data/lib/rom/session/mapper.rb +62 -0
  38. data/lib/rom/session/relation.rb +140 -0
  39. data/lib/rom/session/state.rb +59 -0
  40. data/lib/rom/session/state/created.rb +22 -0
  41. data/lib/rom/session/state/deleted.rb +25 -0
  42. data/lib/rom/session/state/persisted.rb +34 -0
  43. data/lib/rom/session/state/transient.rb +20 -0
  44. data/lib/rom/session/state/updated.rb +29 -0
  45. data/lib/rom/session/tracker.rb +62 -0
  46. data/lib/rom/support/axiom/adapter.rb +111 -0
  47. data/lib/rom/support/axiom/adapter/data_objects.rb +38 -0
  48. data/lib/rom/support/axiom/adapter/memory.rb +25 -0
  49. data/lib/rom/support/axiom/adapter/postgres.rb +19 -0
  50. data/lib/rom/support/axiom/adapter/sqlite3.rb +20 -0
  51. data/lib/version.rb +1 -1
  52. data/rom.gemspec +7 -3
  53. data/spec/integration/environment_setup_spec.rb +24 -0
  54. data/spec/integration/grouped_mappers_spec.rb +87 -0
  55. data/spec/integration/join_and_group_spec.rb +76 -0
  56. data/spec/integration/join_and_wrap_spec.rb +68 -0
  57. data/spec/integration/mapping_embedded_relations_spec.rb +73 -0
  58. data/spec/integration/mapping_relations_spec.rb +120 -0
  59. data/spec/integration/schema_definition_spec.rb +152 -0
  60. data/spec/integration/session_spec.rb +87 -0
  61. data/spec/integration/wrapped_mappers_spec.rb +73 -0
  62. data/spec/shared/unit/environment_context.rb +6 -0
  63. data/spec/shared/unit/loader.rb +11 -0
  64. data/spec/shared/unit/loader_identity.rb +13 -0
  65. data/spec/shared/unit/mapper_context.rb +11 -0
  66. data/spec/shared/unit/relation_context.rb +82 -0
  67. data/spec/shared/unit/session_environment_context.rb +11 -0
  68. data/spec/shared/unit/session_relation_context.rb +18 -0
  69. data/spec/spec_helper.rb +49 -0
  70. data/spec/support/helper.rb +34 -0
  71. data/spec/support/ice_nine_config.rb +10 -0
  72. data/spec/support/test_mapper.rb +110 -0
  73. data/spec/unit/rom/environment/builder/mapping_spec.rb +24 -0
  74. data/spec/unit/rom/environment/builder/schema_spec.rb +33 -0
  75. data/spec/unit/rom/environment/class_methods/setup_spec.rb +18 -0
  76. data/spec/unit/rom/environment/repository_spec.rb +21 -0
  77. data/spec/unit/rom/mapper/attribute/embedded_collection/to_ast_spec.rb +18 -0
  78. data/spec/unit/rom/mapper/attribute/embedded_value/to_ast_spec.rb +16 -0
  79. data/spec/unit/rom/mapper/attribute/rename_spec.rb +9 -0
  80. data/spec/unit/rom/mapper/attribute/to_ast_spec.rb +9 -0
  81. data/spec/unit/rom/mapper/builder/class_methods/call_spec.rb +61 -0
  82. data/spec/unit/rom/mapper/class_methods/build_spec.rb +55 -0
  83. data/spec/unit/rom/mapper/dump_spec.rb +11 -0
  84. data/spec/unit/rom/mapper/group_spec.rb +35 -0
  85. data/spec/unit/rom/mapper/header/each_spec.rb +26 -0
  86. data/spec/unit/rom/mapper/header/element_reader_spec.rb +21 -0
  87. data/spec/unit/rom/mapper/header/group_spec.rb +18 -0
  88. data/spec/unit/rom/mapper/header/join_spec.rb +14 -0
  89. data/spec/unit/rom/mapper/header/keys_spec.rb +29 -0
  90. data/spec/unit/rom/mapper/header/project_spec.rb +13 -0
  91. data/spec/unit/rom/mapper/header/rename_spec.rb +11 -0
  92. data/spec/unit/rom/mapper/header/to_ast_spec.rb +11 -0
  93. data/spec/unit/rom/mapper/header/wrap_spec.rb +18 -0
  94. data/spec/unit/rom/mapper/identity_from_tuple_spec.rb +11 -0
  95. data/spec/unit/rom/mapper/identity_spec.rb +11 -0
  96. data/spec/unit/rom/mapper/join_spec.rb +15 -0
  97. data/spec/unit/rom/mapper/load_spec.rb +11 -0
  98. data/spec/unit/rom/mapper/new_object_spec.rb +14 -0
  99. data/spec/unit/rom/mapper/project_spec.rb +11 -0
  100. data/spec/unit/rom/mapper/rename_spec.rb +16 -0
  101. data/spec/unit/rom/mapper/wrap_spec.rb +35 -0
  102. data/spec/unit/rom/relation/delete_spec.rb +15 -0
  103. data/spec/unit/rom/relation/drop_spec.rb +11 -0
  104. data/spec/unit/rom/relation/each_spec.rb +23 -0
  105. data/spec/unit/rom/relation/first_spec.rb +19 -0
  106. data/spec/unit/rom/relation/group_spec.rb +29 -0
  107. data/spec/unit/rom/relation/inject_mapper_spec.rb +17 -0
  108. data/spec/unit/rom/relation/insert_spec.rb +13 -0
  109. data/spec/unit/rom/relation/last_spec.rb +19 -0
  110. data/spec/unit/rom/relation/one_spec.rb +49 -0
  111. data/spec/unit/rom/relation/rename_spec.rb +21 -0
  112. data/spec/unit/rom/relation/replace_spec.rb +13 -0
  113. data/spec/unit/rom/relation/restrict_spec.rb +25 -0
  114. data/spec/unit/rom/relation/sort_by_spec.rb +25 -0
  115. data/spec/unit/rom/relation/take_spec.rb +11 -0
  116. data/spec/unit/rom/relation/to_a_spec.rb +20 -0
  117. data/spec/unit/rom/relation/update_spec.rb +25 -0
  118. data/spec/unit/rom/relation/wrap_spec.rb +29 -0
  119. data/spec/unit/rom/repository/class_methods/build_spec.rb +27 -0
  120. data/spec/unit/rom/repository/element_reader_spec.rb +21 -0
  121. data/spec/unit/rom/repository/element_writer_spec.rb +18 -0
  122. data/spec/unit/rom/schema/builder/class_methods/build_spec.rb +103 -0
  123. data/spec/unit/rom/schema/element_reader_spec.rb +15 -0
  124. data/spec/unit/rom/session/class_methods/start_spec.rb +23 -0
  125. data/spec/unit/rom/session/clean_predicate_spec.rb +21 -0
  126. data/spec/unit/rom/session/environment/element_reader_spec.rb +13 -0
  127. data/spec/unit/rom/session/flush_spec.rb +58 -0
  128. data/spec/unit/rom/session/mapper/load_spec.rb +47 -0
  129. data/spec/unit/rom/session/relation/delete_spec.rb +28 -0
  130. data/spec/unit/rom/session/relation/dirty_predicate_spec.rb +35 -0
  131. data/spec/unit/rom/session/relation/identity_spec.rb +11 -0
  132. data/spec/unit/rom/session/relation/new_spec.rb +50 -0
  133. data/spec/unit/rom/session/relation/save_spec.rb +50 -0
  134. data/spec/unit/rom/session/relation/state_spec.rb +23 -0
  135. data/spec/unit/rom/session/relation/track_spec.rb +23 -0
  136. data/spec/unit/rom/session/relation/tracking_predicate_spec.rb +23 -0
  137. data/spec/unit/rom/session/relation/update_attributes_spec.rb +45 -0
  138. data/spec/unit/rom/session/state_spec.rb +79 -0
  139. metadata +212 -11
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mapper::Header, '#rename' do
6
+ subject(:header) { Mapper::Header.build([[:id], [:name]]) }
7
+
8
+ it 'returns a new header with renamed attributes' do
9
+ expect(header.rename(:id => :user_id)).to eql(Mapper::Header.build([[:user_id], [:name]]))
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mapper::Header, '#to_ast' do
6
+ subject(:header) { Mapper::Header.build([[:id], [:name]]) }
7
+
8
+ it 'returns a morpher transformer node that can load an attribute hash' do
9
+ expect(Morpher.compile(header.to_ast).call(id: 1, name: 'Jane', other: 'Foo')).to eql(id: 1, name: 'Jane')
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mapper::Header, '#wrap' do
6
+ subject(:header) { left.wrap(model: mapper) }
7
+
8
+ let(:left) { Mapper::Header.build([[:id], [:name]]) }
9
+ let(:mapper) { Mapper.build([[:id], [:title]]) }
10
+
11
+ it 'returns a header with embedded value attribute' do
12
+ expected = Mapper::Header.build([
13
+ [:id], [:name], mapper.attribute(Mapper::Attribute::EmbeddedValue, :model)
14
+ ])
15
+
16
+ expect(header).to eq(expected)
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mapper, '#identity_from_tuple' do
6
+ subject { mapper.identity_from_tuple(tuple) }
7
+
8
+ include_context 'Mapper'
9
+
10
+ it { should eq([1]) }
11
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mapper, '#identity' do
6
+ subject { mapper.identity(object) }
7
+
8
+ include_context 'Mapper'
9
+
10
+ it { should eq([1]) }
11
+ end
@@ -0,0 +1,15 @@
1
+ require "spec_helper"
2
+
3
+ describe Mapper, "#join" do
4
+ let(:task) { Mapper.build([[:title]], model: task_model) }
5
+ let(:user) { Mapper.build([[:name]], model: user_model) }
6
+
7
+ let(:task_model) { mock_model(:title) }
8
+ let(:user_model) { mock_model(:name) }
9
+
10
+ subject(:mapper) { task.join(user) }
11
+
12
+ it "returns a mapper with joined headers" do
13
+ expect(mapper).to eql(Mapper.build([[:title], [:name]], model: task_model))
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mapper, '#load' do
6
+ include_context 'Mapper'
7
+
8
+ it 'loads the tuple into model' do
9
+ expect(mapper.load(tuple)).to eql(object)
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mapper, '#new_object' do
6
+ subject { mapper.new_object(name: 'Jane', &block) }
7
+
8
+ include_context 'Mapper'
9
+
10
+ let(:attributes) { Hash[id: 1, name: 'Jane'] }
11
+ let(:block) { proc { self.id = 1 } }
12
+
13
+ it { should eql(model.new(attributes)) }
14
+ end
@@ -0,0 +1,11 @@
1
+ require "spec_helper"
2
+
3
+ describe Mapper, "#project" do
4
+ let(:task) { Mapper.build([[:id, from: :user_id], [:name]]) }
5
+
6
+ subject(:mapper) { task.project([:user_id]) }
7
+
8
+ it "returns a mapper with a projected header" do
9
+ expect(mapper).to eql(Mapper.build([[:id, from: :user_id]]))
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Mapper, '#rename' do
6
+ include_context 'Mapper'
7
+
8
+ let(:model) { mock_model(:user_id, :user_name) }
9
+
10
+ it 'renames attributes' do
11
+ tuple = { :user_id => 1, :user_name => 'Jane' }
12
+ user = model.new(:user_id => 1, :user_name => 'Jane')
13
+
14
+ expect(mapper.rename(:id => :user_id, :name => :user_name).load(tuple)).to eql(user)
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ require "spec_helper"
2
+
3
+ describe Mapper, "#wrap" do
4
+ let(:task) { Mapper.build([[:title]], model: task_model) }
5
+ let(:user) { Mapper.build([[:id], [:name]], model: user_model) }
6
+
7
+ let(:task_model) { mock_model(:title, :user) }
8
+ let(:user_model) { mock_model(:id, :name) }
9
+
10
+ subject(:mapper) { task.wrap(user: user) }
11
+
12
+ let(:loader_transformer) do
13
+ Morpher.compile(
14
+ s(:block,
15
+ s(:hash_transform,
16
+ s(:block, s(:key_fetch, :title), s(:key_dump, :title)),
17
+ s(:key_transform, :user, :user, user.loader.node)
18
+ ),
19
+ s(:load_instance_variables, s(:param, task_model, :title, :user))
20
+ )
21
+ )
22
+ end
23
+
24
+ let(:dumper_transformer) do
25
+ loader_transformer.inverse
26
+ end
27
+
28
+ it "returns a mapper that can load wrapped tuples" do
29
+ expect(mapper.loader).to eq(loader_transformer)
30
+ end
31
+
32
+ it "returns a mapper that can dump wrapped objects" do
33
+ expect(mapper.dumper).to eq(dumper_transformer)
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#delete' do
6
+ include_context 'Relation'
7
+
8
+ subject { relation.delete(john) }
9
+
10
+ it { should be_instance_of(Relation) }
11
+
12
+ it 'deletes tuples from the relation' do
13
+ should_not include(john)
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#drop' do
6
+ include_context 'Relation'
7
+
8
+ it 'drops the relation by the given offset' do
9
+ expect(relation.drop(1).to_a).to eql([jane, jack, jade])
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#each' do
6
+ include_context 'Relation'
7
+
8
+ context 'with a block' do
9
+ it 'yields objects' do
10
+ retval = relation.each do |tuple|
11
+ expect(tuple).to be_instance_of(model)
12
+ end
13
+
14
+ expect(retval).to be(relation)
15
+ end
16
+ end
17
+
18
+ context 'without a block' do
19
+ it 'returns enumerator' do
20
+ expect(relation.each).to be_instance_of(Enumerator)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#first' do
6
+ include_context 'Relation'
7
+
8
+ context 'when limit is not provided' do
9
+ it 'returns first object' do
10
+ expect(relation.first.to_a).to eql([john])
11
+ end
12
+ end
13
+
14
+ context 'when limit is provided' do
15
+ it 'returns first n-objects' do
16
+ expect(relation.first(2).to_a).to eql([john, jane])
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#group' do
6
+ let(:users) { Relation.new(user_relation, user_mapper) }
7
+ let(:tasks) { Relation.new(task_relation, task_mapper) }
8
+
9
+ fake(:user_relation) { Axiom::Relation }
10
+ fake(:user_mapper) { Mapper }
11
+
12
+ fake(:task_relation) { Axiom::Relation }
13
+ fake(:task_header) { Axiom::Relation::Header }
14
+ fake(:task_mapper) { Mapper }
15
+
16
+ fake(:groupped_relation) { Axiom::Relation }
17
+ fake(:groupped_mapper) { Mapper }
18
+
19
+ it "groups relation and mapper" do
20
+ stub(task_relation).header { task_header }
21
+ stub(user_relation).group(:tasks => task_header) { groupped_relation }
22
+ stub(user_mapper).group(:tasks => task_mapper) { groupped_mapper }
23
+
24
+ expect(users.group(:tasks => tasks)).to eql(Relation.new(groupped_relation, groupped_mapper))
25
+
26
+ expect(user_relation).to have_received.group(:tasks => task_header)
27
+ expect(user_mapper).to have_received.group(:tasks => task_mapper)
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#inject_mapper' do
6
+ subject(:relation) { described_class.new([], mapper) }
7
+
8
+ fake(:mapper)
9
+ fake(:other_mapper) { Mapper }
10
+
11
+ it 'returns new relation with injected new mapper' do
12
+ other_relation = relation.inject_mapper(other_mapper)
13
+
14
+ expect(other_relation.relation).to be(relation.relation)
15
+ expect(other_relation.mapper).to be(other_mapper)
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#insert' do
6
+ include_context 'Relation'
7
+
8
+ let(:user) { model.new(name: 'Piotr') }
9
+
10
+ it 'inserts object into relation' do
11
+ expect(relation.insert(user).to_a).to include(user)
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#last' do
6
+ include_context 'Relation'
7
+
8
+ context 'when limit is not provided' do
9
+ it 'returns last object' do
10
+ expect(relation.last.to_a).to eql([jade])
11
+ end
12
+ end
13
+
14
+ context 'when limit is provided' do
15
+ it 'returns last n-objects' do
16
+ expect(relation.last(2).to_a).to eql([jack, jade])
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#one' do
6
+ include_context 'Relation'
7
+
8
+ it 'limits the underlying relation' do
9
+ stub(relation).take(2) { [john] }
10
+ expect(relation.one).to eql(john)
11
+ relation.should have_received.take(2)
12
+ end
13
+
14
+ context 'when no block is given' do
15
+ context 'when one tuple is returned' do
16
+ it 'returns one object' do
17
+ expect(relation.restrict(name: 'John').one).to eql(john)
18
+ end
19
+ end
20
+
21
+ context 'when no tuple is returned' do
22
+ it 'raises NoTuplesError' do
23
+ expect { relation.restrict(name: 'unknown').one }.to raise_error(NoTuplesError)
24
+ end
25
+ end
26
+
27
+ context 'when more than one tuple is returned' do
28
+ let(:header) { [[:id, Integer], [:name, String]] }
29
+ let(:users) { Axiom::Relation.new(header, [[1, 'Jane'], [2, 'Jane']]) }
30
+ let(:model) { mock_model(:id, :name) }
31
+
32
+ it 'raises ManyTuplesError' do
33
+ expect { relation.restrict(name: 'Jane').one }.to raise_error(ManyTuplesError)
34
+ end
35
+ end
36
+ end
37
+
38
+ context 'when a block is given' do
39
+ let(:block) { ->() { raise error } }
40
+
41
+ context 'when no tuple is returned' do
42
+ let(:error) { Class.new(StandardError) }
43
+
44
+ it 'invokes the block' do
45
+ expect { relation.restrict(name: 'unknown').one(&block) }.to raise_error(error)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#rename' do
6
+ subject(:relation) { users.rename(:user_name => :name) }
7
+
8
+ let(:users) do
9
+ Relation.new(
10
+ Axiom::Relation.new([[:user_name, String]], [['Jane']]),
11
+ Mapper.build([[:user_name]], model: model)
12
+ )
13
+ end
14
+
15
+ let(:model) { mock_model(:name) }
16
+ let(:user) { model.new(name: 'Jane') }
17
+
18
+ it "renames the attributes" do
19
+ expect(relation.to_a.first).to eql(user)
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#replace' do
6
+ subject(:relation) { described_class.new(users, mapper) }
7
+
8
+ include_context 'Relation'
9
+
10
+ it 'replaces the relation with a new one' do
11
+ expect(relation.replace([jane]).to_a).to eq([jane])
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Relation, '#restrict' do
6
+ include_context 'Relation'
7
+
8
+ share_examples_for 'restricted relation' do
9
+ specify do
10
+ should eq([jane])
11
+ end
12
+ end
13
+
14
+ context 'with condition hash' do
15
+ subject { relation.restrict(name: 'Jane').to_a }
16
+
17
+ it_behaves_like 'restricted relation'
18
+ end
19
+
20
+ context 'with a block' do
21
+ subject { relation.restrict { |r| r.name.eq('Jane') }.to_a }
22
+
23
+ it_behaves_like 'restricted relation'
24
+ end
25
+ end