ohm_util 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gems +4 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +408 -0
- data/CONTRIBUTING +19 -0
- data/LICENSE +19 -0
- data/README.md +570 -0
- data/benchmarks/common.rb +28 -0
- data/benchmarks/create.rb +21 -0
- data/benchmarks/delete.rb +13 -0
- data/examples/activity-feed.rb +157 -0
- data/examples/chaining.rb +162 -0
- data/examples/json-hash.rb +75 -0
- data/examples/one-to-many.rb +118 -0
- data/examples/philosophy.rb +137 -0
- data/examples/redis-logging.txt +179 -0
- data/examples/slug.rb +149 -0
- data/examples/tagging.rb +237 -0
- data/lib/lua/delete.lua +72 -0
- data/lib/lua/save.lua +126 -0
- data/lib/ohm_util.rb +116 -0
- data/makefile +9 -0
- data/ohm-util.gemspec +14 -0
- data/test/association.rb +33 -0
- data/test/connection.rb +16 -0
- data/test/core.rb +24 -0
- data/test/counters.rb +67 -0
- data/test/enumerable.rb +79 -0
- data/test/filtering.rb +185 -0
- data/test/hash_key.rb +31 -0
- data/test/helper.rb +23 -0
- data/test/indices.rb +138 -0
- data/test/json.rb +62 -0
- data/test/list.rb +83 -0
- data/test/model.rb +819 -0
- data/test/set.rb +37 -0
- data/test/thread_safety.rb +67 -0
- data/test/to_hash.rb +29 -0
- data/test/uniques.rb +108 -0
- metadata +97 -0
data/README.md
ADDED
@@ -0,0 +1,570 @@
|
|
1
|
+
Ohm ॐ
|
2
|
+
=====
|
3
|
+
|
4
|
+
Object-hash mapping library for Redis.
|
5
|
+
|
6
|
+
Description
|
7
|
+
-----------
|
8
|
+
|
9
|
+
Ohm is a library for storing objects in [Redis][redis], a persistent key-value
|
10
|
+
database. It has very good performance.
|
11
|
+
|
12
|
+
Community
|
13
|
+
---------
|
14
|
+
|
15
|
+
Join the mailing list: [http://groups.google.com/group/ohm-ruby](http://groups.google.com/group/ohm-ruby)
|
16
|
+
|
17
|
+
Meet us on IRC: [#ohm](irc://chat.freenode.net/#ohm) on [freenode.net](http://freenode.net/)
|
18
|
+
|
19
|
+
Related projects
|
20
|
+
----------------
|
21
|
+
|
22
|
+
These are libraries in other languages that were inspired by Ohm.
|
23
|
+
|
24
|
+
* [Ohm](https://github.com/soveran/ohm-crystal) for Crystal, created by soveran
|
25
|
+
* [JOhm](https://github.com/xetorthio/johm) for Java, created by xetorthio
|
26
|
+
* [Lohm](https://github.com/slact/lua-ohm) for Lua, created by slact
|
27
|
+
* [ohm.lua](https://github.com/amakawa/ohm.lua) for Lua, created by amakawa
|
28
|
+
* [Nohm](https://github.com/maritz/nohm) for Node.js, created by maritz
|
29
|
+
* [Redisco](https://github.com/iamteem/redisco) for Python, created by iamteem
|
30
|
+
* [redis3m](https://github.com/luca3m/redis3m) for C++, created by luca3m
|
31
|
+
* [Ohmoc](https://github.com/seppo0010/ohmoc) for Objective-C, created by seppo0010
|
32
|
+
* [Sohm](https://github.com/xxuejie/sohm.lua) for Lua, compatible with Twemproxy
|
33
|
+
|
34
|
+
Articles and Presentations
|
35
|
+
--------------------------
|
36
|
+
|
37
|
+
* [Simplicity](http://files.soveran.com/simplicity)
|
38
|
+
* [How to Redis](http://www.paperplanes.de/2009/10/30/how_to_redis.html)
|
39
|
+
* [Redis and Ohm](http://carlopecchia.eu/blog/2010/04/30/redis-and-ohm-part1/)
|
40
|
+
* [Ohm (Redis ORM)](http://blog.s21g.com/articles/1717) (Japanese)
|
41
|
+
* [Redis and Ohm](http://www.slideshare.net/awksedgreep/redis-and-ohm)
|
42
|
+
* [Ruby off Rails](http://www.slideshare.net/cyx.ucron/ruby-off-rails)
|
43
|
+
* [Data modeling with Redis and Ohm](http://www.sitepoint.com/semi-relational-data-modeling-redis-ohm/)
|
44
|
+
|
45
|
+
Getting started
|
46
|
+
---------------
|
47
|
+
|
48
|
+
Install [Redis][redis]. On most platforms it's as easy as grabbing the sources,
|
49
|
+
running make and then putting the `redis-server` binary in the PATH.
|
50
|
+
|
51
|
+
Once you have it installed, you can execute `redis-server` and it will
|
52
|
+
run on `localhost:6379` by default. Check the `redis.conf` file that comes
|
53
|
+
with the sources if you want to change some settings.
|
54
|
+
|
55
|
+
If you don't have Ohm, try this:
|
56
|
+
|
57
|
+
$ [sudo] gem install ohm
|
58
|
+
|
59
|
+
Or you can grab the code from [http://github.com/soveran/ohm][ohm].
|
60
|
+
|
61
|
+
Now, in an irb session you can test the Redis adapter directly:
|
62
|
+
|
63
|
+
>> require "ohm"
|
64
|
+
=> true
|
65
|
+
>> Ohm.redis.call "SET", "Foo", "Bar"
|
66
|
+
=> "OK"
|
67
|
+
>> Ohm.redis.call "GET", "Foo"
|
68
|
+
=> "Bar"
|
69
|
+
|
70
|
+
## Connecting to a Redis database
|
71
|
+
|
72
|
+
Ohm uses a lightweight Redis client called [Redic][redic]. To connect
|
73
|
+
to a Redis database, you will need to set an instance of `Redic`, with
|
74
|
+
an URL of the form `redis://:<passwd>@<host>:<port>/<db>`, through the
|
75
|
+
`Ohm.redis=` method, e.g.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
require "ohm"
|
79
|
+
|
80
|
+
Ohm.redis = Redic.new("redis://127.0.0.1:6379")
|
81
|
+
|
82
|
+
Ohm.redis.call "SET", "Foo", "Bar"
|
83
|
+
|
84
|
+
Ohm.redis.call "GET", "Foo"
|
85
|
+
# => "Bar"
|
86
|
+
```
|
87
|
+
|
88
|
+
Ohm defaults to a Redic connection to "redis://127.0.0.1:6379". The
|
89
|
+
example above could be rewritten as:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
require "ohm"
|
93
|
+
|
94
|
+
Ohm.redis.call "SET", "Foo", "Bar"
|
95
|
+
|
96
|
+
Ohm.redis.call "GET", "Foo"
|
97
|
+
# => "Bar"
|
98
|
+
```
|
99
|
+
|
100
|
+
All Ohm models inherit the same connection settings from `Ohm.redis`.
|
101
|
+
For cases where certain models need to connect to different databases,
|
102
|
+
they simple have to override that, i.e.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
require "ohm"
|
106
|
+
|
107
|
+
Ohm.redis = Redic.new(ENV["REDIS_URL1"])
|
108
|
+
|
109
|
+
class User < Ohm::Model
|
110
|
+
end
|
111
|
+
|
112
|
+
User.redis = Redic.new(ENV["REDIS_URL2"])
|
113
|
+
```
|
114
|
+
|
115
|
+
Models
|
116
|
+
------
|
117
|
+
|
118
|
+
Ohm's purpose in life is to map objects to a key value datastore. It
|
119
|
+
doesn't need migrations or external schema definitions. Take a look at
|
120
|
+
the example below:
|
121
|
+
|
122
|
+
### Example
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
class Event < Ohm::Model
|
126
|
+
attribute :name
|
127
|
+
reference :venue, :Venue
|
128
|
+
set :participants, :Person
|
129
|
+
counter :votes
|
130
|
+
|
131
|
+
index :name
|
132
|
+
end
|
133
|
+
|
134
|
+
class Venue < Ohm::Model
|
135
|
+
attribute :name
|
136
|
+
collection :events, :Event
|
137
|
+
end
|
138
|
+
|
139
|
+
class Person < Ohm::Model
|
140
|
+
attribute :name
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
All models have the `id` attribute built in, you don't need to declare it.
|
145
|
+
|
146
|
+
This is how you interact with IDs:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
event = Event.create :name => "Ohm Worldwide Conference 2031"
|
150
|
+
event.id
|
151
|
+
# => 1
|
152
|
+
|
153
|
+
# Find an event by id
|
154
|
+
event == Event[1]
|
155
|
+
# => true
|
156
|
+
|
157
|
+
# Update an event
|
158
|
+
event.update :name => "Ohm Worldwide Conference 2032"
|
159
|
+
# => #<Event:0x007fb4c35e2458 @attributes={:name=>"Ohm Worldwide Conference"}, @_memo={}, @id="1">
|
160
|
+
|
161
|
+
# Trying to find a non existent event
|
162
|
+
Event[2]
|
163
|
+
# => nil
|
164
|
+
|
165
|
+
# Finding all the events
|
166
|
+
Event.all.to_a
|
167
|
+
# => [<Event:1 name='Ohm Worldwide Conference 2032'>]
|
168
|
+
```
|
169
|
+
|
170
|
+
This example shows some basic features, like attribute declarations and
|
171
|
+
querying. Keep reading to find out what you can do with models.
|
172
|
+
|
173
|
+
Attribute types
|
174
|
+
---------------
|
175
|
+
|
176
|
+
Ohm::Model provides 4 attribute types:
|
177
|
+
|
178
|
+
* `Ohm::Model.attribute`,
|
179
|
+
* `Ohm::Model.set`
|
180
|
+
* `Ohm::Model.list`
|
181
|
+
* `Ohm::Model.counter`
|
182
|
+
|
183
|
+
and 2 meta types:
|
184
|
+
|
185
|
+
* `Ohm::Model.reference`
|
186
|
+
* `Ohm::Model.collection`.
|
187
|
+
|
188
|
+
### attribute
|
189
|
+
|
190
|
+
An `attribute` is just any value that can be stored as a string. In the
|
191
|
+
example above, we used this field to store the event's `name`. You can
|
192
|
+
use it to store numbers, but be aware that Redis will return a string
|
193
|
+
when you retrieve the value.
|
194
|
+
|
195
|
+
### set
|
196
|
+
|
197
|
+
A `set` in Redis is an unordered list, with an external behavior similar
|
198
|
+
to that of Ruby arrays, but optimized for faster membership lookups.
|
199
|
+
It's used internally by Ohm to keep track of the instances of each model
|
200
|
+
and for generating and maintaining indexes.
|
201
|
+
|
202
|
+
### list
|
203
|
+
|
204
|
+
A `list` is like an array in Ruby. It's perfectly suited for queues
|
205
|
+
and for keeping elements in order.
|
206
|
+
|
207
|
+
### counter
|
208
|
+
|
209
|
+
A `counter` is like a regular attribute, but the direct manipulation
|
210
|
+
of the value is not allowed. You can retrieve, increase or decrease
|
211
|
+
the value, but you can not assign it. In the example above, we used a
|
212
|
+
counter attribute for tracking votes. As the `increment` and `decrement`
|
213
|
+
operations are atomic, you can rest assured a vote won't be counted twice.
|
214
|
+
|
215
|
+
### reference
|
216
|
+
|
217
|
+
It's a special kind of attribute that references another model.
|
218
|
+
Internally, Ohm will keep a pointer to the model (its ID), but you get
|
219
|
+
accessors that give you real instances. You can think of it as the model
|
220
|
+
containing the foreign key to another model.
|
221
|
+
|
222
|
+
### collection
|
223
|
+
|
224
|
+
Provides an accessor to search for all models that `reference` the current model.
|
225
|
+
|
226
|
+
Tracked keys
|
227
|
+
------------
|
228
|
+
|
229
|
+
Besides the provided attribute types, it is possible to instruct
|
230
|
+
Ohm to track arbitrary keys and tie them to the object's lifecycle.
|
231
|
+
|
232
|
+
For example:
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
class Log < Ohm::Model
|
236
|
+
track :text
|
237
|
+
|
238
|
+
def append(msg)
|
239
|
+
redis.call("APPEND", key[:text], msg)
|
240
|
+
end
|
241
|
+
|
242
|
+
def tail(n = 100)
|
243
|
+
redis.call("GETRANGE", key[:text], -(n), -1)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
log = Log.create
|
248
|
+
log.append("hello\n")
|
249
|
+
|
250
|
+
assert_equal "hello\n", log.tail
|
251
|
+
|
252
|
+
log.append("world\n")
|
253
|
+
|
254
|
+
assert_equal "world\n", log.tail(6)
|
255
|
+
```
|
256
|
+
|
257
|
+
When the `log` object is deleted, the `:text` key will be deleted
|
258
|
+
too. Note that the key is scoped to that particular instance of
|
259
|
+
`Log`, so if `log.id` is `42` then the key will be `Log:42:text`.
|
260
|
+
|
261
|
+
Persistence strategy
|
262
|
+
--------------------
|
263
|
+
|
264
|
+
The attributes declared with `attribute` are only persisted after
|
265
|
+
calling `save`.
|
266
|
+
|
267
|
+
Operations on attributes of type `list`, `set` and `counter` are
|
268
|
+
possible only after the object is created (when it has an assigned
|
269
|
+
`id`). Any operation on these kinds of attributes is performed
|
270
|
+
immediately. This design yields better performance than buffering
|
271
|
+
the operations and waiting for a call to `save`.
|
272
|
+
|
273
|
+
For most use cases, this pattern doesn't represent a problem.
|
274
|
+
If you are saving the object, this will suffice:
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
if event.save
|
278
|
+
event.comments.add(Comment.create(body: "Wonderful event!"))
|
279
|
+
end
|
280
|
+
```
|
281
|
+
|
282
|
+
Working with Sets
|
283
|
+
-----------------
|
284
|
+
|
285
|
+
Given the following model declaration:
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
class Event < Ohm::Model
|
289
|
+
attribute :name
|
290
|
+
set :attendees, :Person
|
291
|
+
end
|
292
|
+
```
|
293
|
+
|
294
|
+
You can add instances of `Person` to the set of attendees with the
|
295
|
+
`add` method:
|
296
|
+
|
297
|
+
```ruby
|
298
|
+
event.attendees.add(Person.create(name: "Albert"))
|
299
|
+
|
300
|
+
# And now...
|
301
|
+
event.attendees.each do |person|
|
302
|
+
# ...do what you want with this person.
|
303
|
+
end
|
304
|
+
```
|
305
|
+
|
306
|
+
## Sorting
|
307
|
+
|
308
|
+
Since `attendees` is a `Ohm::Model::Set`, it exposes two sorting
|
309
|
+
methods: `Ohm::Model::Collection#sort` returns the elements
|
310
|
+
ordered by `id`, and `Ohm::Model::Collection#sort_by` receives
|
311
|
+
a parameter with an attribute name, which will determine the sorting
|
312
|
+
order. Both methods receive an options hash which is explained below:
|
313
|
+
|
314
|
+
### :order
|
315
|
+
|
316
|
+
Order direction and strategy. You can pass in any of the following:
|
317
|
+
|
318
|
+
1. ASC
|
319
|
+
2. ASC ALPHA (or ALPHA ASC)
|
320
|
+
3. DESC
|
321
|
+
4. DESC ALPHA (or ALPHA DESC)
|
322
|
+
|
323
|
+
It defaults to `ASC`.
|
324
|
+
|
325
|
+
__Important Note:__ Starting with Redis 2.6, `ASC` and `DESC` only
|
326
|
+
work with integers or floating point data types. If you need to sort
|
327
|
+
by an alphanumeric field, add the `ALPHA` keyword.
|
328
|
+
|
329
|
+
### :limit
|
330
|
+
|
331
|
+
The offset and limit from which we should start with. Note that
|
332
|
+
this is 0-indexed. It defaults to `0`.
|
333
|
+
|
334
|
+
Example:
|
335
|
+
|
336
|
+
`limit: [0, 10]` will get the first 10 entries starting from offset 0.
|
337
|
+
|
338
|
+
### :by
|
339
|
+
|
340
|
+
Key or Hash key with which to sort by. An important distinction with
|
341
|
+
using `Ohm::Model::Collection#sort` and
|
342
|
+
`Ohm::Model::Collection#sort_by` is that `sort_by` automatically
|
343
|
+
converts the passed argument with the assumption that it is a hash key
|
344
|
+
and it's within the current model you are sorting.
|
345
|
+
|
346
|
+
```ruby
|
347
|
+
Post.all.sort_by(:title) # SORT Post:all BY Post:*->title
|
348
|
+
Post.all.sort(by: :title) # SORT Post:all BY title
|
349
|
+
```
|
350
|
+
|
351
|
+
__Tip:__ Unless you absolutely know what you're doing, use `sort`
|
352
|
+
when you want to sort your models by their `id`, and use `sort_by`
|
353
|
+
otherwise.
|
354
|
+
|
355
|
+
### :get
|
356
|
+
|
357
|
+
A key pattern to return, e.g. `Post:*->title`. As is the case with
|
358
|
+
the `:by` option, using `Ohm::Model::Collection#sort` and
|
359
|
+
`Ohm::Model::Collection#sort_by` has distinct differences in
|
360
|
+
that `sort_by` does much of the hand-coding for you.
|
361
|
+
|
362
|
+
```ruby
|
363
|
+
Post.all.sort_by(:title, get: :title)
|
364
|
+
# SORT Post:all BY Post:*->title GET Post:*->title
|
365
|
+
|
366
|
+
Post.all.sort(by: :title, get: :title)
|
367
|
+
# SORT Post:all BY title GET title
|
368
|
+
```
|
369
|
+
|
370
|
+
|
371
|
+
Associations
|
372
|
+
------------
|
373
|
+
|
374
|
+
Ohm lets you declare `references` and `collections` to represent associations.
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
class Post < Ohm::Model
|
378
|
+
attribute :title
|
379
|
+
attribute :body
|
380
|
+
collection :comments, :Comment
|
381
|
+
end
|
382
|
+
|
383
|
+
class Comment < Ohm::Model
|
384
|
+
attribute :body
|
385
|
+
reference :post, :Post
|
386
|
+
end
|
387
|
+
```
|
388
|
+
|
389
|
+
After this, every time you refer to `post.comments` you will be talking
|
390
|
+
about instances of the model `Comment`. If you want to get a list of IDs
|
391
|
+
you can use `post.comments.ids`.
|
392
|
+
|
393
|
+
### References explained
|
394
|
+
|
395
|
+
Doing a `Ohm::Model.reference` is actually just a shortcut for
|
396
|
+
the following:
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
# Redefining our model above
|
400
|
+
class Comment < Ohm::Model
|
401
|
+
attribute :body
|
402
|
+
attribute :post_id
|
403
|
+
index :post_id
|
404
|
+
|
405
|
+
def post=(post)
|
406
|
+
self.post_id = post.id
|
407
|
+
end
|
408
|
+
|
409
|
+
def post
|
410
|
+
Post[post_id]
|
411
|
+
end
|
412
|
+
end
|
413
|
+
```
|
414
|
+
|
415
|
+
The only difference with the actual implementation is that the model
|
416
|
+
is memoized.
|
417
|
+
|
418
|
+
The net effect here is we can conveniently set and retrieve `Post` objects,
|
419
|
+
and also search comments using the `post_id` index.
|
420
|
+
|
421
|
+
```ruby
|
422
|
+
Comment.find(post_id: 1)
|
423
|
+
```
|
424
|
+
|
425
|
+
### Collections explained
|
426
|
+
|
427
|
+
The reason a `Ohm::Model.reference` and a
|
428
|
+
`Ohm::Model.collection` go hand in hand, is that a collection is
|
429
|
+
just a macro that defines a finder for you, and we know that to find a model
|
430
|
+
by a field requires an `Ohm::Model.index` to be defined for the field
|
431
|
+
you want to search.
|
432
|
+
|
433
|
+
```ruby
|
434
|
+
# Redefining our post above
|
435
|
+
class Post < Ohm::Model
|
436
|
+
attribute :title
|
437
|
+
attribute :body
|
438
|
+
|
439
|
+
def comments
|
440
|
+
Comment.find(post_id: self.id)
|
441
|
+
end
|
442
|
+
end
|
443
|
+
```
|
444
|
+
|
445
|
+
The only "magic" happening is with the inference of the `index` that was used
|
446
|
+
in the other model. The following all produce the same effect:
|
447
|
+
|
448
|
+
```ruby
|
449
|
+
# easiest, with the basic assumption that the index is `:post_id`
|
450
|
+
collection :comments, :Comment
|
451
|
+
|
452
|
+
# we can explicitly declare this as follows too:
|
453
|
+
collection :comments, :Comment, :post
|
454
|
+
|
455
|
+
# finally, we can use the default argument for the third parameter which
|
456
|
+
# is `to_reference`.
|
457
|
+
collection :comments, :Comment, to_reference
|
458
|
+
|
459
|
+
# exploring `to_reference` reveals a very interesting and simple concept:
|
460
|
+
Post.to_reference == :post
|
461
|
+
# => true
|
462
|
+
```
|
463
|
+
|
464
|
+
Modules
|
465
|
+
-------
|
466
|
+
|
467
|
+
If your models are defined inside a module, you will have to define
|
468
|
+
the references and collections as in the following example:
|
469
|
+
|
470
|
+
```ruby
|
471
|
+
module SomeNamespace
|
472
|
+
class Foo < Ohm::Model
|
473
|
+
attribute :name
|
474
|
+
end
|
475
|
+
|
476
|
+
class Bar < Ohm::Model
|
477
|
+
reference :foo, 'SomeNamespace::Foo'
|
478
|
+
end
|
479
|
+
end
|
480
|
+
```
|
481
|
+
|
482
|
+
Indices
|
483
|
+
-------
|
484
|
+
|
485
|
+
An `Ohm::Model.index` is a set that's handled automatically by Ohm. For
|
486
|
+
any index declared, Ohm maintains different sets of objects IDs for quick
|
487
|
+
lookups.
|
488
|
+
|
489
|
+
In the `Event` example, the index on the name attribute will
|
490
|
+
allow for searches like `Event.find(name: "some value")`.
|
491
|
+
|
492
|
+
Note that the methods `Ohm::Model::Set#find` and
|
493
|
+
`Ohm::Model::Set#except` need a corresponding index in order to work.
|
494
|
+
|
495
|
+
### Finding records
|
496
|
+
|
497
|
+
You can find a collection of records with the `find` method:
|
498
|
+
|
499
|
+
```ruby
|
500
|
+
# This returns a collection of users with the username "Albert"
|
501
|
+
User.find(username: "Albert")
|
502
|
+
```
|
503
|
+
|
504
|
+
### Filtering results
|
505
|
+
|
506
|
+
```ruby
|
507
|
+
# Find all users from Argentina
|
508
|
+
User.find(country: "Argentina")
|
509
|
+
|
510
|
+
# Find all active users from Argentina
|
511
|
+
User.find(country: "Argentina", status: "active")
|
512
|
+
|
513
|
+
# Find all active users from Argentina and Uruguay
|
514
|
+
User.find(status: "active").combine(country: ["Argentina", "Uruguay"])
|
515
|
+
|
516
|
+
# Find all users from Argentina, except those with a suspended account.
|
517
|
+
User.find(country: "Argentina").except(status: "suspended")
|
518
|
+
|
519
|
+
# Find all users both from Argentina and Uruguay
|
520
|
+
User.find(country: "Argentina").union(country: "Uruguay")
|
521
|
+
```
|
522
|
+
|
523
|
+
Note that calling these methods results in new sets being created
|
524
|
+
on the fly. This is important so that you can perform further operations
|
525
|
+
before reading the items to the client.
|
526
|
+
|
527
|
+
For more information, see [SINTERSTORE](http://redis.io/commands/sinterstore),
|
528
|
+
[SDIFFSTORE](http://redis.io/commands/sdiffstore) and
|
529
|
+
[SUNIONSTORE](http://redis.io/commands/sunionstore)
|
530
|
+
|
531
|
+
Uniques
|
532
|
+
-------
|
533
|
+
|
534
|
+
Uniques are similar to indices except that there can only be one record per
|
535
|
+
entry. The canonical example of course would be the email of your user, e.g.
|
536
|
+
|
537
|
+
```ruby
|
538
|
+
class User < Ohm::Model
|
539
|
+
attribute :email
|
540
|
+
unique :email
|
541
|
+
end
|
542
|
+
|
543
|
+
u = User.create(email: "foo@bar.com")
|
544
|
+
u == User.with(:email, "foo@bar.com")
|
545
|
+
# => true
|
546
|
+
|
547
|
+
User.create(email: "foo@bar.com")
|
548
|
+
# => raises Ohm::UniqueIndexViolation
|
549
|
+
```
|
550
|
+
|
551
|
+
Ohm Extensions
|
552
|
+
==============
|
553
|
+
|
554
|
+
Ohm is rather small and can be extended in many ways.
|
555
|
+
|
556
|
+
A lot of amazing contributions are available at [Ohm Contrib][contrib]
|
557
|
+
make sure to check them if you need to extend Ohm's functionality.
|
558
|
+
|
559
|
+
[contrib]: http://cyx.github.com/ohm-contrib/
|
560
|
+
|
561
|
+
Upgrading
|
562
|
+
=========
|
563
|
+
|
564
|
+
Ohm 2 breaks the compatibility with previous versions. If you're upgrading an
|
565
|
+
existing application, it's nice to have a good test coverage before going in.
|
566
|
+
To know about fixes and changes, please refer to the CHANGELOG file.
|
567
|
+
|
568
|
+
[redis]: http://redis.io
|
569
|
+
[ohm]: http://github.com/soveran/ohm
|
570
|
+
[redic]: https://github.com/amakawa/redic
|