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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +4 -0
  4. data/.travis.yml +4 -3
  5. data/README.md +119 -153
  6. data/Rakefile +1 -2
  7. data/examples/mongo_embedded.rb +27 -21
  8. data/examples/scopes.rb +28 -0
  9. data/examples/sql_repository.rb +14 -26
  10. data/examples/timestamps.rb +7 -10
  11. data/examples/webapp/models/task.rb +9 -0
  12. data/examples/webapp/repositories/task_repository.rb +14 -0
  13. data/examples/webapp/server.rb +25 -0
  14. data/examples/webapp/support/mappers.rb +11 -0
  15. data/examples/webapp/support/repositories.rb +12 -0
  16. data/examples/webapp/support/sources.rb +13 -0
  17. data/examples/webapp/views/index.erb +20 -0
  18. data/lib/rmodel.rb +11 -8
  19. data/lib/rmodel/array_mapper.rb +23 -0
  20. data/lib/rmodel/base_mapper.rb +56 -0
  21. data/lib/rmodel/dummy_mapper.rb +15 -0
  22. data/lib/rmodel/mongo/mapper.rb +11 -0
  23. data/lib/rmodel/mongo/source.rb +50 -0
  24. data/lib/rmodel/repository.rb +36 -0
  25. data/lib/rmodel/repository_ext/scopable.rb +44 -0
  26. data/lib/rmodel/repository_ext/sugarable.rb +35 -0
  27. data/lib/rmodel/repository_ext/timestampable.rb +29 -0
  28. data/lib/rmodel/scope.rb +31 -0
  29. data/lib/rmodel/sequel/mapper.rb +6 -0
  30. data/lib/rmodel/sequel/source.rb +43 -0
  31. data/lib/rmodel/uni_hash.rb +16 -0
  32. data/lib/rmodel/version.rb +1 -1
  33. data/rmodel.gemspec +9 -3
  34. data/spec/rmodel/array_mapper_spec.rb +43 -0
  35. data/spec/rmodel/mongo/mapper_spec.rb +154 -0
  36. data/spec/rmodel/mongo/repository_spec.rb +16 -37
  37. data/spec/rmodel/mongo/source_spec.rb +113 -0
  38. data/spec/rmodel/sequel/mapper_spec.rb +57 -0
  39. data/spec/rmodel/sequel/repository_spec.rb +27 -38
  40. data/spec/rmodel/sequel/source_spec.rb +121 -0
  41. data/spec/shared/base_mapper.rb +39 -0
  42. data/spec/shared/clean_moped.rb +6 -2
  43. data/spec/shared/clean_sequel.rb +1 -1
  44. data/spec/shared/{repository_ext → repository}/crud.rb +20 -14
  45. data/spec/shared/repository/initialization.rb +39 -0
  46. data/spec/shared/repository/scopable.rb +137 -0
  47. data/spec/shared/repository/sugarable.rb +67 -0
  48. data/spec/shared/repository/timestampable.rb +137 -0
  49. data/spec/spec_helper.rb +17 -18
  50. metadata +120 -54
  51. data/examples/advanced_creation_of_repository.rb +0 -15
  52. data/examples/user.rb +0 -48
  53. data/lib/rmodel/base/repository.rb +0 -12
  54. data/lib/rmodel/base/repository_ext/sugarable.rb +0 -17
  55. data/lib/rmodel/base/repository_ext/timestampable.rb +0 -19
  56. data/lib/rmodel/mongo/repository.rb +0 -62
  57. data/lib/rmodel/mongo/repository_ext/query.rb +0 -34
  58. data/lib/rmodel/mongo/repository_ext/queryable.rb +0 -34
  59. data/lib/rmodel/mongo/setup.rb +0 -17
  60. data/lib/rmodel/mongo/simple_factory.rb +0 -78
  61. data/lib/rmodel/sequel/repository.rb +0 -61
  62. data/lib/rmodel/sequel/repository_ext/query.rb +0 -34
  63. data/lib/rmodel/sequel/repository_ext/queryable.rb +0 -30
  64. data/lib/rmodel/sequel/setup.rb +0 -12
  65. data/lib/rmodel/sequel/simple_factory.rb +0 -28
  66. data/lib/rmodel/setup.rb +0 -34
  67. data/spec/rmodel/mongo/repository_ext/queryable_spec.rb +0 -103
  68. data/spec/rmodel/mongo/repository_initialize_spec.rb +0 -174
  69. data/spec/rmodel/mongo/simple_factory_spec.rb +0 -195
  70. data/spec/rmodel/sequel/repository_ext/queryable_spec.rb +0 -114
  71. data/spec/rmodel/sequel/repository_initialize_spec.rb +0 -118
  72. data/spec/rmodel/sequel/simple_factory_spec.rb +0 -55
  73. data/spec/rmodel/setup_spec.rb +0 -54
  74. data/spec/shared/repository_ext/sugarable.rb +0 -44
  75. data/spec/shared/repository_ext/timestampable.rb +0 -67
@@ -1,10 +1,14 @@
1
1
  RSpec.shared_context 'clean mongo database' do
2
- let(:mongo_session) { Mongo::Client.new([ '127.0.0.1:27017' ], database: 'rmodel_test') }
2
+ let(:mongo_session) { create_connection }
3
3
 
4
4
  before(:all) do
5
5
  Mongo::Logger.logger.level = Logger::ERROR
6
- mongo_session = Mongo::Client.new([ '127.0.0.1:27017' ], database: 'rmodel_test')
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
@@ -1,5 +1,5 @@
1
1
  RSpec.shared_context 'clean sequel database' do
2
- let(:sequel_conn) { Sequel.connect(adapter: 'sqlite', database: 'rmodel_test.sqlite3') }
2
+ let(:sequel_conn) { Sequel.sqlite('rmodel_test.sqlite3') }
3
3
 
4
4
  before(:all) do
5
5
  Mongo::Logger.logger.level = Logger::ERROR
@@ -1,10 +1,12 @@
1
1
  RSpec.shared_examples 'repository crud' do
2
- before do
3
- stub_const('Thing', Struct.new(:id, :name))
4
- end
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 { insert_record(1, name: 'chair') }
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 '#insert' do
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, 'chair') }
44
+ let(:thing) { Thing.new(1000) }
43
45
 
44
46
  it 'uses the existent id' do
45
- subject.insert(thing)
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
- before { subject.insert(thing) }
54
+
55
+ before { subject.insert_one(thing) }
53
56
 
54
57
  it 'raises the error' do
55
- expect { subject.insert(thing) }.to raise_error unique_constraint_error
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 '#remove' do
79
+ describe '#destroy' do
75
80
  let(:thing) { Thing.new }
81
+
76
82
  before { subject.insert(thing) }
77
83
 
78
- it 'removes the record' do
79
- subject.remove(thing)
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