rmodel 0.3.1.dev → 0.4.0.dev

Sign up to get free protection for your applications and to get access to all the features.
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