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
data/spec/shared/clean_moped.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
RSpec.shared_context 'clean mongo database' do
|
2
|
-
let(:mongo_session) {
|
2
|
+
let(:mongo_session) { create_connection }
|
3
3
|
|
4
4
|
before(:all) do
|
5
5
|
Mongo::Logger.logger.level = Logger::ERROR
|
6
|
-
mongo_session =
|
6
|
+
mongo_session = create_connection
|
7
7
|
mongo_session.database.drop
|
8
8
|
end
|
9
9
|
after { mongo_session.database.drop }
|
10
|
+
|
11
|
+
def create_connection
|
12
|
+
Mongo::Client.new(['127.0.0.1:27017'], database: 'rmodel_test')
|
13
|
+
end
|
10
14
|
end
|
data/spec/shared/clean_sequel.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
RSpec.shared_examples 'repository crud' do
|
2
|
-
before
|
3
|
-
|
4
|
-
|
2
|
+
before { stub_const('Thing', Struct.new(:id, :name)) }
|
3
|
+
|
4
|
+
let(:mapper) { mapper_klass.new(Thing).define_attributes(:name) }
|
5
|
+
|
6
|
+
subject { Rmodel::Repository.new(source, mapper) }
|
5
7
|
|
6
8
|
describe '#find' do
|
7
|
-
before {
|
9
|
+
before { subject.insert_one(Thing.new(1, 'chair')) }
|
8
10
|
|
9
11
|
context 'when an existent id is given' do
|
10
12
|
it 'returns the instance of correct type' do
|
@@ -23,36 +25,39 @@ RSpec.shared_examples 'repository crud' do
|
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
|
-
describe '#
|
28
|
+
describe '#insert_one(object)' do
|
27
29
|
context 'when the id is not provided' do
|
28
30
|
let(:thing) { Thing.new(nil, 'chair') }
|
29
31
|
|
32
|
+
before { subject.insert_one(thing) }
|
33
|
+
|
30
34
|
it 'sets the id before insert' do
|
31
|
-
subject.insert(thing)
|
32
35
|
expect(thing.id).not_to be_nil
|
33
36
|
end
|
34
37
|
|
35
38
|
it 'persists the object' do
|
36
|
-
subject.insert(thing)
|
37
39
|
expect(subject.find(thing.id).name).to eq 'chair'
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
41
43
|
context 'when the id is provided' do
|
42
|
-
let(:thing) { Thing.new(1000
|
44
|
+
let(:thing) { Thing.new(1000) }
|
43
45
|
|
44
46
|
it 'uses the existent id' do
|
45
|
-
subject.
|
47
|
+
subject.insert_one(thing)
|
46
48
|
expect(thing.id).to eq 1000
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
50
52
|
context 'when the given id already exists' do
|
51
53
|
let(:thing) { Thing.new }
|
52
|
-
|
54
|
+
|
55
|
+
before { subject.insert_one(thing) }
|
53
56
|
|
54
57
|
it 'raises the error' do
|
55
|
-
expect
|
58
|
+
expect do
|
59
|
+
subject.insert_one(thing)
|
60
|
+
end.to raise_error unique_constraint_error
|
56
61
|
end
|
57
62
|
end
|
58
63
|
end
|
@@ -71,12 +76,13 @@ RSpec.shared_examples 'repository crud' do
|
|
71
76
|
end
|
72
77
|
end
|
73
78
|
|
74
|
-
describe '#
|
79
|
+
describe '#destroy' do
|
75
80
|
let(:thing) { Thing.new }
|
81
|
+
|
76
82
|
before { subject.insert(thing) }
|
77
83
|
|
78
|
-
it '
|
79
|
-
subject.
|
84
|
+
it 'destroys the record' do
|
85
|
+
subject.destroy(thing)
|
80
86
|
expect(subject.find(thing.id)).to be_nil
|
81
87
|
end
|
82
88
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
RSpec.shared_examples 'initialization' do
|
2
|
+
before do
|
3
|
+
stub_const 'Thing', Struct.new(:id, :name)
|
4
|
+
|
5
|
+
stub_const 'ThingRepository', Class.new(Rmodel::Repository)
|
6
|
+
class ThingRepository
|
7
|
+
attr_reader :source, :mapper
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:source) { Object.new }
|
12
|
+
let(:mapper) { mapper_klass.new(Thing).define_attributes(:name) }
|
13
|
+
|
14
|
+
describe '#initialize(source, collection, mapper)' do
|
15
|
+
context 'when all constructor arguments are passed' do
|
16
|
+
it 'works!' do
|
17
|
+
expect do
|
18
|
+
ThingRepository.new(source, mapper)
|
19
|
+
end.not_to raise_error
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when source is nil' do
|
24
|
+
it 'raises the error' do
|
25
|
+
expect do
|
26
|
+
ThingRepository.new(nil, mapper)
|
27
|
+
end.to raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when mapper is nil' do
|
32
|
+
it 'raises the error' do
|
33
|
+
expect do
|
34
|
+
ThingRepository.new(source, nil)
|
35
|
+
end.to raise_error(ArgumentError)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
RSpec.shared_examples 'scopable repository' do
|
2
|
+
before do
|
3
|
+
stub_const 'Thing', Struct.new(:id, :a, :b)
|
4
|
+
stub_const 'ThingRepository', Class.new(Rmodel::Repository)
|
5
|
+
|
6
|
+
# Use the same scope syntax for Mongo and Sequel
|
7
|
+
class ThingRepository
|
8
|
+
scope :a_equals_2 do
|
9
|
+
where(a: 2)
|
10
|
+
end
|
11
|
+
|
12
|
+
scope :a_equals do |n|
|
13
|
+
where(a: n)
|
14
|
+
end
|
15
|
+
|
16
|
+
scope :b_equals do |n|
|
17
|
+
where(b: n)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:mapper) { mapper_klass.new(Thing).define_attributes(:a, :b) }
|
23
|
+
|
24
|
+
subject { ThingRepository.new(source, mapper) }
|
25
|
+
|
26
|
+
before do
|
27
|
+
create_database if defined?(create_database)
|
28
|
+
|
29
|
+
subject.insert(Thing.new(nil, 2, 3))
|
30
|
+
subject.insert(Thing.new(nil, 2, 4))
|
31
|
+
subject.insert(Thing.new(nil, 5, 6))
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '.scope' do
|
35
|
+
context 'when a scope w/o arguments is defined' do
|
36
|
+
it 'works!' do
|
37
|
+
expect(subject.fetch.a_equals_2.count).to eq 2
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns an array of instances of the appropriate class' do
|
41
|
+
expect(subject.fetch.a_equals_2.first).to be_an_instance_of Thing
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when a scope w/ arguments is defined' do
|
46
|
+
it 'works!' do
|
47
|
+
expect(subject.fetch.a_equals(2).count).to eq 2
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when two scopes are defined and chained' do
|
52
|
+
it 'works!' do
|
53
|
+
expect(subject.fetch.a_equals(2).b_equals(4).count).to eq 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when an unknown scope is used' do
|
58
|
+
it 'raises the NoMethodError' do
|
59
|
+
expect { subject.fetch.something }.to raise_error NoMethodError
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#fetch' do
|
65
|
+
describe '#delete_all' do
|
66
|
+
context 'when no scope is given' do
|
67
|
+
it 'removes all objects' do
|
68
|
+
subject.fetch.delete_all
|
69
|
+
expect(subject.fetch.count).to eq 0
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when the scope filters 2 objects from 3' do
|
74
|
+
it 'removes 2 objects' do
|
75
|
+
subject.fetch.a_equals_2.delete_all
|
76
|
+
expect(subject.fetch.count).to eq 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#destroy_all' do
|
82
|
+
context 'when no scope is given' do
|
83
|
+
it 'destroys all objects' do
|
84
|
+
subject.fetch.destroy_all
|
85
|
+
expect(subject.fetch.count).to eq 0
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'calls #destroy for each object' do
|
89
|
+
expect(subject).to receive(:destroy).exactly(3).times
|
90
|
+
subject.fetch.destroy_all
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'when the scope filters 2 objects from 3' do
|
95
|
+
it 'destroys 2 objects' do
|
96
|
+
subject.fetch.a_equals_2.destroy_all
|
97
|
+
expect(subject.fetch.count).to eq 1
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'calls #destroy for each object' do
|
101
|
+
expect(subject).to receive(:destroy).exactly(2).times
|
102
|
+
subject.fetch.a_equals_2.destroy_all
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#all' do
|
109
|
+
it 'gets all objects' do
|
110
|
+
objects = subject.all
|
111
|
+
expect(objects.length).to eq 3
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#delete_all' do
|
116
|
+
context 'when no scope is given' do
|
117
|
+
it 'deletes all objects' do
|
118
|
+
subject.delete_all
|
119
|
+
expect(subject.fetch.count).to eq 0
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe '#destroy_all' do
|
125
|
+
context 'when no scope is given' do
|
126
|
+
it 'deletes all objects' do
|
127
|
+
subject.destroy_all
|
128
|
+
expect(subject.fetch.count).to eq 0
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'calls #destroy for each object' do
|
132
|
+
expect(subject).to receive(:destroy).exactly(3).times
|
133
|
+
subject.destroy_all
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
RSpec.shared_examples 'sugarable repository' do
|
2
|
+
before { stub_const('Thing', Struct.new(:id, :name)) }
|
3
|
+
|
4
|
+
let(:mapper) { mapper_klass.new(Thing).define_attributes(:name) }
|
5
|
+
|
6
|
+
subject { Rmodel::Repository.new(source, mapper) }
|
7
|
+
|
8
|
+
describe '#find!(id)' do
|
9
|
+
context 'when an existent id is given' do
|
10
|
+
before { subject.insert(Thing.new(1)) }
|
11
|
+
|
12
|
+
it 'returns the right instance' do
|
13
|
+
expect(subject.find!(1)).not_to be_nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when a non-existent id is given' do
|
18
|
+
it 'raises NotFound' do
|
19
|
+
expect { subject.find!(1) }.to raise_error Rmodel::NotFound
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#insert(object1, object2, ...)' do
|
25
|
+
context 'when one object is provided' do
|
26
|
+
it 'inserts one object' do
|
27
|
+
subject.insert(Thing.new)
|
28
|
+
expect(subject.fetch.count).to eq 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when an array of objects is provided' do
|
33
|
+
it 'inserts all objects' do
|
34
|
+
subject.insert([Thing.new, Thing.new])
|
35
|
+
expect(subject.fetch.count).to eq 2
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'when objects are provided as many arguments' do
|
40
|
+
it 'inserts all objects' do
|
41
|
+
subject.insert(Thing.new, Thing.new)
|
42
|
+
expect(subject.fetch.count).to eq 2
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'save' do
|
48
|
+
let(:thing) { Thing.new }
|
49
|
+
|
50
|
+
context 'when a new object is given' do
|
51
|
+
it 'gets inserted' do
|
52
|
+
subject.save(thing)
|
53
|
+
expect(subject.find(thing.id)).not_to be_nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when an existent object is given' do
|
58
|
+
before { subject.insert(thing) }
|
59
|
+
|
60
|
+
it 'gets updated' do
|
61
|
+
thing.name = 'chair'
|
62
|
+
subject.save(thing)
|
63
|
+
expect(subject.find(thing.id).name).to eq 'chair'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
RSpec.shared_examples 'timestampable repository' do
|
2
|
+
let(:mapper) { mapper_klass.new(Thing).define_attribute(:name) }
|
3
|
+
|
4
|
+
subject { Rmodel::Repository.new(source, mapper) }
|
5
|
+
|
6
|
+
context 'when the entity object has timestamp attributes' do
|
7
|
+
before do
|
8
|
+
stub_const('Thing', Struct.new(:id, :name, :created_at, :updated_at))
|
9
|
+
mapper.define_attributes(:created_at, :updated_at)
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'when we insert(object)' do
|
13
|
+
context 'and created_at is already set' do
|
14
|
+
let(:thing) { Thing.new(nil, 'chair', Time.now) }
|
15
|
+
|
16
|
+
it "doesn't change the value of created_at" do
|
17
|
+
set_created_at = thing.created_at
|
18
|
+
subject.insert(thing)
|
19
|
+
expect(thing.created_at).to eq set_created_at
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'and created_at is not set yet' do
|
24
|
+
let(:thing) { Thing.new }
|
25
|
+
|
26
|
+
before { subject.insert(thing) }
|
27
|
+
|
28
|
+
it 'sets the value of created_at' do
|
29
|
+
expect(thing.created_at).not_to be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'saves created_at in the database' do
|
33
|
+
expect(subject.find(thing.id).created_at).not_to be_nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when we update(object)' do
|
39
|
+
let(:thing) { Thing.new }
|
40
|
+
|
41
|
+
before do
|
42
|
+
subject.insert(thing)
|
43
|
+
thing.name = 'chair'
|
44
|
+
subject.update(thing)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'sets the value of updated_at' do
|
48
|
+
expect(thing.updated_at).not_to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'saves the updated_at in a database' do
|
52
|
+
expect(subject.find(thing.id).updated_at).not_to be_nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when the Time.current method exists' do
|
57
|
+
let(:thing) { Thing.new }
|
58
|
+
|
59
|
+
before do
|
60
|
+
stub_const 'Time', Time
|
61
|
+
def Time.current
|
62
|
+
now
|
63
|
+
end
|
64
|
+
allow(Time).to receive(:current)
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'on insert' do
|
68
|
+
before { subject.insert(thing) }
|
69
|
+
|
70
|
+
it 'calls Time.current' do
|
71
|
+
expect(Time).to have_received(:current)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'on update' do
|
76
|
+
before do
|
77
|
+
subject.insert(thing)
|
78
|
+
subject.update(thing)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'calls Time.current' do
|
82
|
+
expect(Time).to have_received(:current).twice
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'when the entity has no attributes created_at and updated_at' do
|
89
|
+
before { stub_const 'Thing', Struct.new(:id, :name) }
|
90
|
+
|
91
|
+
let(:thing) { Thing.new }
|
92
|
+
|
93
|
+
it "doesn't raise errors" do
|
94
|
+
expect do
|
95
|
+
subject.insert(thing)
|
96
|
+
thing.name = 'chair'
|
97
|
+
subject.update(thing)
|
98
|
+
end.not_to raise_error
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when the repo class is inherited' do
|
103
|
+
before do
|
104
|
+
stub_const('Thing', Struct.new(:id, :name, :created_at, :updated_at))
|
105
|
+
mapper.define_attributes(:created_at, :updated_at)
|
106
|
+
stub_const 'ThingRepository', Class.new(Rmodel::Repository)
|
107
|
+
end
|
108
|
+
|
109
|
+
subject { ThingRepository.new(source, mapper) }
|
110
|
+
|
111
|
+
context 'on insert' do
|
112
|
+
let(:thing) { Thing.new }
|
113
|
+
|
114
|
+
before { subject.insert(thing) }
|
115
|
+
|
116
|
+
it 'sets the created_at' do
|
117
|
+
found = subject.find(thing.id)
|
118
|
+
expect(found.created_at).not_to be_nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'on update' do
|
123
|
+
let(:thing) { Thing.new }
|
124
|
+
|
125
|
+
before do
|
126
|
+
subject.insert(thing)
|
127
|
+
thing.name = 'chair'
|
128
|
+
subject.update(thing)
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'sets the updated_at' do
|
132
|
+
found = subject.find(thing.id)
|
133
|
+
expect(found.updated_at).not_to be_nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|