rmodel 0.3.1.dev → 0.4.0.dev

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0f799f5512a154a18435f2fd1b32f5143c3a520a
4
- data.tar.gz: 9452ccc5d051436556d50db6dbc5add3256a5189
3
+ metadata.gz: bfd28def37943474200080f54ed361a5a7ff742c
4
+ data.tar.gz: 09936b026f1c1d5b232cafe8ccce68665726a0eb
5
5
  SHA512:
6
- metadata.gz: f1b7e7218d9ac0e476d71bc9c0533b784d3a0de490efcbbe64f5b843aedfd378ca1c729c72a5a1ca401bfee59fee2268b0e243332423ae87b82aad8743379f43
7
- data.tar.gz: 09a28541977e304e63b83a1d57ceb42e391f1b060404e548e4121e83a4c1a408960b5a14dc6f844d54c27cdd12d0d7575e7ce24b2009c6345a9fc474c79abd0f
6
+ metadata.gz: d7857a21445b42cab34bb02205949cbb3204cd245fd59508204cfca4cfa6c74753f698d1d5ce56596559dc03dbbe5abd58ab6092ca486332b426c9e887d6fa94
7
+ data.tar.gz: fd441d8e206b3d21eb0cc636c6c178e319f7fe05114b5924e44d0440ff1f6b572735642e4080a255729580a21a6353b960ef7f2520664f62def8d8632ba0273f
data/README.md CHANGED
@@ -10,12 +10,13 @@
10
10
  * [Sugar methods](#sugar-methods)
11
11
  * [Advanced creation of repository](#advanced-creation-of-repository)
12
12
  * [SQL repository](#sql-repository)
13
+ * [Embedded documents in MongoDB](#embedded-documents-in-mongodb)
13
14
 
14
15
  Rmodel is an ORM library, which tends to follow the SOLID principles.
15
16
 
16
17
  **Currently works with MongoDB and SQL databases supported by Sequel.**
17
18
 
18
- The main thoughts behind it are:
19
+ The main thoughts of it are:
19
20
 
20
21
  * let you models be simple and independent of the persistent layer,
21
22
  * be able to switch the persistent layer at any moment,
@@ -32,7 +33,7 @@ Basic implemented features:
32
33
 
33
34
  1. CRUD operations: `find`, `insert`, `update`, `remove`;
34
35
  2. Scopes: `userRepository.query.recent.sorted`
35
- 3. Based on query operations: `userRepository.query.recent.remove`
36
+ 3. Query-based operations: `userRepository.query.recent.remove`
36
37
 
37
38
  ## Installation
38
39
 
@@ -281,6 +282,114 @@ p repo.query.worth_more_than(400).count # 1
281
282
  p repo.query.worth_more_than(400).to_sql
282
283
  ```
283
284
 
285
+ ### Embedded documents in MongoDB
286
+
287
+ Let's assume that we have the `flats` collection and every documents reveals
288
+ the following structure.
289
+
290
+ ```js
291
+ > db.flats.findOne()
292
+ {
293
+ "_id" : ObjectId("5632910ee5fcc32d40000000"),
294
+ "address" : "Googleplex, Mountain View, California, U.S",
295
+ "rooms" : [
296
+ {
297
+ "name" : "dining room",
298
+ "square" : 150,
299
+ "_id" : ObjectId("5632910ee5fcc32d40000001")
300
+ },
301
+ {
302
+ "name" : "sleeping room #1",
303
+ "square" : 50,
304
+ "_id" : ObjectId("5632910ee5fcc32d40000002"),
305
+ "bed" : {
306
+ "type" : "single",
307
+ "_id" : ObjectId("5632910ee5fcc32d40000003")
308
+ }
309
+ },
310
+ {
311
+ "name" : "sleeping room #2",
312
+ "square" : 20,
313
+ "_id" : ObjectId("5632910ee5fcc32d40000004"),
314
+ "bed" : {
315
+ "type" : "king-size",
316
+ "_id" : ObjectId("5632910ee5fcc32d40000005")
317
+ }
318
+ }
319
+ ],
320
+ "owner" : {
321
+ "first_name" : "John",
322
+ "last_name" : "Doe",
323
+ "_id" : ObjectId("5632910ee5fcc32d40000006")
324
+ }
325
+ }
326
+ ```
327
+
328
+ We need a rather complicated factory to build such object. Here is the example how we can map nested embedded documents with SimpleFactory.
329
+
330
+ ```ruby
331
+ Owner = Struct.new(:id, :first_name, :last_name)
332
+ Room = Struct.new(:id, :name, :square, :bed)
333
+ Flat = Struct.new(:id, :address, :rooms, :owner)
334
+ Bed = Struct.new(:id, :type)
335
+
336
+ class FlatRepository < Rmodel::Mongo::Repository
337
+ simple_factory Flat, :address do
338
+ embeds_many :rooms, simple_factory(Room, :name, :square) do
339
+ embeds_one :bed, simple_factory(Bed, :type)
340
+ end
341
+ embeds_one :owner, simple_factory(Owner, :first_name, :last_name)
342
+ end
343
+ end
344
+ ```
345
+
346
+ 1. The row `simple_factory Flat, :address` create a factory for Flat.
347
+ * It takes a block, where the detailed declaration goes.
348
+ * `embeds_many` and `embeds_one` are methods of the created factory.
349
+ 2. The row `embeds_many :rooms, simple_factory(Room, :name, :square)` describes the embedded array of rooms within the flat.
350
+ * The first argument `:room` is the name of the flat attribute (`flat.rooms`).
351
+ * The second argument is another simple factory for the Room class.
352
+ * The row `embeds_many :rooms` takes the block, that described nested embedded documents for the Room factory.
353
+ 3. etc.
354
+
355
+ The full example.
356
+
357
+ ```ruby
358
+ require 'rmodel'
359
+
360
+ Rmodel.setup do
361
+ client :default, { hosts: [ 'localhost'], database: 'test' }
362
+ end
363
+
364
+ Owner = Struct.new(:id, :first_name, :last_name)
365
+ Room = Struct.new(:id, :name, :square, :bed)
366
+ Flat = Struct.new(:id, :address, :rooms, :owner)
367
+ Bed = Struct.new(:id, :type)
368
+
369
+ class FlatRepository < Rmodel::Mongo::Repository
370
+ simple_factory Flat, :address do
371
+ embeds_many :rooms, simple_factory(Room, :name, :square) do
372
+ embeds_one :bed, simple_factory(Bed, :type)
373
+ end
374
+ embeds_one :owner, simple_factory(Owner, :first_name, :last_name)
375
+ end
376
+ end
377
+ repo = FlatRepository.new
378
+ repo.query.remove
379
+
380
+ flat = Flat.new
381
+ flat.address = 'Googleplex, Mountain View, California, U.S'
382
+ flat.rooms = [
383
+ Room.new(nil, 'dining room', 150),
384
+ Room.new(nil, 'sleeping room #1', 50, Bed.new(nil, 'single')),
385
+ Room.new(nil, 'sleeping room #2', 20, Bed.new(nil, 'king-size'))
386
+ ]
387
+ flat.owner = Owner.new(nil, 'John', 'Doe')
388
+
389
+ repo.insert(flat)
390
+ p repo.find(flat.id)
391
+ ```
392
+
284
393
  ## Contributing
285
394
 
286
395
  1. Fork it ( https://github.com/alexei-lexx/rmodel/fork )
@@ -0,0 +1,33 @@
1
+ require 'rmodel'
2
+
3
+ Rmodel.setup do
4
+ client :default, { hosts: [ 'localhost'], database: 'test' }
5
+ end
6
+
7
+ Owner = Struct.new(:id, :first_name, :last_name)
8
+ Room = Struct.new(:id, :name, :square, :bed)
9
+ Flat = Struct.new(:id, :address, :rooms, :owner)
10
+ Bed = Struct.new(:id, :type)
11
+
12
+ class FlatRepository < Rmodel::Mongo::Repository
13
+ simple_factory Flat, :address do
14
+ embeds_many :rooms, simple_factory(Room, :name, :square) do
15
+ embeds_one :bed, simple_factory(Bed, :type)
16
+ end
17
+ embeds_one :owner, simple_factory(Owner, :first_name, :last_name)
18
+ end
19
+ end
20
+ repo = FlatRepository.new
21
+ repo.query.remove
22
+
23
+ flat = Flat.new
24
+ flat.address = 'Googleplex, Mountain View, California, U.S'
25
+ flat.rooms = [
26
+ Room.new(nil, 'dining room', 150),
27
+ Room.new(nil, 'sleeping room #1', 50, Bed.new(nil, 'single')),
28
+ Room.new(nil, 'sleeping room #2', 20, Bed.new(nil, 'king-size'))
29
+ ]
30
+ flat.owner = Owner.new(nil, 'John', 'Doe')
31
+
32
+ repo.insert(flat)
33
+ p repo.find(flat.id)
@@ -54,8 +54,8 @@ module Rmodel::Mongo
54
54
  end
55
55
  end
56
56
 
57
- def simple_factory(klass, *attributes)
58
- @setting_factory = SimpleFactory.new(klass, *attributes)
57
+ def simple_factory(klass, *attributes, &block)
58
+ @setting_factory = SimpleFactory.new(klass, *attributes, &block)
59
59
  end
60
60
  end
61
61
  end
@@ -1,8 +1,11 @@
1
1
  module Rmodel::Mongo
2
2
  class SimpleFactory
3
- def initialize(klass, *attributes)
3
+ def initialize(klass, *attributes, &block)
4
4
  @klass = klass
5
5
  @attributes = attributes
6
+ @embeds_many = {}
7
+ @embeds_one = {}
8
+ instance_eval(&block) if block
6
9
  end
7
10
 
8
11
  def fromHash(hash)
@@ -11,6 +14,20 @@ module Rmodel::Mongo
11
14
  @attributes.each do |attribute|
12
15
  object.public_send "#{attribute}=", hash[attribute.to_s]
13
16
  end
17
+ @embeds_many.each do |attribute, factory|
18
+ if hash[attribute.to_s]
19
+ object.public_send "#{attribute}=", []
20
+ hash[attribute.to_s].each do |sub_hash|
21
+ object.public_send(attribute) << factory.fromHash(sub_hash)
22
+ end
23
+ end
24
+ end
25
+ @embeds_one.each do |attribute, factory|
26
+ sub_hash = hash[attribute.to_s]
27
+ if sub_hash
28
+ object.public_send "#{attribute}=", factory.fromHash(sub_hash)
29
+ end
30
+ end
14
31
  object
15
32
  end
16
33
 
@@ -22,7 +39,40 @@ module Rmodel::Mongo
22
39
  if id_included
23
40
  hash['_id'] = object.id
24
41
  end
42
+ @embeds_many.each do |attribute, factory|
43
+ hash[attribute.to_s] = []
44
+ sub_objects = object.public_send(attribute)
45
+ if sub_objects
46
+ sub_objects.each do |sub_object|
47
+ sub_object.id ||= BSON::ObjectId.new
48
+ hash[attribute.to_s] << factory.toHash(sub_object, true)
49
+ end
50
+ end
51
+ end
52
+ @embeds_one.each do |attribute, factory|
53
+ sub_object = object.public_send(attribute)
54
+ if sub_object
55
+ sub_object.id ||= BSON::ObjectId.new
56
+ hash[attribute.to_s] = factory.toHash(sub_object, true)
57
+ end
58
+ end
25
59
  hash
26
60
  end
61
+
62
+ private
63
+
64
+ def embeds_many(attribute, factory, &block)
65
+ factory.instance_eval(&block) if block
66
+ @embeds_many[attribute.to_sym] = factory
67
+ end
68
+
69
+ def embeds_one(attribute, factory, &block)
70
+ factory.instance_eval(&block) if block
71
+ @embeds_one[attribute.to_sym] = factory
72
+ end
73
+
74
+ def simple_factory(klass, *attributes, &block)
75
+ self.class.new(klass, *attributes, &block)
76
+ end
27
77
  end
28
78
  end
@@ -1,3 +1,3 @@
1
1
  module Rmodel
2
- VERSION = "0.3.1.dev"
2
+ VERSION = "0.4.0.dev"
3
3
  end
@@ -1,5 +1,6 @@
1
1
  RSpec.describe Rmodel::Mongo::Repository do
2
2
  before do
3
+ Mongo::Logger.logger.level = Logger::ERROR
3
4
  stub_const('User', Struct.new(:id, :name, :email))
4
5
  end
5
6
 
@@ -108,7 +109,7 @@ RSpec.describe Rmodel::Mongo::Repository do
108
109
  end
109
110
  end
110
111
 
111
- describe '.simple_factory(klass, attribute1, attribute2, ...)' do
112
+ describe '.simple_factory(klass, attribute1, attribute2, ..., &block)' do
112
113
  subject { UserRepository.new }
113
114
 
114
115
  before do
@@ -142,6 +143,18 @@ RSpec.describe Rmodel::Mongo::Repository do
142
143
  }.to raise_error ArgumentError
143
144
  end
144
145
  end
146
+
147
+ context 'when a block is given' do
148
+ it 'evaluates the block within the context of the factory' do
149
+ tmp = nil
150
+ stub_const('UserRepository', Class.new(Rmodel::Mongo::Repository) {
151
+ simple_factory User, :name, :email do
152
+ tmp = self
153
+ end
154
+ })
155
+ expect(tmp).to be_an_instance_of Rmodel::Mongo::SimpleFactory
156
+ end
157
+ end
145
158
  end
146
159
 
147
160
  describe '#initialize(client, collection, factory)' do
@@ -1,41 +1,123 @@
1
1
  RSpec.describe Rmodel::Mongo::SimpleFactory do
2
- context 'when the User(id, name, email) class is defined' do
3
- before { stub_const('User', Struct.new(:id, :name, :email)) }
2
+ context 'when the Thing(id, name, price, parts, owner) class is defined' do
3
+ before do
4
+ stub_const('Thing', Struct.new(:id, :name, :price, :parts, :owner))
5
+ stub_const('Part', Struct.new(:id, :name, :producer))
6
+ stub_const('Producer', Struct.new(:id, :country))
7
+ stub_const('Owner', Struct.new(:id, :full_name, :phones))
8
+ stub_const('Phone', Struct.new(:id, :number))
9
+ end
10
+
11
+ subject do
12
+ described_class.new(Thing, :name, :price) do
13
+ embeds_many :parts, simple_factory(Part, :name) do
14
+ embeds_one :producer, simple_factory(Producer, :country)
15
+ end
16
+ embeds_one :owner, simple_factory(Owner, :full_name) do
17
+ embeds_many :phones, simple_factory(Phone, :number)
18
+ end
19
+ end
20
+ end
4
21
 
5
- subject(:factory) { described_class.new(User, :name, :email) }
22
+ describe '#fromHash(hash)' do
23
+ let(:result) { subject.fromHash(hash) }
6
24
 
7
- describe '#fromHash' do
8
- context 'when the hash with _id, name and email is given' do
9
- let(:hash) { { '_id' => 1, 'name' => 'John', 'email' => 'john@example.com' } }
10
- let(:result) { factory.fromHash(hash) }
25
+ context 'when the hash with _id, name and price is given' do
26
+ let(:hash) { { '_id' => 1, 'name' => 'chair', 'price' => 100 } }
11
27
 
12
- it 'returns an instance of User' do
13
- expect(result).to be_an_instance_of User
28
+ it 'returns an instance of Thing' do
29
+ expect(result).to be_an_instance_of Thing
14
30
  end
15
31
 
16
32
  it 'sets the attributes correctly' do
17
- expect(result.name).to eq 'John'
18
- expect(result.email).to eq 'john@example.com'
33
+ expect(result.name).to eq 'chair'
34
+ expect(result.price).to eq 100
19
35
  end
20
36
 
21
- it 'sets the User#id correctly' do
37
+ it 'sets the Thing#id correctly' do
22
38
  expect(result.id).to eq 1
23
39
  end
40
+
41
+ it 'leaves <many embedded> nil' do
42
+ expect(result.parts).to be_nil
43
+ end
44
+
45
+ it 'leaves <one embedded> nil' do
46
+ expect(result.owner).to be_nil
47
+ end
48
+ end
49
+
50
+ context 'when the hash contains many parts' do
51
+ let(:hash) do
52
+ {
53
+ 'parts' => [
54
+ { '_id' => 1, 'name' => 'back', 'producer' => { '_id' => 10, 'country' => 'UK' } },
55
+ { '_id' => 2, 'name' => 'leg' }
56
+ ]
57
+ }
58
+ end
59
+
60
+ it 'maps subdocuments to <many embedded>' do
61
+ expect(result.parts.length).to eq 2
62
+
63
+ expect(result.parts[0]).to be_an_instance_of Part
64
+ expect(result.parts[0].id).to eq 1
65
+ expect(result.parts[0].name).to eq 'back'
66
+
67
+ expect(result.parts[0].producer).to be_an_instance_of Producer
68
+ expect(result.parts[0].producer.id).to eq 10
69
+ expect(result.parts[0].producer.country).to eq 'UK'
70
+
71
+ expect(result.parts[1]).to be_an_instance_of Part
72
+ expect(result.parts[1].id).to eq 2
73
+ expect(result.parts[1].name).to eq 'leg'
74
+ expect(result.parts[1].producer).to be_nil
75
+ end
76
+ end
77
+
78
+ context 'when the hash contains one owner' do
79
+ let(:hash) do
80
+ {
81
+ 'owner' => {
82
+ '_id' => 3,
83
+ 'full_name' => 'John Doe',
84
+ 'phones' => [
85
+ { '_id' => 20, 'number' => '+1111111111' },
86
+ { '_id' => 21, 'number' => '+2222222222' }
87
+ ]
88
+ }
89
+ }
90
+ end
91
+
92
+ it 'maps subdocument to <one embedded>' do
93
+ expect(result.owner).to be_an_instance_of Owner
94
+ expect(result.owner.id).to eq 3
95
+ expect(result.owner.full_name).to eq 'John Doe'
96
+
97
+ expect(result.owner.phones.length).to eq 2
98
+
99
+ expect(result.owner.phones[0].id).to eq 20
100
+ expect(result.owner.phones[0].number).to eq '+1111111111'
101
+
102
+ expect(result.owner.phones[1].id).to eq 21
103
+ expect(result.owner.phones[1].number).to eq '+2222222222'
104
+ end
24
105
  end
25
106
  end
26
107
 
27
- describe '#toHash' do
28
- let(:user) { User.new(1, 'John', 'john@example.com') }
108
+ describe '#toHash(object, id_included)' do
109
+ let(:thing) { Thing.new(1, 'chair', 100) }
110
+
29
111
  context 'when id_included is false' do
30
- let(:result) { factory.toHash(user, false) }
112
+ let(:result) { subject.toHash(thing, false) }
31
113
 
32
114
  it 'returns an instance of Hash' do
33
115
  expect(result).to be_an_instance_of Hash
34
116
  end
35
117
 
36
118
  it 'sets the keys correctly' do
37
- expect(result['name']).to eq 'John'
38
- expect(result['email']).to eq 'john@example.com'
119
+ expect(result['name']).to eq 'chair'
120
+ expect(result['price']).to eq 100
39
121
  end
40
122
 
41
123
  it 'has no the "_id" key' do
@@ -44,12 +126,70 @@ RSpec.describe Rmodel::Mongo::SimpleFactory do
44
126
  end
45
127
 
46
128
  context 'when id_included is true' do
47
- let(:result) { factory.toHash(user, true) }
129
+ let(:result) { subject.toHash(thing, true) }
48
130
 
49
131
  it 'sets the "_id" key' do
50
132
  expect(result['_id']).to eq 1
51
133
  end
52
134
  end
135
+
136
+ context 'when the object has <many embedded>' do
137
+ let(:thing) do
138
+ Thing.new(1, 'chair', 100, [
139
+ Part.new(1, 'back', Producer.new(10, 'UK')),
140
+ Part.new(2, 'leg')
141
+ ])
142
+ end
143
+ let(:result) { subject.toHash(thing, true) }
144
+
145
+ it 'maps <many embedded> to subdocuments' do
146
+ expect(result['parts'].length).to eq 2
147
+
148
+ expect(result['parts'][0]['_id']).to eq 1
149
+ expect(result['parts'][0]['name']).to eq 'back'
150
+
151
+ expect(result['parts'][0]['producer']['_id']).to eq 10
152
+ expect(result['parts'][0]['producer']['country']).to eq 'UK'
153
+
154
+ expect(result['parts'][1]['_id']).to eq 2
155
+ expect(result['parts'][1]['name']).to eq 'leg'
156
+ end
157
+ end
158
+
159
+ context 'when the object has <one embedded>' do
160
+ let(:thing) do
161
+ Thing.new(1, 'chair', 100, nil, Owner.new(3, 'John Doe', [
162
+ Phone.new(20, '+1111111111'),
163
+ Phone.new(21, '+2222222222')
164
+ ]))
165
+ end
166
+ let(:result) { subject.toHash(thing, true) }
167
+
168
+ it 'maps <one embedded> to the subdocument' do
169
+ expect(result['owner']['_id']).to eq 3
170
+ expect(result['owner']['full_name']).to eq 'John Doe'
171
+
172
+ expect(result['owner']['phones'].length).to eq 2
173
+
174
+ expect(result['owner']['phones'][0]['_id']).to eq 20
175
+ expect(result['owner']['phones'][0]['number']).to eq '+1111111111'
176
+
177
+ expect(result['owner']['phones'][1]['_id']).to eq 21
178
+ expect(result['owner']['phones'][1]['number']).to eq '+2222222222'
179
+ end
180
+ end
181
+ end
182
+
183
+ describe '#initialize(..., &block)' do
184
+ context 'when a block is given' do
185
+ it 'passes self to the block' do
186
+ tmp = nil
187
+ factory = described_class.new(Thing, :name) do
188
+ tmp = self
189
+ end
190
+ expect(tmp).to be factory
191
+ end
192
+ end
53
193
  end
54
194
  end
55
195
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rmodel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1.dev
4
+ version: 0.4.0.dev
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexei
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-28 00:00:00.000000000 Z
11
+ date: 2015-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mongo
@@ -137,6 +137,7 @@ files:
137
137
  - README.md
138
138
  - Rakefile
139
139
  - examples/advanced_creation_of_repository.rb
140
+ - examples/mongo_embedded.rb
140
141
  - examples/sql_repository.rb
141
142
  - examples/timestamps.rb
142
143
  - examples/user.rb