rmodel 0.4.0.dev → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|