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