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