rmodel 0.4.0.dev → 1.0.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +4 -0
- data/.travis.yml +4 -3
- data/README.md +119 -153
- data/Rakefile +1 -2
- data/examples/mongo_embedded.rb +27 -21
- data/examples/scopes.rb +28 -0
- data/examples/sql_repository.rb +14 -26
- data/examples/timestamps.rb +7 -10
- data/examples/webapp/models/task.rb +9 -0
- data/examples/webapp/repositories/task_repository.rb +14 -0
- data/examples/webapp/server.rb +25 -0
- data/examples/webapp/support/mappers.rb +11 -0
- data/examples/webapp/support/repositories.rb +12 -0
- data/examples/webapp/support/sources.rb +13 -0
- data/examples/webapp/views/index.erb +20 -0
- data/lib/rmodel.rb +11 -8
- data/lib/rmodel/array_mapper.rb +23 -0
- data/lib/rmodel/base_mapper.rb +56 -0
- data/lib/rmodel/dummy_mapper.rb +15 -0
- data/lib/rmodel/mongo/mapper.rb +11 -0
- data/lib/rmodel/mongo/source.rb +50 -0
- data/lib/rmodel/repository.rb +36 -0
- data/lib/rmodel/repository_ext/scopable.rb +44 -0
- data/lib/rmodel/repository_ext/sugarable.rb +35 -0
- data/lib/rmodel/repository_ext/timestampable.rb +29 -0
- data/lib/rmodel/scope.rb +31 -0
- data/lib/rmodel/sequel/mapper.rb +6 -0
- data/lib/rmodel/sequel/source.rb +43 -0
- data/lib/rmodel/uni_hash.rb +16 -0
- data/lib/rmodel/version.rb +1 -1
- data/rmodel.gemspec +9 -3
- data/spec/rmodel/array_mapper_spec.rb +43 -0
- data/spec/rmodel/mongo/mapper_spec.rb +154 -0
- data/spec/rmodel/mongo/repository_spec.rb +16 -37
- data/spec/rmodel/mongo/source_spec.rb +113 -0
- data/spec/rmodel/sequel/mapper_spec.rb +57 -0
- data/spec/rmodel/sequel/repository_spec.rb +27 -38
- data/spec/rmodel/sequel/source_spec.rb +121 -0
- data/spec/shared/base_mapper.rb +39 -0
- data/spec/shared/clean_moped.rb +6 -2
- data/spec/shared/clean_sequel.rb +1 -1
- data/spec/shared/{repository_ext → repository}/crud.rb +20 -14
- data/spec/shared/repository/initialization.rb +39 -0
- data/spec/shared/repository/scopable.rb +137 -0
- data/spec/shared/repository/sugarable.rb +67 -0
- data/spec/shared/repository/timestampable.rb +137 -0
- data/spec/spec_helper.rb +17 -18
- metadata +120 -54
- data/examples/advanced_creation_of_repository.rb +0 -15
- data/examples/user.rb +0 -48
- data/lib/rmodel/base/repository.rb +0 -12
- data/lib/rmodel/base/repository_ext/sugarable.rb +0 -17
- data/lib/rmodel/base/repository_ext/timestampable.rb +0 -19
- data/lib/rmodel/mongo/repository.rb +0 -62
- data/lib/rmodel/mongo/repository_ext/query.rb +0 -34
- data/lib/rmodel/mongo/repository_ext/queryable.rb +0 -34
- data/lib/rmodel/mongo/setup.rb +0 -17
- data/lib/rmodel/mongo/simple_factory.rb +0 -78
- data/lib/rmodel/sequel/repository.rb +0 -61
- data/lib/rmodel/sequel/repository_ext/query.rb +0 -34
- data/lib/rmodel/sequel/repository_ext/queryable.rb +0 -30
- data/lib/rmodel/sequel/setup.rb +0 -12
- data/lib/rmodel/sequel/simple_factory.rb +0 -28
- data/lib/rmodel/setup.rb +0 -34
- data/spec/rmodel/mongo/repository_ext/queryable_spec.rb +0 -103
- data/spec/rmodel/mongo/repository_initialize_spec.rb +0 -174
- data/spec/rmodel/mongo/simple_factory_spec.rb +0 -195
- data/spec/rmodel/sequel/repository_ext/queryable_spec.rb +0 -114
- data/spec/rmodel/sequel/repository_initialize_spec.rb +0 -118
- data/spec/rmodel/sequel/simple_factory_spec.rb +0 -55
- data/spec/rmodel/setup_spec.rb +0 -54
- data/spec/shared/repository_ext/sugarable.rb +0 -44
- data/spec/shared/repository_ext/timestampable.rb +0 -67
@@ -1,50 +1,29 @@
|
|
1
|
-
RSpec.describe
|
2
|
-
|
3
|
-
include_context 'clean mongo database'
|
1
|
+
RSpec.describe 'Repository with MongoDB' do
|
2
|
+
include_context 'clean mongo database'
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
shared_examples 'definitions' do
|
5
|
+
let(:source) { Rmodel::Mongo::Source.new(mongo_session, :things) }
|
6
|
+
let(:mapper_klass) { Rmodel::Mongo::Mapper }
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
it_behaves_like 'repository crud' do
|
10
|
+
include_context 'definitions'
|
11
11
|
let(:unique_constraint_error) { Mongo::Error::OperationFailure }
|
12
|
-
|
13
|
-
def insert_record(id, columns)
|
14
|
-
record = columns.dup
|
15
|
-
record['_id'] = id
|
16
|
-
mongo_session[:things].insert_one(record)
|
17
|
-
end
|
18
12
|
end
|
19
13
|
|
20
14
|
it_behaves_like 'sugarable repository' do
|
21
|
-
include_context '
|
22
|
-
|
23
|
-
before do
|
24
|
-
stub_const('ThingRepository', Class.new(Rmodel::Mongo::Repository))
|
25
|
-
end
|
26
|
-
|
27
|
-
subject do
|
28
|
-
factory = Rmodel::Mongo::SimpleFactory.new(Thing, :name)
|
29
|
-
ThingRepository.new(mongo_session, :things, factory)
|
30
|
-
end
|
15
|
+
include_context 'definitions'
|
31
16
|
end
|
32
17
|
|
33
18
|
it_behaves_like 'timestampable repository' do
|
34
|
-
include_context '
|
35
|
-
|
36
|
-
before do
|
37
|
-
stub_const('ThingRepository', Class.new(Rmodel::Mongo::Repository))
|
38
|
-
end
|
19
|
+
include_context 'definitions'
|
20
|
+
end
|
39
21
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
22
|
+
it_behaves_like 'initialization' do
|
23
|
+
include_context 'definitions'
|
24
|
+
end
|
44
25
|
|
45
|
-
|
46
|
-
|
47
|
-
ThingRepository.new(mongo_session, :things, factory)
|
48
|
-
end
|
26
|
+
it_behaves_like 'scopable repository' do
|
27
|
+
include_context 'definitions'
|
49
28
|
end
|
50
29
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
RSpec.describe Rmodel::Mongo::Source do
|
2
|
+
include_context 'clean mongo database'
|
3
|
+
|
4
|
+
subject { Rmodel::Mongo::Source.new(mongo_session, :things) }
|
5
|
+
|
6
|
+
describe '#initialize(connection, collection)'
|
7
|
+
|
8
|
+
describe 'find(id)' do
|
9
|
+
context 'when an existent id is given' do
|
10
|
+
before do
|
11
|
+
mongo_session[:things].insert_one('_id' => 1, 'name' => 'chair')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns the doc' do
|
15
|
+
doc = subject.find(1)
|
16
|
+
expect(doc['name']).to eq 'chair'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when a non-existent id is given' do
|
21
|
+
it 'returns nil' do
|
22
|
+
expect(subject.find(1)).to be_nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'insert(doc)' do
|
28
|
+
context 'when the _id is not set' do
|
29
|
+
let(:inserted_id) { subject.insert('name' => 'chair') }
|
30
|
+
|
31
|
+
it 'returns the new _id' do
|
32
|
+
expect(inserted_id).not_to be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'saves the doc' do
|
36
|
+
found = mongo_session[:things].find('_id' => inserted_id).first
|
37
|
+
expect(found['name']).to eq 'chair'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when the _id is set' do
|
42
|
+
context 'when the _id is already occupied' do
|
43
|
+
before do
|
44
|
+
mongo_session[:things].insert_one('_id' => 1)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'throws the error' do
|
48
|
+
expect do
|
49
|
+
subject.insert('_id' => 1)
|
50
|
+
end.to raise_error Mongo::Error::OperationFailure
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when the _id is not occupied' do
|
55
|
+
it 'returns the same id' do
|
56
|
+
id = subject.insert('_id' => 1)
|
57
|
+
expect(id).to eq 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'update(id, doc)' do
|
64
|
+
before do
|
65
|
+
subject.insert('_id' => 1, 'name' => 'chair')
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'updates the doc' do
|
69
|
+
subject.update(1, 'name' => 'table')
|
70
|
+
expect(subject.find(1)['name']).to eq 'table'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'delete(id)' do
|
75
|
+
before do
|
76
|
+
subject.insert('_id' => 1)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'deletes the doc' do
|
80
|
+
subject.delete(1)
|
81
|
+
expect(subject.find(1)).to be_nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'build_query' do
|
86
|
+
it 'returns the object what includes methods of Origin::Queryable' do
|
87
|
+
expect(subject.build_query).to respond_to :aliases, :where, :group
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'exec_query(query)' do
|
92
|
+
before do
|
93
|
+
3.times { subject.insert({}) }
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns docs' do
|
97
|
+
docs = subject.exec_query(subject.build_query)
|
98
|
+
expect(docs.to_a.length).to eq 3
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'delete_by_query(query)' do
|
103
|
+
before do
|
104
|
+
3.times { subject.insert({}) }
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'deletes docs' do
|
108
|
+
subject.delete_by_query(subject.build_query)
|
109
|
+
found = subject.exec_query(subject.build_query)
|
110
|
+
expect(found.to_a).to be_empty
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
RSpec.describe Rmodel::Sequel::Mapper do
|
2
|
+
before do
|
3
|
+
stub_const 'User', Struct.new(:id, :name, :age)
|
4
|
+
end
|
5
|
+
|
6
|
+
subject { described_class.new(User).define_attributes(:name, :age) }
|
7
|
+
|
8
|
+
describe '#deserialize(hash)' do
|
9
|
+
it 'returns an instance of the appropriate class' do
|
10
|
+
expect(subject.deserialize({})).to be_an_instance_of User
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'sets the attributes correctly' do
|
14
|
+
object = subject.deserialize(name: 'John', age: 20)
|
15
|
+
|
16
|
+
expect(object.name).to eq 'John'
|
17
|
+
expect(object.age).to eq 20
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when _id is given' do
|
21
|
+
it 'sets the #id correctly' do
|
22
|
+
object = subject.deserialize(id: 1)
|
23
|
+
expect(object.id).to eq 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#serialize(object, id_included)' do
|
29
|
+
it 'returns an instance of Hash' do
|
30
|
+
hash = subject.serialize(User.new(1, 'John', 20), true)
|
31
|
+
expect(hash).to be_an_instance_of Hash
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'sets the keys correctly' do
|
35
|
+
hash = subject.serialize(User.new(1, 'John', 20), true)
|
36
|
+
|
37
|
+
expect(hash[:name]).to eq 'John'
|
38
|
+
expect(hash[:age]).to eq 20
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when id_included = true' do
|
42
|
+
it 'sets the id' do
|
43
|
+
hash = subject.serialize(User.new(1), true)
|
44
|
+
expect(hash[:id]).to eq 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when id_included = false' do
|
49
|
+
it 'doesnt set the id' do
|
50
|
+
hash = subject.serialize(User.new(1), false)
|
51
|
+
expect(hash.key?(:id)).to be false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it_behaves_like 'base mapper'
|
57
|
+
end
|
@@ -1,53 +1,42 @@
|
|
1
|
-
RSpec.describe
|
2
|
-
|
3
|
-
include_examples 'clean sequel database'
|
1
|
+
RSpec.describe 'Repository with Sequel' do
|
2
|
+
include_examples 'clean sequel database'
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
shared_examples 'definitions' do
|
5
|
+
let(:source) { Rmodel::Sequel::Source.new(sequel_conn, :things) }
|
6
|
+
let(:mapper_klass) { Rmodel::Sequel::Mapper }
|
7
|
+
end
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
let(:unique_constraint_error) { Sequel::UniqueConstraintViolation }
|
9
|
+
it_behaves_like 'repository crud' do
|
10
|
+
before { create_database }
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
record[:id] = id
|
17
|
-
sequel_conn[:things].insert(record)
|
18
|
-
end
|
12
|
+
include_context 'definitions'
|
13
|
+
let(:unique_constraint_error) { Sequel::UniqueConstraintViolation }
|
19
14
|
end
|
20
15
|
|
21
16
|
it_behaves_like 'sugarable repository' do
|
22
|
-
|
23
|
-
|
24
|
-
before do
|
25
|
-
create_database
|
26
|
-
stub_const('ThingRepository', Class.new(Rmodel::Sequel::Repository))
|
27
|
-
end
|
28
|
-
|
29
|
-
subject do
|
30
|
-
factory = Rmodel::Sequel::SimpleFactory.new(Thing, :name)
|
31
|
-
ThingRepository.new(sequel_conn, :things, factory)
|
32
|
-
end
|
17
|
+
before { create_database }
|
18
|
+
include_context 'definitions'
|
33
19
|
end
|
34
20
|
|
35
21
|
it_behaves_like 'timestampable repository' do
|
36
|
-
|
22
|
+
before { create_database(true) }
|
23
|
+
include_context 'definitions'
|
24
|
+
end
|
37
25
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
26
|
+
it_behaves_like 'initialization' do
|
27
|
+
before { create_database(true) }
|
28
|
+
include_context 'definitions'
|
29
|
+
end
|
42
30
|
|
43
|
-
|
44
|
-
|
45
|
-
ThingRepository.new(sequel_conn, :things, factory)
|
46
|
-
end
|
31
|
+
it_behaves_like 'scopable repository' do
|
32
|
+
include_context 'definitions'
|
47
33
|
|
48
|
-
|
49
|
-
|
50
|
-
|
34
|
+
def create_database
|
35
|
+
sequel_conn.create_table(:things) do
|
36
|
+
primary_key :id
|
37
|
+
Integer :a
|
38
|
+
Integer :b
|
39
|
+
end
|
51
40
|
end
|
52
41
|
end
|
53
42
|
|
@@ -0,0 +1,121 @@
|
|
1
|
+
RSpec.describe Rmodel::Sequel::Source do
|
2
|
+
include_context 'clean sequel database'
|
3
|
+
before { create_database }
|
4
|
+
|
5
|
+
subject { Rmodel::Sequel::Source.new(sequel_conn, :things) }
|
6
|
+
|
7
|
+
describe '#initialize(connection, table)'
|
8
|
+
|
9
|
+
describe 'find(id)' do
|
10
|
+
context 'when an existent id is given' do
|
11
|
+
before do
|
12
|
+
sequel_conn[:things].insert(id: 1, name: 'chair')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns the tuple' do
|
16
|
+
tuple = subject.find(1)
|
17
|
+
expect(tuple[:name]).to eq 'chair'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'when a non-existent id is given' do
|
22
|
+
it 'returns nil' do
|
23
|
+
expect(subject.find(1)).to be_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'insert(tuple)' do
|
29
|
+
context 'when the id is not set' do
|
30
|
+
let(:inserted_id) { subject.insert(name: 'chair') }
|
31
|
+
|
32
|
+
it 'returns the new id' do
|
33
|
+
expect(inserted_id).not_to be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'saves the tuple' do
|
37
|
+
found = sequel_conn[:things].where(id: inserted_id).first
|
38
|
+
expect(found[:name]).to eq 'chair'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when the id is set' do
|
43
|
+
context 'when the id is already occupied' do
|
44
|
+
before do
|
45
|
+
sequel_conn[:things].insert(id: 1)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'throws the error' do
|
49
|
+
expect do
|
50
|
+
subject.insert(id: 1)
|
51
|
+
end.to raise_error Sequel::UniqueConstraintViolation
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when the id is not occupied' do
|
56
|
+
it 'returns the same id' do
|
57
|
+
id = subject.insert(id: 1)
|
58
|
+
expect(id).to eq 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'update(id, doc)' do
|
65
|
+
before do
|
66
|
+
subject.insert(id: 1, name: 'chair')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'updates the doc' do
|
70
|
+
subject.update(1, name: 'table')
|
71
|
+
expect(subject.find(1)[:name]).to eq 'table'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'delete(id)' do
|
76
|
+
before do
|
77
|
+
subject.insert(id: 1)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'deletes the doc' do
|
81
|
+
subject.delete(1)
|
82
|
+
expect(subject.find(1)).to be_nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'build_query' do
|
87
|
+
it 'returns the instance of Sequel::Dataset' do
|
88
|
+
expect(subject.build_query).to be_a_kind_of Sequel::Dataset
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'exec_query(query)' do
|
93
|
+
before do
|
94
|
+
3.times { subject.insert({}) }
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'returns tuples' do
|
98
|
+
docs = subject.exec_query(subject.build_query)
|
99
|
+
expect(docs.to_a.length).to eq 3
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe 'delete_by_query(query)' do
|
104
|
+
before do
|
105
|
+
3.times { subject.insert({}) }
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'deletes tuples' do
|
109
|
+
subject.delete_by_query(subject.build_query)
|
110
|
+
found = subject.exec_query(subject.build_query)
|
111
|
+
expect(found.to_a).to be_empty
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def create_database
|
116
|
+
sequel_conn.create_table(:things) do
|
117
|
+
primary_key :id
|
118
|
+
String :name
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'active_support/hash_with_indifferent_access'
|
2
|
+
|
3
|
+
RSpec.shared_examples 'base mapper' do
|
4
|
+
describe '#initialize' do
|
5
|
+
context 'when the Thing model is given' do
|
6
|
+
before { stub_const 'Thing', Class.new }
|
7
|
+
subject { described_class.new(Thing) }
|
8
|
+
|
9
|
+
it 'creates instances of Thing' do
|
10
|
+
expect(subject.deserialize({})).to be_instance_of Thing
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#define_timestamps' do
|
16
|
+
context 'on serialization' do
|
17
|
+
before do
|
18
|
+
stub_const 'Thing', Struct.new(:created_at, :updated_at)
|
19
|
+
end
|
20
|
+
|
21
|
+
subject { described_class.new(Thing).define_timestamps }
|
22
|
+
|
23
|
+
let(:thing) { Thing.new(Time.now, Time.now) }
|
24
|
+
|
25
|
+
let(:serialized) do
|
26
|
+
hash = subject.serialize(thing, false)
|
27
|
+
ActiveSupport::HashWithIndifferentAccess.new(hash)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'adds created_at to a hash' do
|
31
|
+
expect(serialized).to have_key(:created_at)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'adds updated_at to a hash' do
|
35
|
+
expect(serialized).to have_key(:updated_at)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|