ohm 1.0.0.alpha2 → 1.0.0.rc1
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.
- data/{README.markdown → README.md} +212 -196
- data/lib/ohm.rb +185 -27
- data/lib/ohm/json.rb +24 -0
- data/lib/ohm/transaction.rb +3 -3
- data/test/connection.rb +32 -2
- data/test/filtering.rb +10 -0
- data/test/helper.rb +1 -1
- data/test/json.rb +13 -1
- data/test/list.rb +69 -0
- data/test/model.rb +22 -0
- data/test/transactions.rb +13 -1
- metadata +14 -12
@@ -123,47 +123,51 @@ the example below:
|
|
123
123
|
|
124
124
|
### Example
|
125
125
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
126
|
+
```ruby
|
127
|
+
class Event < Ohm::Model
|
128
|
+
attribute :name
|
129
|
+
reference :venue, Venue
|
130
|
+
set :participants, Person
|
131
|
+
counter :votes
|
131
132
|
|
132
|
-
|
133
|
+
index :name
|
133
134
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
135
|
+
def validate
|
136
|
+
assert_present :name
|
137
|
+
end
|
138
|
+
end
|
138
139
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
140
|
+
class Venue < Ohm::Model
|
141
|
+
attribute :name
|
142
|
+
collection :events, Event
|
143
|
+
end
|
143
144
|
|
144
|
-
|
145
|
-
|
146
|
-
|
145
|
+
class Person < Ohm::Model
|
146
|
+
attribute :name
|
147
|
+
end
|
148
|
+
```
|
147
149
|
|
148
150
|
All models have the `id` attribute built in, you don't need to declare it.
|
149
151
|
|
150
152
|
This is how you interact with IDs:
|
151
153
|
|
152
|
-
|
153
|
-
|
154
|
-
|
154
|
+
```ruby
|
155
|
+
event = Event.create :name => "Ohm Worldwide Conference 2031"
|
156
|
+
event.id
|
157
|
+
# => 1
|
155
158
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
+
# Find an event by id
|
160
|
+
event == Event[1]
|
161
|
+
# => true
|
159
162
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
+
# Trying to find a non existent event
|
164
|
+
Event[2]
|
165
|
+
# => nil
|
163
166
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
+
# Finding all the events
|
168
|
+
Event.all
|
169
|
+
# => [#<Event @values={:id=>1, :name=>"Ohm Worldwide Conference 2031"}>]
|
170
|
+
```
|
167
171
|
|
168
172
|
This example shows some basic features, like attribute declarations and
|
169
173
|
validations. Keep reading to find out what you can do with models.
|
@@ -233,35 +237,43 @@ For most use cases, this pattern doesn't represent a problem.
|
|
233
237
|
If you need to check for validity before operating on lists, sets or
|
234
238
|
counters, you can use this pattern:
|
235
239
|
|
236
|
-
|
237
|
-
|
238
|
-
|
240
|
+
```ruby
|
241
|
+
if event.valid?
|
242
|
+
event.comments.add(Comment.create(:body => "Great event!"))
|
243
|
+
end
|
244
|
+
```
|
239
245
|
|
240
246
|
If you are saving the object, this will suffice:
|
241
247
|
|
242
|
-
|
243
|
-
|
244
|
-
|
248
|
+
```ruby
|
249
|
+
if event.save
|
250
|
+
event.comments.add(Comment.create(:body => "Wonderful event!"))
|
251
|
+
end
|
252
|
+
```
|
245
253
|
|
246
254
|
Working with Sets
|
247
255
|
-----------------
|
248
256
|
|
249
257
|
Given the following model declaration:
|
250
258
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
259
|
+
```ruby
|
260
|
+
class Event < Ohm::Model
|
261
|
+
attribute :name
|
262
|
+
set :attendees, Person
|
263
|
+
end
|
264
|
+
```
|
255
265
|
|
256
266
|
You can add instances of `Person` to the set of attendees with the
|
257
|
-
|
267
|
+
`add` method:
|
258
268
|
|
259
|
-
|
269
|
+
```ruby
|
270
|
+
event.attendees.add(Person.create(:name => "Albert"))
|
260
271
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
272
|
+
# And now...
|
273
|
+
event.attendees.each do |person|
|
274
|
+
# ...do what you want with this person.
|
275
|
+
end
|
276
|
+
```
|
265
277
|
|
266
278
|
## Sorting
|
267
279
|
|
@@ -275,22 +287,25 @@ order. Both methods receive an options hash which is explained below:
|
|
275
287
|
|
276
288
|
Order direction and strategy. You can pass in any of the following:
|
277
289
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
290
|
+
1. ASC
|
291
|
+
2. ASC ALPHA (or ALPHA ASC)
|
292
|
+
3. DESC
|
293
|
+
4. DESC ALPHA (or ALPHA DESC)
|
282
294
|
|
283
295
|
It defaults to `ASC`.
|
284
296
|
|
285
|
-
|
297
|
+
__Important Note:__ Starting with Redis 2.6, `ASC` and `DESC` only
|
298
|
+
work with integers or floating point data types. If you need to sort
|
299
|
+
by an alphanumeric field, add the `ALPHA` keyword.
|
300
|
+
|
301
|
+
### :limit
|
286
302
|
|
287
|
-
The offset from which we should start with. Note that
|
303
|
+
The offset and limit from which we should start with. Note that
|
288
304
|
this is 0-indexed. It defaults to `0`.
|
289
305
|
|
290
|
-
|
306
|
+
Example:
|
291
307
|
|
292
|
-
|
293
|
-
get all the results from the LIST or SET that you are sorting.
|
308
|
+
`limit: [0, 10]` will get the first 10 entries starting from offset 0.
|
294
309
|
|
295
310
|
### :by
|
296
311
|
|
@@ -300,8 +315,14 @@ using {Ohm::Model::Collection#sort sort} and
|
|
300
315
|
converts the passed argument with the assumption that it is a hash key
|
301
316
|
and it's within the current model you are sorting.
|
302
317
|
|
303
|
-
|
304
|
-
|
318
|
+
```ruby
|
319
|
+
Post.all.sort_by(:title) # SORT Post:all BY Post:*->title
|
320
|
+
Post.all.sort(:by => :title) # SORT Post:all BY title
|
321
|
+
```
|
322
|
+
|
323
|
+
__Tip:__ Unless you absolutely know what you're doing, use `sort`
|
324
|
+
when you want to sort your models by their `id`, and use `sort_by`
|
325
|
+
otherwise.
|
305
326
|
|
306
327
|
### :get
|
307
328
|
|
@@ -310,34 +331,13 @@ the `:by` option, using {Ohm::Model::Collection#sort sort} and
|
|
310
331
|
{Ohm::Model::Collection#sort_by sort_by} has distinct differences in
|
311
332
|
that `sort_by` does much of the hand-coding for you.
|
312
333
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
Post.all.sort(:by => :title, :get => :title)
|
317
|
-
# SORT Post:all BY title GET title
|
318
|
-
|
319
|
-
|
320
|
-
### :store
|
321
|
-
|
322
|
-
An optional key which you may use to cache the sorted result. The key
|
323
|
-
may or may not exist.
|
324
|
-
|
325
|
-
This option can only be used together with `:get`.
|
334
|
+
```ruby
|
335
|
+
Post.all.sort_by(:title, :get => :title)
|
336
|
+
# SORT Post:all BY Post:*->title GET Post:*->title
|
326
337
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
# Get all the results stored in FOO.
|
332
|
-
Post.db.lrange("FOO", 0, -1)
|
333
|
-
|
334
|
-
When using temporary values, it might be a good idea to use a `volatile`
|
335
|
-
key. In Ohm, a volatile key means it just starts with a `~` character.
|
336
|
-
|
337
|
-
Post.all.sort_by(:title, :get => :title,
|
338
|
-
:store => Post.key.volatile["FOO"])
|
339
|
-
|
340
|
-
Post.key.volatile["FOO"].lrange 0, -1
|
338
|
+
Post.all.sort(:by => :title, :get => :title)
|
339
|
+
# SORT Post:all BY title GET title
|
340
|
+
```
|
341
341
|
|
342
342
|
|
343
343
|
Associations
|
@@ -345,16 +345,18 @@ Associations
|
|
345
345
|
|
346
346
|
Ohm lets you declare `references` and `collections` to represent associations.
|
347
347
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
348
|
+
```ruby
|
349
|
+
class Post < Ohm::Model
|
350
|
+
attribute :title
|
351
|
+
attribute :body
|
352
|
+
collection :comments, Comment
|
353
|
+
end
|
353
354
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
355
|
+
class Comment < Ohm::Model
|
356
|
+
attribute :body
|
357
|
+
reference :post, Post
|
358
|
+
end
|
359
|
+
```
|
358
360
|
|
359
361
|
After this, every time you refer to `post.comments` you will be talking
|
360
362
|
about instances of the model `Comment`. If you want to get a list of IDs
|
@@ -365,20 +367,22 @@ you can use `post.comments.key.smembers`.
|
|
365
367
|
Doing a {Ohm::Model.reference reference} is actually just a shortcut for
|
366
368
|
the following:
|
367
369
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
370
|
+
```ruby
|
371
|
+
# Redefining our model above
|
372
|
+
class Comment < Ohm::Model
|
373
|
+
attribute :body
|
374
|
+
attribute :post_id
|
375
|
+
index :post_id
|
373
376
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
+
def post=(post)
|
378
|
+
self.post_id = post.id
|
379
|
+
end
|
377
380
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
381
|
+
def post
|
382
|
+
Post[post_id]
|
383
|
+
end
|
384
|
+
end
|
385
|
+
```
|
382
386
|
|
383
387
|
The only difference with the actual implementation is that the model
|
384
388
|
is memoized.
|
@@ -386,8 +390,9 @@ is memoized.
|
|
386
390
|
The net effect here is we can conveniently set and retrieve `Post` objects,
|
387
391
|
and also search comments using the `post_id` index.
|
388
392
|
|
389
|
-
|
390
|
-
|
393
|
+
```ruby
|
394
|
+
Comment.find(:post_id => 1)
|
395
|
+
```
|
391
396
|
|
392
397
|
### Collections explained
|
393
398
|
|
@@ -397,34 +402,38 @@ just a macro that defines a finder for you, and we know that to find a model
|
|
397
402
|
by a field requires an {Ohm::Model.index index} to be defined for the field
|
398
403
|
you want to search.
|
399
404
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
405
|
+
```ruby
|
406
|
+
# Redefining our post above
|
407
|
+
class Post < Ohm::Model
|
408
|
+
attribute :title
|
409
|
+
attribute :body
|
404
410
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
411
|
+
def comments
|
412
|
+
Comment.find(:post_id => self.id)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
```
|
409
416
|
|
410
417
|
The only "magic" happening is with the inference of the `index` that was used
|
411
418
|
in the other model. The following all produce the same effect:
|
412
419
|
|
413
|
-
|
414
|
-
|
420
|
+
```ruby
|
421
|
+
# easiest, with the basic assumption that the index is `:post_id`
|
422
|
+
collection :comments, Comment
|
415
423
|
|
416
|
-
|
417
|
-
|
424
|
+
# we can explicitly declare this as follows too:
|
425
|
+
collection :comments, Comment, :post
|
418
426
|
|
419
|
-
|
420
|
-
|
421
|
-
|
427
|
+
# finally, we can use the default argument for the third parameter which
|
428
|
+
# is `to_reference`.
|
429
|
+
collection :comments, Comment, to_reference
|
422
430
|
|
423
|
-
|
424
|
-
|
425
|
-
|
431
|
+
# exploring `to_reference` reveals a very interesting and simple concept:
|
432
|
+
Post.to_reference == :post
|
433
|
+
# => true
|
434
|
+
```
|
426
435
|
|
427
|
-
|
436
|
+
Indices
|
428
437
|
-------
|
429
438
|
|
430
439
|
An {Ohm::Model.index index} is a set that's handled automatically by Ohm. For
|
@@ -442,28 +451,54 @@ validation and the methods {Ohm::Model::Set#find find} and
|
|
442
451
|
|
443
452
|
You can find a collection of records with the `find` method:
|
444
453
|
|
445
|
-
|
446
|
-
|
454
|
+
```ruby
|
455
|
+
# This returns a collection of users with the username "Albert"
|
456
|
+
User.find(:username => "Albert")
|
457
|
+
```
|
447
458
|
|
448
459
|
### Filtering results
|
449
460
|
|
450
|
-
|
451
|
-
|
461
|
+
```ruby
|
462
|
+
# Find all users from Argentina
|
463
|
+
User.find(:country => "Argentina")
|
452
464
|
|
453
|
-
|
454
|
-
|
465
|
+
# Find all activated users from Argentina
|
466
|
+
User.find(:country => "Argentina", :status => "activated")
|
455
467
|
|
456
|
-
|
457
|
-
|
468
|
+
# Find all users from Argentina, except those with a suspended account.
|
469
|
+
User.find(:country => "Argentina").except(:status => "suspended")
|
458
470
|
|
459
|
-
|
460
|
-
|
471
|
+
# Find all users both from Argentina and Uruguay
|
472
|
+
User.find(:country => "Argentina").union(:country => "Uruguay")
|
473
|
+
```
|
461
474
|
|
462
475
|
Note that calling these methods results in new sets being created
|
463
476
|
on the fly. This is important so that you can perform further operations
|
464
477
|
before reading the items to the client.
|
465
478
|
|
466
|
-
For more information, see [SINTERSTORE](http://redis.io/commands/sinterstore)
|
479
|
+
For more information, see [SINTERSTORE](http://redis.io/commands/sinterstore),
|
480
|
+
[SDIFFSTORE](http://redis.io/commands/sdiffstore) and
|
481
|
+
[SUNIONSTORE](http://redis.io/commands/sunionstore)
|
482
|
+
|
483
|
+
Uniques
|
484
|
+
-------
|
485
|
+
|
486
|
+
Uniques are similar to indices except that there can only be one record per
|
487
|
+
entry. The canonical example of course would be the email of your user, e.g.
|
488
|
+
|
489
|
+
```ruby
|
490
|
+
class User < Ohm::Model
|
491
|
+
attribute :email
|
492
|
+
unique :email
|
493
|
+
end
|
494
|
+
|
495
|
+
u = User.create(email: "foo@bar.com")
|
496
|
+
u == User.with(:email, "foo@bar.com")
|
497
|
+
# => true
|
498
|
+
|
499
|
+
User.create(email: "foo@bar.com")
|
500
|
+
# => raises Ohm::UniqueIndexViolation
|
501
|
+
```
|
467
502
|
|
468
503
|
Validations
|
469
504
|
-----------
|
@@ -486,37 +521,38 @@ The `assert` method is used by all the other assertions. It pushes the
|
|
486
521
|
second parameter to the list of errors if the first parameter evaluates
|
487
522
|
to false.
|
488
523
|
|
489
|
-
|
490
|
-
|
491
|
-
|
524
|
+
```ruby
|
525
|
+
def assert(value, error)
|
526
|
+
value or errors.push(error) && false
|
527
|
+
end
|
528
|
+
```
|
492
529
|
|
493
530
|
### assert_present
|
494
531
|
|
495
532
|
Checks that the given field is not nil or empty. The error code for this
|
496
|
-
assertion is
|
533
|
+
assertion is `:not_present`.
|
497
534
|
|
498
|
-
|
535
|
+
```ruby
|
536
|
+
assert_present :name
|
537
|
+
```
|
499
538
|
|
500
539
|
### assert_format
|
501
540
|
|
502
541
|
Checks that the given field matches the provided format. The error code
|
503
542
|
for this assertion is :format.
|
504
543
|
|
505
|
-
|
544
|
+
```ruby
|
545
|
+
assert_format :username, /^\w+$/
|
546
|
+
```
|
506
547
|
|
507
548
|
### assert_numeric
|
508
549
|
|
509
550
|
Checks that the given field holds a number as a Fixnum or as a string
|
510
551
|
representation. The error code for this assertion is :not_numeric.
|
511
552
|
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
Validates that the attribute or array of attributes are unique.
|
517
|
-
For this, an index of the same kind must exist. The error code is :not_unique.
|
518
|
-
|
519
|
-
assert_unique :email
|
553
|
+
```ruby
|
554
|
+
assert_numeric :votes
|
555
|
+
```
|
520
556
|
|
521
557
|
Errors
|
522
558
|
------
|
@@ -529,60 +565,37 @@ was issued and the error code.
|
|
529
565
|
|
530
566
|
Given the following example:
|
531
567
|
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
568
|
+
```ruby
|
569
|
+
def validate
|
570
|
+
assert_present :foo
|
571
|
+
assert_numeric :bar
|
572
|
+
assert_format :baz, /^\d{2}$/
|
573
|
+
end
|
574
|
+
```
|
538
575
|
|
539
576
|
If all the assertions fail, the following errors will be present:
|
540
577
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
-----------------
|
546
|
-
|
547
|
-
Unlike other ORMs, that define the full error messages in the model
|
548
|
-
itself, Ohm encourages you to define the error messages outside. If
|
549
|
-
you are using Ohm in the context of a web framework, the views are the
|
550
|
-
proper place to write the error messages.
|
551
|
-
|
552
|
-
Ohm provides a presenter that helps you in this quest. The basic usage
|
553
|
-
is as follows:
|
554
|
-
|
555
|
-
error_messages = @model.errors.present do |e|
|
556
|
-
e.on [:name, :not_present], "Name must be present"
|
557
|
-
e.on [:account, :not_present], "You must supply an account"
|
558
|
-
end
|
559
|
-
|
560
|
-
error_messages
|
561
|
-
# => ["Name must be present", "You must supply an account"]
|
562
|
-
|
563
|
-
Having the error message definitions in the views means you can use any
|
564
|
-
sort of helpers. You can also use blocks instead of strings for the
|
565
|
-
values. The result of the block is used as the error message:
|
566
|
-
|
567
|
-
error_messages = @model.errors.present do |e|
|
568
|
-
e.on [:email, :not_unique] do
|
569
|
-
"The email #{@model.email} is already registered."
|
570
|
-
end
|
571
|
-
end
|
572
|
-
|
573
|
-
error_messages
|
574
|
-
# => ["The email foo@example.com is already registered."]
|
578
|
+
```ruby
|
579
|
+
obj.errors
|
580
|
+
# => { foo: [:not_present], bar: [:not_numeric], baz: [:format] }
|
581
|
+
```
|
575
582
|
|
576
583
|
Ohm Extensions
|
577
584
|
==============
|
578
585
|
|
579
586
|
Ohm is rather small and can be extended in many ways.
|
580
587
|
|
581
|
-
A lot of amazing contributions are available at [Ohm Contrib]
|
588
|
+
A lot of amazing contributions are available at [Ohm Contrib][contrib]
|
589
|
+
make sure to check them if you need to extend Ohm's functionality.
|
590
|
+
|
591
|
+
[contrib]: http://cyx.github.com/ohm-contrib/doc/,
|
582
592
|
|
583
593
|
Tutorials
|
584
594
|
=========
|
585
595
|
|
596
|
+
NOTE: These tutorials were written against Ohm 0.1.x. Please give us
|
597
|
+
a while to fully update all of them.
|
598
|
+
|
586
599
|
Check the examples to get a feeling of the design patterns for Redis.
|
587
600
|
|
588
601
|
1. [Activity Feed](http://ohm.keyvalue.org/examples/activity-feed.html)
|
@@ -594,11 +607,12 @@ Check the examples to get a feeling of the design patterns for Redis.
|
|
594
607
|
7. [Slugs and permalinks](http://ohm.keyvalue.org/examples/slug.html)
|
595
608
|
8. [Tagging](http://ohm.keyvalue.org/examples/tagging.html)
|
596
609
|
|
610
|
+
|
597
611
|
Versions
|
598
612
|
========
|
599
613
|
|
600
|
-
Ohm uses features from Redis >
|
601
|
-
versions, please use Ohm 0.
|
614
|
+
Ohm uses features from Redis > 2.6.x. If you are stuck in previous
|
615
|
+
versions, please use Ohm 0.1.x instead.
|
602
616
|
|
603
617
|
Upgrading from 0.0.x to 0.1
|
604
618
|
---------------------------
|
@@ -607,11 +621,13 @@ Since Ohm 0.1 changes the persistence strategy (from 1-key-per-attribute
|
|
607
621
|
to Hashes), you'll need to run a script to upgrade your old data set.
|
608
622
|
Fortunately, it is built in:
|
609
623
|
|
610
|
-
|
624
|
+
```ruby
|
625
|
+
require "ohm/utils/upgrade"
|
611
626
|
|
612
|
-
|
627
|
+
Ohm.connect :port => 6380
|
613
628
|
|
614
|
-
|
629
|
+
Ohm::Utils::Upgrade.new([:User, :Post, :Comment]).run
|
630
|
+
```
|
615
631
|
|
616
632
|
Yes, you need to provide the model names. The good part is that you
|
617
633
|
don't have to load your application environment. Since we assume it's
|
data/lib/ohm.rb
CHANGED
@@ -267,45 +267,145 @@ module Ohm
|
|
267
267
|
|
268
268
|
def fetch(ids)
|
269
269
|
arr = model.db.pipelined do
|
270
|
-
ids.each { |id| namespace[id]
|
270
|
+
ids.each { |id| model.db.hgetall(namespace[id]) }
|
271
271
|
end
|
272
272
|
|
273
273
|
return [] if arr.nil?
|
274
274
|
|
275
275
|
arr.map.with_index do |atts, idx|
|
276
|
-
model.new(atts.update(id: ids[idx]))
|
276
|
+
model.new(Hash[*atts].update(id: ids[idx]))
|
277
277
|
end
|
278
278
|
end
|
279
279
|
end
|
280
280
|
|
281
|
-
class
|
282
|
-
include
|
281
|
+
class List < Struct.new(:key, :namespace, :model)
|
282
|
+
include Enumerable
|
283
283
|
|
284
|
-
#
|
284
|
+
# Returns the total size of the list using LLEN.
|
285
|
+
def size
|
286
|
+
key.llen
|
287
|
+
end
|
288
|
+
|
289
|
+
# Returns the first element of the list using LINDEX.
|
290
|
+
def first
|
291
|
+
model[key.lindex(0)]
|
292
|
+
end
|
293
|
+
|
294
|
+
# Returns the last element of the list using LINDEX.
|
295
|
+
def last
|
296
|
+
model[key.lindex(-1)]
|
297
|
+
end
|
298
|
+
|
299
|
+
# Checks if the model is part of this List.
|
300
|
+
#
|
301
|
+
# An important thing to note is that this method loads all of the
|
302
|
+
# elements of the List since there is no command in Redis that
|
303
|
+
# allows you to actually check the list contents efficiently.
|
304
|
+
#
|
305
|
+
# You may want to avoid doing this if your list has say, 10K entries.
|
306
|
+
def include?(model)
|
307
|
+
ids.include?(model.id.to_s)
|
308
|
+
end
|
309
|
+
|
310
|
+
# Replace all the existing elements of a list with a different
|
311
|
+
# collection of models. This happens atomically in a MULTI-EXEC
|
312
|
+
# block.
|
285
313
|
#
|
286
314
|
# Example:
|
287
315
|
#
|
288
316
|
# user = User.create
|
289
|
-
#
|
317
|
+
# p1 = Post.create
|
318
|
+
# user.posts.push(p1)
|
290
319
|
#
|
291
|
-
#
|
320
|
+
# p2, p3 = Post.create, Post.create
|
321
|
+
# user.posts.replace([p2, p3])
|
292
322
|
#
|
293
|
-
|
294
|
-
|
323
|
+
# user.posts.include?(p1)
|
324
|
+
# # => false
|
325
|
+
#
|
326
|
+
def replace(models)
|
327
|
+
ids = models.map { |model| model.id }
|
328
|
+
|
329
|
+
model.db.multi do
|
330
|
+
key.del
|
331
|
+
ids.each { |id| key.rpush(id) }
|
332
|
+
end
|
295
333
|
end
|
296
334
|
|
297
|
-
#
|
335
|
+
# Fetch the data from Redis in one go.
|
336
|
+
def to_a
|
337
|
+
fetch(ids)
|
338
|
+
end
|
339
|
+
|
340
|
+
def each
|
341
|
+
to_a.each { |element| yield element }
|
342
|
+
end
|
343
|
+
|
344
|
+
def empty?
|
345
|
+
size == 0
|
346
|
+
end
|
347
|
+
|
348
|
+
# Pushes the model to the _end_ of the list using RPUSH.
|
349
|
+
def push(model)
|
350
|
+
key.rpush(model.id)
|
351
|
+
end
|
352
|
+
|
353
|
+
# Pushes the model to the _beginning_ of the list using LPUSH.
|
354
|
+
def unshift(model)
|
355
|
+
key.lpush(model.id)
|
356
|
+
end
|
357
|
+
|
358
|
+
# Delete a model from the list.
|
359
|
+
#
|
360
|
+
# Note: If your list contains the model multiple times, this method
|
361
|
+
# will delete all instances of that model in one go.
|
298
362
|
#
|
299
363
|
# Example:
|
300
364
|
#
|
301
|
-
#
|
302
|
-
#
|
365
|
+
# class Comment < Ohm::Model
|
366
|
+
# end
|
303
367
|
#
|
304
|
-
#
|
368
|
+
# class Post < Ohm::Model
|
369
|
+
# list :comments, Comment
|
370
|
+
# end
|
371
|
+
#
|
372
|
+
# p = Post.create
|
373
|
+
# c = Comment.create
|
374
|
+
#
|
375
|
+
# p.comments.push(c)
|
376
|
+
# p.comments.push(c)
|
377
|
+
#
|
378
|
+
# p.comments.delete(c)
|
379
|
+
#
|
380
|
+
# p.comments.size == 0
|
381
|
+
# # => true
|
305
382
|
#
|
306
383
|
def delete(model)
|
307
|
-
key
|
384
|
+
# LREM key 0 <id> means remove all elements matching <id>
|
385
|
+
# @see http://redis.io/commands/lrem
|
386
|
+
key.lrem(0, model.id)
|
387
|
+
end
|
388
|
+
|
389
|
+
private
|
390
|
+
def ids
|
391
|
+
key.lrange(0, -1)
|
392
|
+
end
|
393
|
+
|
394
|
+
def fetch(ids)
|
395
|
+
arr = model.db.pipelined do
|
396
|
+
ids.each { |id| model.db.hgetall(namespace[id]) }
|
397
|
+
end
|
398
|
+
|
399
|
+
return [] if arr.nil?
|
400
|
+
|
401
|
+
arr.map.with_index do |atts, idx|
|
402
|
+
model.new(Hash[*atts].update(id: ids[idx]))
|
403
|
+
end
|
308
404
|
end
|
405
|
+
end
|
406
|
+
|
407
|
+
class Set < Struct.new(:key, :namespace, :model)
|
408
|
+
include Collection
|
309
409
|
|
310
410
|
# Chain new fiters on an existing set.
|
311
411
|
#
|
@@ -349,6 +449,39 @@ module Ohm
|
|
349
449
|
MultiSet.new([key], namespace, model).union(dict)
|
350
450
|
end
|
351
451
|
|
452
|
+
private
|
453
|
+
def execute
|
454
|
+
yield key
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
class MutableSet < Set
|
459
|
+
# Add a model directly to the set.
|
460
|
+
#
|
461
|
+
# Example:
|
462
|
+
#
|
463
|
+
# user = User.create
|
464
|
+
# post = Post.create
|
465
|
+
#
|
466
|
+
# user.posts.add(post)
|
467
|
+
#
|
468
|
+
def add(model)
|
469
|
+
key.sadd(model.id)
|
470
|
+
end
|
471
|
+
|
472
|
+
# Remove a model directly from the set.
|
473
|
+
#
|
474
|
+
# Example:
|
475
|
+
#
|
476
|
+
# user = User.create
|
477
|
+
# post = Post.create
|
478
|
+
#
|
479
|
+
# user.posts.delete(post)
|
480
|
+
#
|
481
|
+
def delete(model)
|
482
|
+
key.srem(model.id)
|
483
|
+
end
|
484
|
+
|
352
485
|
# Replace all the existing elements of a set with a different
|
353
486
|
# collection of models. This happens atomically in a MULTI-EXEC
|
354
487
|
# block.
|
@@ -373,13 +506,9 @@ module Ohm
|
|
373
506
|
ids.each { |id| key.sadd(id) }
|
374
507
|
end
|
375
508
|
end
|
376
|
-
|
377
|
-
private
|
378
|
-
def execute
|
379
|
-
yield key
|
380
|
-
end
|
381
509
|
end
|
382
510
|
|
511
|
+
|
383
512
|
# Anytime you filter a set with more than one requirement, you
|
384
513
|
# internally use a `MultiSet`. `MutiSet` is a bit slower than just
|
385
514
|
# a `Set` because it has to `SINTERSTORE` all the keys prior to
|
@@ -693,7 +822,37 @@ module Ohm
|
|
693
822
|
define_method name do
|
694
823
|
model = Utils.const(self.class, model)
|
695
824
|
|
696
|
-
Ohm::
|
825
|
+
Ohm::MutableSet.new(key[name], model.key, model)
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
829
|
+
# Declare an Ohm::List with the given name.
|
830
|
+
#
|
831
|
+
# Example:
|
832
|
+
#
|
833
|
+
# class Comment < Ohm::Model
|
834
|
+
# end
|
835
|
+
#
|
836
|
+
# class Post < Ohm::Model
|
837
|
+
# list :comments, :Comment
|
838
|
+
# end
|
839
|
+
#
|
840
|
+
# p = Post.create
|
841
|
+
# p.comments.push(Comment.create)
|
842
|
+
# p.comments.unshift(Comment.create)
|
843
|
+
# p.comments.size == 2
|
844
|
+
# # => true
|
845
|
+
#
|
846
|
+
# Note: You can't use the list until you save the model. If you try
|
847
|
+
# to do it, you'll receive an Ohm::MissingID error.
|
848
|
+
#
|
849
|
+
def self.list(name, model)
|
850
|
+
collections << name unless collections.include?(name)
|
851
|
+
|
852
|
+
define_method name do
|
853
|
+
model = Utils.const(self.class, model)
|
854
|
+
|
855
|
+
Ohm::List.new(key[name], model.key, model)
|
697
856
|
end
|
698
857
|
end
|
699
858
|
|
@@ -1027,11 +1186,6 @@ module Ohm
|
|
1027
1186
|
return attrs
|
1028
1187
|
end
|
1029
1188
|
|
1030
|
-
# Export a JSON representation of the model by encoding `to_hash`.
|
1031
|
-
def to_json(*args)
|
1032
|
-
to_hash.to_json(*args)
|
1033
|
-
end
|
1034
|
-
|
1035
1189
|
# Persist the model attributes and update indices and unique
|
1036
1190
|
# indices. The `counter`s and `set`s are not touched during save.
|
1037
1191
|
#
|
@@ -1210,6 +1364,8 @@ module Ohm
|
|
1210
1364
|
atts.each do |att, val|
|
1211
1365
|
ret[att] = send(att).to_s unless val.to_s.empty?
|
1212
1366
|
end
|
1367
|
+
|
1368
|
+
throw :empty if ret.empty?
|
1213
1369
|
end
|
1214
1370
|
end
|
1215
1371
|
|
@@ -1218,8 +1374,10 @@ module Ohm
|
|
1218
1374
|
end
|
1219
1375
|
|
1220
1376
|
def _save
|
1221
|
-
|
1222
|
-
|
1377
|
+
catch :empty do
|
1378
|
+
key.del
|
1379
|
+
key.hmset(*_skip_empty(attributes).flatten)
|
1380
|
+
end
|
1223
1381
|
end
|
1224
1382
|
|
1225
1383
|
def _verify_uniques
|
data/lib/ohm/json.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module Ohm
|
4
|
+
class Model
|
5
|
+
# Export a JSON representation of the model by encoding `to_hash`.
|
6
|
+
def to_json(*args)
|
7
|
+
to_hash.to_json(*args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Collection
|
12
|
+
# Sugar for to_a.to_json for all types of Sets
|
13
|
+
def to_json(*args)
|
14
|
+
to_a.to_json(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class List
|
19
|
+
# Sugar for to_a.to_json for lists.
|
20
|
+
def to_json(*args)
|
21
|
+
to_a.to_json(*args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/ohm/transaction.rb
CHANGED
@@ -46,7 +46,7 @@ module Ohm
|
|
46
46
|
class EntryAlreadyExistsError < ::RuntimeError
|
47
47
|
end
|
48
48
|
|
49
|
-
def method_missing(writer, value)
|
49
|
+
def method_missing(writer, value = nil)
|
50
50
|
super unless writer[-1] == "="
|
51
51
|
|
52
52
|
reader = writer[0..-2].to_sym
|
@@ -69,14 +69,14 @@ module Ohm
|
|
69
69
|
attr :phase
|
70
70
|
|
71
71
|
def initialize
|
72
|
-
@phase = Hash.new { |h, k| h[k] =
|
72
|
+
@phase = Hash.new { |h, k| h[k] = Array.new }
|
73
73
|
|
74
74
|
yield self if block_given?
|
75
75
|
end
|
76
76
|
|
77
77
|
def append(t)
|
78
78
|
t.phase.each do |key, values|
|
79
|
-
phase[key].
|
79
|
+
phase[key].concat(values - phase[key])
|
80
80
|
end
|
81
81
|
|
82
82
|
self
|
data/test/connection.rb
CHANGED
@@ -4,13 +4,27 @@ require File.expand_path("./helper", File.dirname(__FILE__))
|
|
4
4
|
|
5
5
|
prepare.clear
|
6
6
|
|
7
|
+
test "no rewriting of settings hash when using Ohm.connect" do
|
8
|
+
settings = { url: "redis://127.0.0.1:6379/15" }.freeze
|
9
|
+
|
10
|
+
ex = nil
|
11
|
+
|
12
|
+
begin
|
13
|
+
Ohm.connect(settings)
|
14
|
+
rescue RuntimeError => e
|
15
|
+
ex = e
|
16
|
+
end
|
17
|
+
|
18
|
+
assert_equal ex, nil
|
19
|
+
end
|
20
|
+
|
7
21
|
test "connects lazily" do
|
8
22
|
Ohm.connect(:port => 9876)
|
9
23
|
|
10
24
|
begin
|
11
25
|
Ohm.redis.get "foo"
|
12
26
|
rescue => e
|
13
|
-
assert_equal
|
27
|
+
assert_equal Errno::ECONNREFUSED, e.class
|
14
28
|
end
|
15
29
|
end
|
16
30
|
|
@@ -40,7 +54,7 @@ test "supports connecting by URL" do
|
|
40
54
|
begin
|
41
55
|
Ohm.redis.get "foo"
|
42
56
|
rescue => e
|
43
|
-
assert_equal
|
57
|
+
assert_equal Errno::ECONNREFUSED, e.class
|
44
58
|
end
|
45
59
|
end
|
46
60
|
|
@@ -54,6 +68,22 @@ test "connection class" do
|
|
54
68
|
assert conn.redis.kind_of?(Redis)
|
55
69
|
end
|
56
70
|
|
71
|
+
test "issue #46" do
|
72
|
+
class B < Ohm::Model
|
73
|
+
connect(:url => "redis://localhost:6379/15")
|
74
|
+
end
|
75
|
+
|
76
|
+
# We do this since we did prepare.clear above.
|
77
|
+
B.db.flushall
|
78
|
+
|
79
|
+
b1, b2 = nil, nil
|
80
|
+
|
81
|
+
Thread.new { b1 = B.create }.join
|
82
|
+
Thread.new { b2 = B.create }.join
|
83
|
+
|
84
|
+
assert_equal [b1, b2], B.all.sort.to_a
|
85
|
+
end
|
86
|
+
|
57
87
|
test "model can define its own connection" do
|
58
88
|
class B < Ohm::Model
|
59
89
|
connect(:url => "redis://localhost:6379/1")
|
data/test/filtering.rb
CHANGED
@@ -24,6 +24,16 @@ test "findability" do |john, jane|
|
|
24
24
|
assert User.find(lname: "Doe", fname: "Jane").include?(jane)
|
25
25
|
end
|
26
26
|
|
27
|
+
test "sets aren't mutable" do |john, jane|
|
28
|
+
assert_raise NoMethodError do
|
29
|
+
User.find(lname: "Doe").add(john)
|
30
|
+
end
|
31
|
+
|
32
|
+
assert_raise NoMethodError do
|
33
|
+
User.find(lname: "Doe", fname: "John").add(john)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
27
37
|
test "#first" do |john, jane|
|
28
38
|
set = User.find(lname: "Doe", status: "active")
|
29
39
|
|
data/test/helper.rb
CHANGED
data/test/json.rb
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
4
|
|
5
5
|
require "json"
|
6
|
+
require "ohm/json"
|
6
7
|
|
7
8
|
class Venue < Ohm::Model
|
8
9
|
attribute :name
|
10
|
+
list :programmers, :Programmer
|
9
11
|
|
10
12
|
def validate
|
11
13
|
assert_present :name
|
@@ -70,5 +72,15 @@ test "export an array of records to json" do
|
|
70
72
|
Programmer.create(language: "Python")
|
71
73
|
|
72
74
|
expected = [{ id: "1", language: "Ruby" }, { id: "2", language: "Python"}].to_json
|
73
|
-
assert_equal expected, Programmer.all.
|
75
|
+
assert_equal expected, Programmer.all.to_json
|
76
|
+
end
|
77
|
+
|
78
|
+
test "export an array of lists to json" do
|
79
|
+
venue = Venue.create(name: "Foo")
|
80
|
+
|
81
|
+
venue.programmers.push(Programmer.create(language: "Ruby"))
|
82
|
+
venue.programmers.push(Programmer.create(language: "Python"))
|
83
|
+
|
84
|
+
expected = [{ id: "1", language: "Ruby" }, { id: "2", language: "Python"}].to_json
|
85
|
+
assert_equal expected, venue.programmers.to_json
|
74
86
|
end
|
data/test/list.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class Post < Ohm::Model
|
4
|
+
list :comments, :Comment
|
5
|
+
end
|
6
|
+
|
7
|
+
class Comment < Ohm::Model
|
8
|
+
end
|
9
|
+
|
10
|
+
setup do
|
11
|
+
post = Post.create
|
12
|
+
|
13
|
+
post.comments.push(c1 = Comment.create)
|
14
|
+
post.comments.push(c2 = Comment.create)
|
15
|
+
post.comments.push(c3 = Comment.create)
|
16
|
+
|
17
|
+
[post, c1, c2, c3]
|
18
|
+
end
|
19
|
+
|
20
|
+
test "include?" do |p, c1, c2, c3|
|
21
|
+
assert p.comments.include?(c1)
|
22
|
+
assert p.comments.include?(c2)
|
23
|
+
assert p.comments.include?(c3)
|
24
|
+
end
|
25
|
+
|
26
|
+
test "first / last / size / empty?" do |p, c1, c2, c3|
|
27
|
+
assert_equal 3, p.comments.size
|
28
|
+
assert_equal c1, p.comments.first
|
29
|
+
assert_equal c3, p.comments.last
|
30
|
+
assert ! p.comments.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
test "replace" do |p, c1, c2, c3|
|
34
|
+
c4 = Comment.create
|
35
|
+
|
36
|
+
p.comments.replace([c4])
|
37
|
+
|
38
|
+
assert_equal [c4], p.comments.to_a
|
39
|
+
end
|
40
|
+
|
41
|
+
test "push / unshift" do |p, c1, c2, c3|
|
42
|
+
c4 = Comment.create
|
43
|
+
c5 = Comment.create
|
44
|
+
|
45
|
+
p.comments.unshift(c4)
|
46
|
+
p.comments.push(c5)
|
47
|
+
|
48
|
+
assert_equal c4, p.comments.first
|
49
|
+
assert_equal c5, p.comments.last
|
50
|
+
end
|
51
|
+
|
52
|
+
test "delete" do |p, c1, c2, c3|
|
53
|
+
p.comments.delete(c1)
|
54
|
+
assert_equal 2, p.comments.size
|
55
|
+
assert ! p.comments.include?(c1)
|
56
|
+
|
57
|
+
p.comments.delete(c2)
|
58
|
+
assert_equal 1, p.comments.size
|
59
|
+
assert ! p.comments.include?(c2)
|
60
|
+
|
61
|
+
p.comments.delete(c3)
|
62
|
+
assert p.comments.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
test "deleting main model cleans up the collection" do |p, _, _, _|
|
66
|
+
p.delete
|
67
|
+
|
68
|
+
assert ! Ohm.redis.exists(p.key[:comments])
|
69
|
+
end
|
data/test/model.rb
CHANGED
@@ -51,6 +51,28 @@ class Meetup < Ohm::Model
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
class Invoice < Ohm::Model
|
55
|
+
def _initialize_id
|
56
|
+
@id = "_custom_id"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
test "customized ID" do
|
61
|
+
inv = Invoice.create
|
62
|
+
assert_equal "_custom_id", inv.id
|
63
|
+
|
64
|
+
i = Invoice.create(id: "_diff_id")
|
65
|
+
assert_equal "_diff_id", i.id
|
66
|
+
assert_equal i, Invoice["_diff_id"]
|
67
|
+
end
|
68
|
+
|
69
|
+
test "empty model is ok" do
|
70
|
+
class Foo < Ohm::Model
|
71
|
+
end
|
72
|
+
|
73
|
+
foo = Foo.create
|
74
|
+
end
|
75
|
+
|
54
76
|
test "counters are cleaned up during deletion" do
|
55
77
|
e = Event.create(name: "Foo")
|
56
78
|
e.incr :votes, 10
|
data/test/transactions.rb
CHANGED
@@ -95,7 +95,7 @@ test "composed transaction" do |db|
|
|
95
95
|
|
96
96
|
assert_equal "bar", db.get("foo")
|
97
97
|
|
98
|
-
assert_equal
|
98
|
+
assert_equal ["foo"], t5.phase[:watch]
|
99
99
|
assert_equal 2, t5.phase[:write].size
|
100
100
|
end
|
101
101
|
|
@@ -173,6 +173,18 @@ test "storage in composed transactions" do |db|
|
|
173
173
|
assert_equal "enon", db.get("foo")
|
174
174
|
end
|
175
175
|
|
176
|
+
test "reading an storage entries that doesn't exist raises" do |db|
|
177
|
+
t1 = Ohm::Transaction.new do |t|
|
178
|
+
t.read do |s|
|
179
|
+
s.foo
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
assert_raise NoMethodError do
|
184
|
+
t1.commit(db)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
176
188
|
test "storage entries can't be overriden" do |db|
|
177
189
|
t1 = Ohm::Transaction.new do |t|
|
178
190
|
t.read do |s|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ohm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.rc1
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-03
|
13
|
+
date: 2012-04-03 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: nest
|
17
|
-
requirement: &
|
17
|
+
requirement: &2155966540 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ~>
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: '1.0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *2155966540
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: scrivener
|
28
|
-
requirement: &
|
28
|
+
requirement: &2155963980 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ~>
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: 0.0.3
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *2155963980
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: cutest
|
39
|
-
requirement: &
|
39
|
+
requirement: &2155978980 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ~>
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: '0.1'
|
45
45
|
type: :development
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *2155978980
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: batch
|
50
|
-
requirement: &
|
50
|
+
requirement: &2155978440 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ~>
|
@@ -55,7 +55,7 @@ dependencies:
|
|
55
55
|
version: 0.0.1
|
56
56
|
type: :development
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *2155978440
|
59
59
|
description: Ohm is a library that allows to store an object in Redis, a persistent
|
60
60
|
key-value database. It includes an extensible list of validations and has very good
|
61
61
|
performance.
|
@@ -66,10 +66,11 @@ executables: []
|
|
66
66
|
extensions: []
|
67
67
|
extra_rdoc_files: []
|
68
68
|
files:
|
69
|
+
- lib/ohm/json.rb
|
69
70
|
- lib/ohm/transaction.rb
|
70
71
|
- lib/ohm/utils/upgrade.rb
|
71
72
|
- lib/ohm.rb
|
72
|
-
- README.
|
73
|
+
- README.md
|
73
74
|
- LICENSE
|
74
75
|
- Rakefile
|
75
76
|
- test/1.8.6_test.rb
|
@@ -83,6 +84,7 @@ files:
|
|
83
84
|
- test/helper.rb
|
84
85
|
- test/indices.rb
|
85
86
|
- test/json.rb
|
87
|
+
- test/list.rb
|
86
88
|
- test/lua-save.rb
|
87
89
|
- test/lua.rb
|
88
90
|
- test/model.rb
|
@@ -115,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
117
|
version: 1.3.1
|
116
118
|
requirements: []
|
117
119
|
rubyforge_project: ohm
|
118
|
-
rubygems_version: 1.8.
|
120
|
+
rubygems_version: 1.8.11
|
119
121
|
signing_key:
|
120
122
|
specification_version: 3
|
121
123
|
summary: Object-hash mapping library for Redis.
|