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
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
|