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