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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +4 -0
- data/.travis.yml +4 -3
- data/README.md +119 -153
- data/Rakefile +1 -2
- data/examples/mongo_embedded.rb +27 -21
- data/examples/scopes.rb +28 -0
- data/examples/sql_repository.rb +14 -26
- data/examples/timestamps.rb +7 -10
- data/examples/webapp/models/task.rb +9 -0
- data/examples/webapp/repositories/task_repository.rb +14 -0
- data/examples/webapp/server.rb +25 -0
- data/examples/webapp/support/mappers.rb +11 -0
- data/examples/webapp/support/repositories.rb +12 -0
- data/examples/webapp/support/sources.rb +13 -0
- data/examples/webapp/views/index.erb +20 -0
- data/lib/rmodel.rb +11 -8
- data/lib/rmodel/array_mapper.rb +23 -0
- data/lib/rmodel/base_mapper.rb +56 -0
- data/lib/rmodel/dummy_mapper.rb +15 -0
- data/lib/rmodel/mongo/mapper.rb +11 -0
- data/lib/rmodel/mongo/source.rb +50 -0
- data/lib/rmodel/repository.rb +36 -0
- data/lib/rmodel/repository_ext/scopable.rb +44 -0
- data/lib/rmodel/repository_ext/sugarable.rb +35 -0
- data/lib/rmodel/repository_ext/timestampable.rb +29 -0
- data/lib/rmodel/scope.rb +31 -0
- data/lib/rmodel/sequel/mapper.rb +6 -0
- data/lib/rmodel/sequel/source.rb +43 -0
- data/lib/rmodel/uni_hash.rb +16 -0
- data/lib/rmodel/version.rb +1 -1
- data/rmodel.gemspec +9 -3
- data/spec/rmodel/array_mapper_spec.rb +43 -0
- data/spec/rmodel/mongo/mapper_spec.rb +154 -0
- data/spec/rmodel/mongo/repository_spec.rb +16 -37
- data/spec/rmodel/mongo/source_spec.rb +113 -0
- data/spec/rmodel/sequel/mapper_spec.rb +57 -0
- data/spec/rmodel/sequel/repository_spec.rb +27 -38
- data/spec/rmodel/sequel/source_spec.rb +121 -0
- data/spec/shared/base_mapper.rb +39 -0
- data/spec/shared/clean_moped.rb +6 -2
- data/spec/shared/clean_sequel.rb +1 -1
- data/spec/shared/{repository_ext → repository}/crud.rb +20 -14
- data/spec/shared/repository/initialization.rb +39 -0
- data/spec/shared/repository/scopable.rb +137 -0
- data/spec/shared/repository/sugarable.rb +67 -0
- data/spec/shared/repository/timestampable.rb +137 -0
- data/spec/spec_helper.rb +17 -18
- metadata +120 -54
- data/examples/advanced_creation_of_repository.rb +0 -15
- data/examples/user.rb +0 -48
- data/lib/rmodel/base/repository.rb +0 -12
- data/lib/rmodel/base/repository_ext/sugarable.rb +0 -17
- data/lib/rmodel/base/repository_ext/timestampable.rb +0 -19
- data/lib/rmodel/mongo/repository.rb +0 -62
- data/lib/rmodel/mongo/repository_ext/query.rb +0 -34
- data/lib/rmodel/mongo/repository_ext/queryable.rb +0 -34
- data/lib/rmodel/mongo/setup.rb +0 -17
- data/lib/rmodel/mongo/simple_factory.rb +0 -78
- data/lib/rmodel/sequel/repository.rb +0 -61
- data/lib/rmodel/sequel/repository_ext/query.rb +0 -34
- data/lib/rmodel/sequel/repository_ext/queryable.rb +0 -30
- data/lib/rmodel/sequel/setup.rb +0 -12
- data/lib/rmodel/sequel/simple_factory.rb +0 -28
- data/lib/rmodel/setup.rb +0 -34
- data/spec/rmodel/mongo/repository_ext/queryable_spec.rb +0 -103
- data/spec/rmodel/mongo/repository_initialize_spec.rb +0 -174
- data/spec/rmodel/mongo/simple_factory_spec.rb +0 -195
- data/spec/rmodel/sequel/repository_ext/queryable_spec.rb +0 -114
- data/spec/rmodel/sequel/repository_initialize_spec.rb +0 -118
- data/spec/rmodel/sequel/simple_factory_spec.rb +0 -55
- data/spec/rmodel/setup_spec.rb +0 -54
- data/spec/shared/repository_ext/sugarable.rb +0 -44
- data/spec/shared/repository_ext/timestampable.rb +0 -67
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1cabda8d90f129c91be844839240e35c667e866
|
4
|
+
data.tar.gz: 63d948217cbee488f8e93be4ef575ff838d9cabf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1eb0276bbb918397139567f1a3c081fa3de431eff3cbffb406f2aa0e524c4d862082d5d5155bad31ce2188d5cb66ea300272a3657edc4d8f1e3debbc54f03628
|
7
|
+
data.tar.gz: 85fa52294b5ee0e0cbed4fb23a443f995ef442114e9ec67738aabbf21b8d437506af32834b26de7fa64f9ff217096577ca4dea48972fb8afd9f61a9bbf691dcb
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -20,6 +20,7 @@ The main thoughts of it are:
|
|
20
20
|
|
21
21
|
* let you models be simple and independent of the persistent layer,
|
22
22
|
* be able to switch the persistent layer at any moment,
|
23
|
+
* be able to use different databases for different entities,
|
23
24
|
* keep the simplicity of the Active Record pattern by default,
|
24
25
|
* be able to implement any type of persistence: SQL, NoSQL, files, HTTP etc.
|
25
26
|
|
@@ -27,13 +28,13 @@ It consists of 3 major components:
|
|
27
28
|
|
28
29
|
1. **Entities**; ex.: User, Order etc.
|
29
30
|
2. **Repositories**, which are used to fetch, save and delete entities; ex.: UserRepository, OrderRepository
|
30
|
-
3. **
|
31
|
+
3. **Mappers**, which are used to serialize/deserialize entities to/from database tuples.
|
31
32
|
|
32
33
|
Basic implemented features:
|
33
34
|
|
34
|
-
1. CRUD operations: `find`, `insert`, `update`, `
|
35
|
-
2. Scopes: `
|
36
|
-
3. Query-based operations: `
|
35
|
+
1. CRUD operations: `find`, `insert`, `update`, `destroy`;
|
36
|
+
2. Scopes: `repo.fetch.recent.sorted`;
|
37
|
+
3. Query-based operations: `repo.fetch.recent.delete_all`.
|
37
38
|
|
38
39
|
## Installation
|
39
40
|
|
@@ -54,80 +55,78 @@ Or install it yourself as:
|
|
54
55
|
Let's define an entity
|
55
56
|
|
56
57
|
```ruby
|
57
|
-
|
58
|
-
attr_accessor :id, :name, :email
|
59
|
-
end
|
58
|
+
User = Struct.new(:id, :name, :email)
|
60
59
|
```
|
61
60
|
|
62
|
-
As you see it's a
|
61
|
+
As you see it's a PORO (Plain Old Ruby Objects), a class which inherits from nothing.
|
62
|
+
It must have either the zero-argument `#initialize` method or no `#initialize` at all.
|
63
63
|
|
64
64
|
Of course we need a repository to save users.
|
65
65
|
|
66
66
|
```ruby
|
67
67
|
require 'rmodel' # dont forget to require the gem
|
68
68
|
|
69
|
-
|
70
|
-
attr_accessor :id, :name, :email
|
71
|
-
end
|
72
|
-
|
73
|
-
class UserRepository < Rmodel::Mongo::Repository
|
74
|
-
end
|
69
|
+
User = Struct.new(:id, :name, :email)
|
75
70
|
|
76
|
-
|
77
|
-
|
78
|
-
The code above raises the exception *Client driver is not setup (ArgumentError)*. UserRepository derives from Rmodel::Mongo::Repository, which uses the ruby mongo driver to access the database. We must provide the appropriate connection options. To do this we use the following code:
|
71
|
+
DB = Mongo::Client.new(['localhost'], database: 'test')
|
72
|
+
source = Rmodel::Mongo::Source.new(DB, :users)
|
79
73
|
|
80
|
-
|
81
|
-
require 'rmodel'
|
74
|
+
mapper = Rmodel::Mongo::Mapper.new(User).define_attributes(:name, :email)
|
82
75
|
|
83
|
-
Rmodel.
|
84
|
-
client :default, { hosts: [ 'localhost' ], database: 'test' }
|
85
|
-
end
|
76
|
+
user_repository = Rmodel::Repository.new(source, mapper)
|
86
77
|
```
|
87
78
|
|
88
|
-
|
79
|
+
Here 3 main components of Rmodel are described:
|
89
80
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
The `simple_factory` class macro says that every database tuple will be straightforwardly converted to an instance of User with attributes :id, :name and :email. There is no need to specify :id, because it's required.
|
81
|
+
1. `source` points to the `users` collection withing MongoDB.
|
82
|
+
2. `mapper` is an example of a mapper class instance. It's methods
|
83
|
+
`#initialize` and `define_attributes` are used to declare the mapping rules
|
84
|
+
(User -> Hash and Hash -> User). It's a rather easy mapper. Every database
|
85
|
+
tuple is straightforwardly converted to an instance of User with attributes
|
86
|
+
:id, :name and :email. There is no need to specify :id.
|
87
|
+
3. Finally, `user_repository` takes `source` and `mapper` and makes all magic
|
88
|
+
about fetching and saving users from/to the database.
|
99
89
|
|
100
90
|
### CRUD
|
101
91
|
|
102
92
|
Let's create and insert several users.
|
103
93
|
|
104
94
|
```ruby
|
105
|
-
john = User.new('John', 'john@example.com')
|
106
|
-
bill = User.new('Bill', 'bill@example.com')
|
107
|
-
bob = User.new('Bob', 'bob@example.com')
|
95
|
+
john = User.new(nil, 'John', 'john@example.com')
|
96
|
+
bill = User.new(nil, 'Bill', 'bill@example.com')
|
97
|
+
bob = User.new(nil, 'Bob', 'bob@example.com')
|
108
98
|
|
109
|
-
|
110
|
-
|
111
|
-
|
99
|
+
user_repository.insert(john)
|
100
|
+
user_repository.insert(bill)
|
101
|
+
user_repository.insert(bob)
|
112
102
|
```
|
113
103
|
|
114
|
-
Now you can check you `test` database. There
|
104
|
+
Now you can check you `test` database. There must be 3 new users there. Print
|
105
|
+
the `john`. As you can see it's got the `@id`.
|
115
106
|
|
116
107
|
```ruby
|
117
108
|
p john
|
118
|
-
#<User
|
109
|
+
#<struct User id=BSON::ObjectId('...'), name="John", email="john@example.com">
|
119
110
|
```
|
120
111
|
|
121
|
-
Let's update John and
|
112
|
+
Let's update John and destroy Bob.
|
122
113
|
|
123
114
|
```ruby
|
124
115
|
john.name = 'John Smith'
|
125
|
-
|
116
|
+
user_repository.update(john)
|
117
|
+
|
118
|
+
user_repository.destroy(bob)
|
119
|
+
|
120
|
+
p user_repository.find(john.id) # <struct User id=BSON::ObjectId('...'), ...>
|
121
|
+
p user_repository.find(bob.id) # nil
|
122
|
+
```
|
126
123
|
|
127
|
-
|
124
|
+
The `insert` method is polysemantic. All options below are valid.
|
128
125
|
|
129
|
-
|
130
|
-
|
126
|
+
```ruby
|
127
|
+
repo.insert(object)
|
128
|
+
repo.insert([ object1, object2, object3 ])
|
129
|
+
repo.insert(object1, object2, object3)
|
131
130
|
```
|
132
131
|
|
133
132
|
### Scopes
|
@@ -135,9 +134,7 @@ p userRepository.find(bob.id) # nil
|
|
135
134
|
Scopes are defined inside the repository.
|
136
135
|
|
137
136
|
```ruby
|
138
|
-
class UserRepository < Rmodel::
|
139
|
-
simple_factory User, :name, :email
|
140
|
-
|
137
|
+
class UserRepository < Rmodel::Repository
|
141
138
|
scope :have_email do
|
142
139
|
where(email: { '$exists' => true })
|
143
140
|
end
|
@@ -147,24 +144,38 @@ class UserRepository < Rmodel::Mongo::Repository
|
|
147
144
|
end
|
148
145
|
end
|
149
146
|
|
150
|
-
|
147
|
+
repo = UserRepository.new(source, mapper)
|
148
|
+
|
149
|
+
p repo.fetch.start_with('b').to_a
|
151
150
|
```
|
152
151
|
|
153
152
|
Of course you can chain scopes.
|
154
153
|
|
155
154
|
```ruby
|
156
|
-
|
155
|
+
p repo.fetch.start_with('b').have_email.to_a
|
157
156
|
```
|
158
157
|
|
159
|
-
The result of the scope is Enumerable, so you can apply the #each method and
|
158
|
+
The result of the scope is Enumerable, so you can apply the #each method and
|
159
|
+
others (map, select etc).
|
160
160
|
|
161
|
-
Inside the scopes you can use any methods supported by the driver (database
|
161
|
+
Inside the scopes you can use any methods supported by the driver (database
|
162
|
+
connection). In our case we use Origin (https://github.com/mongoid/origin) as
|
163
|
+
a query builder for mongo.
|
162
164
|
|
163
165
|
Also it's possible to use scopes to run the multi-row operations.
|
164
166
|
|
165
167
|
```ruby
|
166
|
-
|
167
|
-
|
168
|
+
repo.fetch.have_email.delete_all # simply run the operation against the database
|
169
|
+
repo.fetch.have_email.destroy_all # extract users and run repo.destroy for the each one
|
170
|
+
p repo.fetch.count # 0
|
171
|
+
```
|
172
|
+
|
173
|
+
If you have no scopes then just call
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
repo.all
|
177
|
+
repo.delete_all
|
178
|
+
repo.destroy_all
|
168
179
|
```
|
169
180
|
|
170
181
|
### Timestamps
|
@@ -172,14 +183,20 @@ p userRepository.query.count # 0
|
|
172
183
|
Here is an example how to track the time, when the entity was created and updated.
|
173
184
|
|
174
185
|
```ruby
|
186
|
+
require 'rmodel'
|
187
|
+
|
188
|
+
DB = Mongo::Client.new(['localhost'], database: 'test')
|
189
|
+
source = Rmodel::Mongo::Source.new(DB, :things)
|
190
|
+
|
175
191
|
class Thing
|
176
192
|
attr_accessor :id, :name, :created_at, :updated_at
|
177
193
|
end
|
178
194
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
195
|
+
mapper = Rmodel::Mongo::Mapper.new(Thing)
|
196
|
+
.define_attribute(:name)
|
197
|
+
.define_timestamps
|
198
|
+
|
199
|
+
repo = Rmodel::Repository.new(source, mapper)
|
183
200
|
|
184
201
|
thing = Thing.new
|
185
202
|
thing.name = 'chair'
|
@@ -208,28 +225,6 @@ If the object has no not-nil id then it gets inserted. Otherwise it gets updated
|
|
208
225
|
The `find!` method works like the simple `find`
|
209
226
|
, but instead of nil it raises the Rmodel::NotFound error.
|
210
227
|
|
211
|
-
### Advanced creation of repository
|
212
|
-
|
213
|
-
```ruby
|
214
|
-
require 'rmodel'
|
215
|
-
|
216
|
-
class Thing
|
217
|
-
attr_accessor :id, :name
|
218
|
-
end
|
219
|
-
|
220
|
-
class ThingRepository < Rmodel::Mongo::Repository
|
221
|
-
end
|
222
|
-
|
223
|
-
client = Mongo::Client.new([ 'localhost:27017' ], database: 'test')
|
224
|
-
collection = :things
|
225
|
-
factory = Rmodel::Mongo::SimpleFactory.new(Thing, :name)
|
226
|
-
|
227
|
-
repo = ThingRepository.new(client, collection, factory)
|
228
|
-
repo.find(1)
|
229
|
-
```
|
230
|
-
|
231
|
-
The `factory` is an object, which has 2 methods: `#fromHash(hash)` and `#toHash(object)`.
|
232
|
-
|
233
228
|
### SQL repository
|
234
229
|
|
235
230
|
SQL amenities is based on the Sequel gem (http://sequel.jeremyevans.net/).
|
@@ -242,50 +237,38 @@ Below you can the the example how to setup Rmodel for any supported SQL database
|
|
242
237
|
```ruby
|
243
238
|
require 'rmodel'
|
244
239
|
|
245
|
-
|
246
|
-
|
247
|
-
end
|
240
|
+
DB = Sequel.connect(adapter: 'sqlite', database: 'rmodel_test.sqlite3')
|
241
|
+
source = Rmodel::Sequel::Source.new(DB, :things)
|
248
242
|
|
249
|
-
|
250
|
-
|
251
|
-
client.create_table :things do
|
243
|
+
DB.drop_table? :things
|
244
|
+
DB.create_table :things do
|
252
245
|
primary_key :id
|
253
246
|
String :name
|
254
247
|
Float :price
|
255
248
|
end
|
256
249
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
def initialize(name = nil, price = nil)
|
261
|
-
self.name = name
|
262
|
-
self.price = price
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
class ThingRepository < Rmodel::Sequel::Repository
|
267
|
-
simple_factory Thing, :name, :price
|
250
|
+
Thing = Struct.new :id, :name, :price
|
251
|
+
mapper = Rmodel::Sequel::Mapper.new(Thing).define_attributes(:name, :price)
|
268
252
|
|
253
|
+
class ThingRepository < Rmodel::Repository
|
269
254
|
scope :worth_more_than do |amount|
|
270
255
|
# use Sequel dataset filtering http://sequel.jeremyevans.net/rdoc/files/doc/dataset_filtering_rdoc.html
|
271
256
|
where { price >= amount }
|
272
257
|
end
|
273
258
|
end
|
274
259
|
|
275
|
-
repo = ThingRepository.new
|
276
|
-
repo.insert Thing.new('iPod', 200)
|
277
|
-
repo.insert Thing.new('iPhone', 300)
|
278
|
-
repo.insert Thing.new('iPad', 500)
|
260
|
+
repo = ThingRepository.new(source, mapper)
|
261
|
+
repo.insert Thing.new(nil, 'iPod', 200)
|
262
|
+
repo.insert Thing.new(nil, 'iPhone', 300)
|
263
|
+
repo.insert Thing.new(nil, 'iPad', 500)
|
279
264
|
|
280
|
-
p repo.
|
281
|
-
p repo.
|
282
|
-
p repo.query.worth_more_than(400).to_sql
|
265
|
+
p repo.fetch.count # 3
|
266
|
+
p repo.fetch.worth_more_than(400).count # 1
|
283
267
|
```
|
284
268
|
|
285
269
|
### Embedded documents in MongoDB
|
286
270
|
|
287
|
-
Let's assume that we have the `flats` collection and every documents reveals
|
288
|
-
the following structure.
|
271
|
+
Let's assume that we have the `flats` collection and every documents reveals the following structure.
|
289
272
|
|
290
273
|
```js
|
291
274
|
> db.flats.findOne()
|
@@ -325,66 +308,49 @@ the following structure.
|
|
325
308
|
}
|
326
309
|
```
|
327
310
|
|
328
|
-
We need a rather complicated
|
311
|
+
We need a rather complicated mapper to build such object. Here is the example how we can map nested embedded documents with Rmodel::Mongo::Mapper.
|
312
|
+
|
313
|
+
The idea is easy:
|
314
|
+
* define primitive mappers,
|
315
|
+
* use them in declaration of composite mappers.
|
329
316
|
|
330
317
|
```ruby
|
331
|
-
|
332
|
-
|
318
|
+
require 'rmodel'
|
319
|
+
|
320
|
+
DB = Mongo::Client.new(['localhost'], database: 'test')
|
321
|
+
source = Rmodel::Mongo::Source.new(DB, :flats)
|
322
|
+
|
323
|
+
Owner = Struct.new(:first_name, :last_name)
|
324
|
+
Bed = Struct.new(:type)
|
325
|
+
Room = Struct.new(:name, :square, :bed)
|
333
326
|
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
327
|
|
346
|
-
|
347
|
-
|
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.
|
328
|
+
owner_mapper = Rmodel::Mongo::Mapper.new(Owner)
|
329
|
+
.define_attributes(:first_name, :last_name)
|
354
330
|
|
355
|
-
|
331
|
+
bed_mapper = Rmodel::Mongo::Mapper.new(Bed).define_attribute(:type)
|
356
332
|
|
357
|
-
|
358
|
-
|
333
|
+
room_mapper = Rmodel::Mongo::Mapper.new(Room)
|
334
|
+
.define_attributes(:name, :square)
|
335
|
+
.define_attribute(:bed, bed_mapper)
|
359
336
|
|
360
|
-
Rmodel.
|
361
|
-
|
362
|
-
|
337
|
+
rooms_mapper = Rmodel::ArrayMapper.new(room_mapper)
|
338
|
+
flat_mapper = Rmodel::Mongo::Mapper.new(Flat)
|
339
|
+
.define_attribute(:address)
|
340
|
+
.define_attribute(:rooms, rooms_mapper)
|
341
|
+
.define_attribute(:owner, owner_mapper)
|
363
342
|
|
364
|
-
|
365
|
-
|
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
|
343
|
+
repo = Rmodel::Repository.new(source, flat_mapper)
|
344
|
+
repo.delete_all
|
379
345
|
|
380
346
|
flat = Flat.new
|
381
347
|
flat.address = 'Googleplex, Mountain View, California, U.S'
|
382
348
|
flat.rooms = [
|
383
|
-
Room.new(
|
384
|
-
Room.new(
|
385
|
-
Room.new(
|
349
|
+
Room.new('dining room', 150),
|
350
|
+
Room.new('sleeping room #1', 50, Bed.new('single')),
|
351
|
+
Room.new('sleeping room #2', 20, Bed.new('king-size'))
|
386
352
|
]
|
387
|
-
flat.owner = Owner.new(
|
353
|
+
flat.owner = Owner.new('John', 'Doe')
|
388
354
|
|
389
355
|
repo.insert(flat)
|
390
356
|
p repo.find(flat.id)
|