ohm_util 0.1

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