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,50 +1,29 @@
1
- RSpec.describe Rmodel::Mongo::Repository do
2
- it_behaves_like 'repository crud' do
3
- include_context 'clean mongo database'
1
+ RSpec.describe 'Repository with MongoDB' do
2
+ include_context 'clean mongo database'
4
3
 
5
- before do
6
- stub_const('ThingRepository', Class.new(Rmodel::Mongo::Repository))
7
- end
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
- let(:factory) { Rmodel::Mongo::SimpleFactory.new(Thing, :name) }
10
- subject { ThingRepository.new(mongo_session, :things, factory) }
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 'clean mongo database'
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 'clean mongo database'
35
-
36
- before do
37
- stub_const('ThingRepository', Class.new(Rmodel::Mongo::Repository))
38
- end
19
+ include_context 'definitions'
20
+ end
39
21
 
40
- let(:repo_w_timestamps) do
41
- factory = Rmodel::Mongo::SimpleFactory.new(Thing, :name, :created_at, :updated_at)
42
- ThingRepository.new(mongo_session, :things, factory)
43
- end
22
+ it_behaves_like 'initialization' do
23
+ include_context 'definitions'
24
+ end
44
25
 
45
- let(:repo_wo_timestamps) do
46
- factory = Rmodel::Mongo::SimpleFactory.new(Thing, :name)
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 Rmodel::Sequel::Repository do
2
- it_behaves_like 'repository crud' do
3
- include_examples 'clean sequel database'
1
+ RSpec.describe 'Repository with Sequel' do
2
+ include_examples 'clean sequel database'
4
3
 
5
- before do
6
- create_database
7
- stub_const('ThingRepository', Class.new(Rmodel::Sequel::Repository))
8
- end
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
- let(:factory) { Rmodel::Sequel::SimpleFactory.new(Thing, :name) }
11
- subject { ThingRepository.new(sequel_conn, :things, factory) }
12
- let(:unique_constraint_error) { Sequel::UniqueConstraintViolation }
9
+ it_behaves_like 'repository crud' do
10
+ before { create_database }
13
11
 
14
- def insert_record(id, columns)
15
- record = columns.dup
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
- include_examples 'clean sequel database'
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
- include_examples 'clean sequel database'
22
+ before { create_database(true) }
23
+ include_context 'definitions'
24
+ end
37
25
 
38
- before do
39
- create_database(true)
40
- stub_const('ThingRepository', Class.new(Rmodel::Sequel::Repository))
41
- end
26
+ it_behaves_like 'initialization' do
27
+ before { create_database(true) }
28
+ include_context 'definitions'
29
+ end
42
30
 
43
- let(:repo_w_timestamps) do
44
- factory = Rmodel::Sequel::SimpleFactory.new(Thing, :name, :created_at, :updated_at)
45
- ThingRepository.new(sequel_conn, :things, factory)
46
- end
31
+ it_behaves_like 'scopable repository' do
32
+ include_context 'definitions'
47
33
 
48
- let(:repo_wo_timestamps) do
49
- factory = Rmodel::Sequel::SimpleFactory.new(Thing, :name)
50
- ThingRepository.new(sequel_conn, :things, factory)
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