ohm 1.3.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +14 -6
- data/.gems +4 -0
- data/.gitignore +4 -0
- data/CHANGELOG +52 -0
- data/benchmarks/common.rb +33 -0
- data/benchmarks/create.rb +21 -0
- data/benchmarks/delete.rb +13 -0
- data/examples/activity-feed.rb +162 -0
- data/examples/chaining.rb +203 -0
- data/examples/json-hash.rb +102 -0
- data/examples/one-to-many.rb +124 -0
- data/examples/philosophy.rb +149 -0
- data/examples/redis-logging.txt +179 -0
- data/examples/slug.rb +149 -0
- data/examples/tagging.rb +234 -0
- data/lib/ohm.rb +38 -4
- data/ohm.gemspec +18 -0
- data/test/model.rb +2 -2
- metadata +30 -15
- data/test/ranks.rb +0 -21
- data/test/setup.rb +0 -48
@@ -0,0 +1,102 @@
|
|
1
|
+
### Make Peace wih JSON and Hash
|
2
|
+
|
3
|
+
#### Why do I care?
|
4
|
+
|
5
|
+
# If you've ever needed to build an AJAX route handler, you may have noticed
|
6
|
+
# the prevalence of the design pattern where you return a JSON response.
|
7
|
+
#
|
8
|
+
# post "/comments.json" do
|
9
|
+
# comment = Comment.create(params[:comment])
|
10
|
+
# comment.to_json
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# `Ohm` helps you here by providing sensible defaults. It's not very popular,
|
14
|
+
# but `Ohm` actually has a `to_hash` method.
|
15
|
+
|
16
|
+
# Let's start by requiring `ohm` and `json`. In ruby 1.9, `json` is
|
17
|
+
# actually part of the standard library, so you don't have to install a gem
|
18
|
+
# for it. For ruby 1.8.x, a simple `[sudo] gem install json` will do it.
|
19
|
+
require "ohm"
|
20
|
+
require "json"
|
21
|
+
|
22
|
+
# Here we define our `Post` model with just a single `attribute` called
|
23
|
+
# `title`.
|
24
|
+
#
|
25
|
+
# We also define a validation, asserting the presence of the `title`.
|
26
|
+
class Post < Ohm::Model
|
27
|
+
attribute :title
|
28
|
+
|
29
|
+
def validate
|
30
|
+
assert_present :title
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Now let's load the test framework `cutest` to verify our code. We
|
35
|
+
# also call `Ohm.flush` for each test run.
|
36
|
+
require "cutest"
|
37
|
+
|
38
|
+
prepare { Ohm.flush }
|
39
|
+
|
40
|
+
# When we successfully create a `Post`, we can see that it returns
|
41
|
+
# only the *id* and its value in the hash.
|
42
|
+
test "hash representation when created" do
|
43
|
+
post = Post.create(:title => "my post")
|
44
|
+
|
45
|
+
assert({ :id => "1" } == post.to_hash)
|
46
|
+
end
|
47
|
+
|
48
|
+
# The JSON representation is actually just `post.to_hash.to_json`, so the
|
49
|
+
# same result, only in JSON, is returned.
|
50
|
+
test "json representation when created" do
|
51
|
+
post = Post.create(:title => "my post")
|
52
|
+
|
53
|
+
assert("{\"id\":\"1\"}" == post.to_json)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Let's try and do the opposite now -- that is, purposely try and create
|
57
|
+
# an invalid `Post`. We can see that it returns the `errors` of the
|
58
|
+
# `Post`, because we added an `assert_present :title` in our code above.
|
59
|
+
test "hash representation when validation failed" do
|
60
|
+
post = Post.create
|
61
|
+
|
62
|
+
assert({ :errors => [[:title, :not_present]]} == post.to_hash)
|
63
|
+
end
|
64
|
+
|
65
|
+
# As is the case for a valid record, the JSON representation is
|
66
|
+
# still equivalent to `post.to_hash.to_json`.
|
67
|
+
test "json representation when validation failed" do
|
68
|
+
post = Post.create
|
69
|
+
|
70
|
+
assert("{\"errors\":[[\"title\",\"not_present\"]]}" == post.to_json)
|
71
|
+
end
|
72
|
+
|
73
|
+
#### Whitelisted approach
|
74
|
+
|
75
|
+
# Unlike in other frameworks which dumps out all attributes by default,
|
76
|
+
# `Ohm` favors a whitelisted approach where you have to explicitly
|
77
|
+
# declare which attributes you want.
|
78
|
+
#
|
79
|
+
# By default, only `:id` and `:errors` will be available, depending if
|
80
|
+
# it was successfully saved or if there were validation errors.
|
81
|
+
|
82
|
+
# Let's re-open our Post class, and add a `to_hash` method.
|
83
|
+
class Post
|
84
|
+
def to_hash
|
85
|
+
super.merge(:title => title)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Now, let's test that the title is in fact part of `to_hash`.
|
90
|
+
test "customized to_hash" do
|
91
|
+
post = Post.create(:title => "Override FTW?")
|
92
|
+
assert({ :id => "1", :title => "Override FTW?" } == post.to_hash)
|
93
|
+
end
|
94
|
+
|
95
|
+
#### Conclusion
|
96
|
+
|
97
|
+
# Ohm has a lot of neat intricacies like this. Some of the things to keep
|
98
|
+
# in mind from this tutorial would be:
|
99
|
+
#
|
100
|
+
# 1. `Ohm` doesn't assume too much about your needs.
|
101
|
+
# 2. If you need a customized version, you can always define it yourself.
|
102
|
+
# 3. Customization is easy using basic OOP principles.
|
@@ -0,0 +1,124 @@
|
|
1
|
+
### One to Many Ohm style
|
2
|
+
|
3
|
+
#### Problem
|
4
|
+
|
5
|
+
# Let's say you want to implement a commenting system, and you need to have
|
6
|
+
# comments on different models. In order to do this using an RDBMS you have
|
7
|
+
# one of two options:
|
8
|
+
#
|
9
|
+
# 1. Have multiple comment tables per type i.e. VideoComments, AudioComments,
|
10
|
+
# etc.
|
11
|
+
# 2. Use a polymorphic schema.
|
12
|
+
#
|
13
|
+
# The problem with option 1 is that you'll may possibly run into an explosion
|
14
|
+
# of tables.
|
15
|
+
#
|
16
|
+
# The problem with option 2 is that if you have many comments across the whole
|
17
|
+
# site, you'll quickly hit the limit on a table, and eventually need to shard.
|
18
|
+
|
19
|
+
#### Solution
|
20
|
+
|
21
|
+
# In *Redis*, possibly the best data structure to model a comment would be to
|
22
|
+
# use a *List*, mainly because comments are always presented within the
|
23
|
+
# context of the parent entity, and are typically ordered in a predefined way
|
24
|
+
# (i.e. latest at the top, or latest at the bottom).
|
25
|
+
#
|
26
|
+
|
27
|
+
# Let's start by requiring `Ohm`.
|
28
|
+
require "ohm"
|
29
|
+
|
30
|
+
# We define both a `Video` and `Audio` model, with a `list` of *comments*.
|
31
|
+
class Video < Ohm::Model
|
32
|
+
list :comments, Comment
|
33
|
+
end
|
34
|
+
|
35
|
+
class Audio < Ohm::Model
|
36
|
+
list :comments, Comment
|
37
|
+
end
|
38
|
+
|
39
|
+
# The `Comment` model for this example will just contain one attribute called
|
40
|
+
# `body`.
|
41
|
+
class Comment < Ohm::Model
|
42
|
+
attribute :body
|
43
|
+
end
|
44
|
+
|
45
|
+
# Now let's require the test framework we're going to use called
|
46
|
+
# [cutest](http://github.com/djanowski/cutest)
|
47
|
+
require "cutest"
|
48
|
+
|
49
|
+
# And make sure that every run of our test suite has a clean Redis instance.
|
50
|
+
prepare { Ohm.flush }
|
51
|
+
|
52
|
+
# Let's begin testing. The important thing to verify is that
|
53
|
+
# video comments and audio comments don't munge with each other.
|
54
|
+
#
|
55
|
+
# We can see that they don't since each of the `comments` list only has
|
56
|
+
# one element.
|
57
|
+
test "adding all sorts of comments" do
|
58
|
+
video = Video.create
|
59
|
+
|
60
|
+
video_comment = Comment.create(:body => "First Video Comment")
|
61
|
+
video.comments.push(video_comment)
|
62
|
+
|
63
|
+
audio = Audio.create
|
64
|
+
audio_comment = Comment.create(:body => "First Audio Comment")
|
65
|
+
audio.comments.push(audio_comment)
|
66
|
+
|
67
|
+
assert video.comments.include?(video_comment)
|
68
|
+
assert video.comments.size == 1
|
69
|
+
|
70
|
+
assert audio.comments.include?(audio_comment)
|
71
|
+
assert audio.comments.size == 1
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
#### Discussion
|
76
|
+
#
|
77
|
+
# As you can see above, the design is very simple, and leaves little to be
|
78
|
+
# desired.
|
79
|
+
|
80
|
+
# Latest first ordering can simply be achieved by using `unshift` instead of
|
81
|
+
# `push`.
|
82
|
+
test "latest first ordering" do
|
83
|
+
video = Video.create
|
84
|
+
|
85
|
+
first = Comment.create(:body => "First")
|
86
|
+
second = Comment.create(:body => "Second")
|
87
|
+
|
88
|
+
video.comments.unshift(first)
|
89
|
+
video.comments.unshift(second)
|
90
|
+
|
91
|
+
assert [second, first] == video.comments.to_a
|
92
|
+
end
|
93
|
+
|
94
|
+
# In addition, since Lists are optimized for doing `LRANGE` operations,
|
95
|
+
# pagination of Comments would be very fast compared to doing a LIMIT / OFFSET
|
96
|
+
# query in SQL (some sites also use `WHERE id > ? LIMIT N` and pass the
|
97
|
+
# previous last ID in the set).
|
98
|
+
test "getting paged chunks of comments" do
|
99
|
+
video = Video.create
|
100
|
+
|
101
|
+
20.times { |i| video.comments.push(Comment.create(:body => "C#{i + 1}")) }
|
102
|
+
|
103
|
+
assert %w(C1 C2 C3 C4 C5) == video.comments[0, 4].map(&:body)
|
104
|
+
assert %w(C6 C7 C8 C9 C10) == video.comments[5, 9].map(&:body)
|
105
|
+
|
106
|
+
# ** Range style is also supported.
|
107
|
+
assert %w(C11 C12 C13 C14 C15) == video.comments[10..14].map(&:body)
|
108
|
+
|
109
|
+
# ** Also you can just pass in a single number.
|
110
|
+
assert "C16" == video.comments[15].body
|
111
|
+
end
|
112
|
+
|
113
|
+
#### Caveats
|
114
|
+
|
115
|
+
# Sometimes you need to be able to delete comments. For these cases, you might
|
116
|
+
# possibly need to store a reference back to the parent entity. Also, if you
|
117
|
+
# expect to store millions of comments for a single entity, it might be tricky
|
118
|
+
# to delete comments, as you need to manually loop through the entire LIST.
|
119
|
+
#
|
120
|
+
# Luckily, there is a clean alternative solution, which would be to use a
|
121
|
+
# `SORTED SET`, and to use the timestamp (or the negative of the timestamp) as
|
122
|
+
# the score to maintain the desired order. Deleting a comment from a
|
123
|
+
# `SORTED SET` would be a simple
|
124
|
+
# [ZREM](http://code.google.com/p/redis/wiki/ZremCommand) call.
|
@@ -0,0 +1,149 @@
|
|
1
|
+
### Internals: Nest and the Ohm Philosophy
|
2
|
+
|
3
|
+
#### Ohm does not want to hide Redis from you
|
4
|
+
|
5
|
+
# In contrast to the usual philosophy of ORMs in the wild, Ohm actually
|
6
|
+
# just provides a basic object mapping where you can safely tuck away
|
7
|
+
# attributes and declare grouping of data.
|
8
|
+
#
|
9
|
+
# Beyond that, Ohm doesn't try to hide Redis, but rather exposes it in
|
10
|
+
# a simple way, through key hierarchies provided by the library
|
11
|
+
# [Nest](http://github.com/soveran/nest).
|
12
|
+
|
13
|
+
# Let's require `Ohm`. We also require `Ohm::Contrib` so we can make
|
14
|
+
# use of its module `Ohm::Callbacks`.
|
15
|
+
require "ohm"
|
16
|
+
require "ohm/contrib"
|
17
|
+
|
18
|
+
# Let's quickly declare our `Post` model and include `Ohm::Callbacks`.
|
19
|
+
# We define an *attribute* `title` and also *index* it.
|
20
|
+
#
|
21
|
+
# In addition we specify our `Post` to have a list of *comments*.
|
22
|
+
class Post < Ohm::Model
|
23
|
+
include Ohm::Callbacks
|
24
|
+
|
25
|
+
attribute :title
|
26
|
+
index :title
|
27
|
+
|
28
|
+
list :comments, Comment
|
29
|
+
|
30
|
+
# This is one example of using the underlying library `Nest` directly.
|
31
|
+
# As you can see, we can easily drop down to using raw *Redis* commands,
|
32
|
+
# in this case we use
|
33
|
+
# [ZREVRANGE](http://code.google.com/p/redis/wiki/ZrangeCommand).
|
34
|
+
#
|
35
|
+
# *Note:* Since `Ohm::Model` defines a `to_proc`, we can use the `&` syntax
|
36
|
+
# together with `map` to make our code a little more terse.
|
37
|
+
def self.latest
|
38
|
+
key[:latest].zrevrange(0, -1).map(&Post)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Here we just quickly push this instance of `Post` to our `latest`
|
42
|
+
# *SORTED SET*. We use the current time as the score.
|
43
|
+
protected
|
44
|
+
def after_save
|
45
|
+
self.class.key[:latest].zadd(Time.now.to_i, id)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Since we add every `Post` to our *SORTED SET*, we have to make sure that
|
49
|
+
# we removed it from our `latest` *SORTED SET* as soon as we delete a
|
50
|
+
# `Post`.
|
51
|
+
#
|
52
|
+
# In this case we use the raw *Redis* command
|
53
|
+
# [ZREM](http://code.google.com/p/redis/wiki/ZremCommand).
|
54
|
+
def after_delete
|
55
|
+
self.class.key[:latest].zrem(id)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Now let's quickly define our `Comment` model.
|
60
|
+
class Comment < Ohm::Model
|
61
|
+
end
|
62
|
+
|
63
|
+
#### Test it out
|
64
|
+
|
65
|
+
# For this example, we'll use [Cutest](http://github.com/djanowski/cutest)
|
66
|
+
# for our testing framework.
|
67
|
+
require "cutest"
|
68
|
+
|
69
|
+
# To make it simple, we also ensure that every test run has a clean
|
70
|
+
# *Redis* instance.
|
71
|
+
prepare { Ohm.flush }
|
72
|
+
|
73
|
+
# Now let's create Post. `Cutest` by default yields the return value of the
|
74
|
+
# block to each and every one of the test blocks.
|
75
|
+
setup { Post.create }
|
76
|
+
|
77
|
+
# We then verify the behavior for our `Post:latest` ZSET. Our created
|
78
|
+
# post should automatically be part of `Post:latest`.
|
79
|
+
test "created post is inserted into latest" do |p|
|
80
|
+
assert [p.id] == Post.key[:latest].zrange(0, -1)
|
81
|
+
end
|
82
|
+
|
83
|
+
# And it should automatically be removed from it as soon as we delete our
|
84
|
+
# `Post`.
|
85
|
+
test "deleting the created post removes it from latest" do |p|
|
86
|
+
p.delete
|
87
|
+
|
88
|
+
assert Post.key[:latest].zrange(0, -1).empty?
|
89
|
+
end
|
90
|
+
|
91
|
+
# You might be curious what happens when we do `Post.all`. The test here
|
92
|
+
# demonstrates more or less what's happening when you do that.
|
93
|
+
test "querying Post:all using raw Redis commands" do |p|
|
94
|
+
assert [p.id] == Post.key[:all].smembers
|
95
|
+
|
96
|
+
assert [p] == Post.key[:all].smembers.map(&Post)
|
97
|
+
end
|
98
|
+
|
99
|
+
#### Understanding `post.comments`.
|
100
|
+
|
101
|
+
# Let's pop the hood and see how we can do *LIST* operations on our
|
102
|
+
# `post.comments` object.
|
103
|
+
|
104
|
+
# Getting the current size of our comments is just a wrapper for
|
105
|
+
# [LLEN](http://code.google.com/p/redis/wiki/LlenCommand).
|
106
|
+
test "checking the number of comments for a given post" do |p|
|
107
|
+
assert 0 == p.comments.key.llen
|
108
|
+
assert 0 == p.comments.size
|
109
|
+
end
|
110
|
+
|
111
|
+
# Also, pushing a comment to our `post.comments` object is equivalent
|
112
|
+
# to doing an [RPUSH](http://code.google.com/p/redis/wiki/RpushCommand)
|
113
|
+
# of its `id`.
|
114
|
+
test "pushing a Comment manually and checking for its presence" do |p|
|
115
|
+
comment = Comment.create
|
116
|
+
|
117
|
+
p.comments.key.rpush(comment.id)
|
118
|
+
assert [comment.id] == p.comments.key.lrange(0, -1)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Now for some interesting judo
|
122
|
+
test "now what if we want to find all Ohm or Redis posts" do
|
123
|
+
ohm = Post.create(:title => "Ohm")
|
124
|
+
redis = Post.create(:title => "Redis")
|
125
|
+
|
126
|
+
# Let's first choose an arbitrary key name to hold our `Set`.
|
127
|
+
ohm_redis = Post.key.volatile["ohm-redis"]
|
128
|
+
|
129
|
+
# A *volatile* key just simply means it will be prefixed with a `~`.
|
130
|
+
assert "~:Post:ohm-redis" == ohm_redis
|
131
|
+
|
132
|
+
# Finding all *Ohm* or *Redis* posts now will just be a call to
|
133
|
+
# [SUNIONSTORE](http://code.google.com/p/redis/wiki/SunionstoreCommand)
|
134
|
+
# on our *volatile* `ohm-redis` key.
|
135
|
+
ohm_redis.sunionstore(
|
136
|
+
Post.index_key_for(:title, "Ohm"),
|
137
|
+
Post.index_key_for(:title, "Redis")
|
138
|
+
)
|
139
|
+
|
140
|
+
# And voila, they have been found!
|
141
|
+
assert [ohm.id, redis.id] == ohm_redis.smembers.sort
|
142
|
+
end
|
143
|
+
|
144
|
+
#### The command reference is your friend
|
145
|
+
|
146
|
+
# If you invest a little time reading through all the different
|
147
|
+
# [Redis commands](http://code.google.com/p/redis/wiki/CommandReference),
|
148
|
+
# I'm pretty sure you will enjoy your experience hacking with Ohm, Nest and
|
149
|
+
# Redis a lot more.
|
@@ -0,0 +1,179 @@
|
|
1
|
+
### Understanding Ohm Internals through Logging
|
2
|
+
|
3
|
+
#### Benefits
|
4
|
+
|
5
|
+
# Ohm is actually a very thin wrapper for Redis, and most of the design
|
6
|
+
# patterns implemented in Ohm are based on the prescribed patterns for using
|
7
|
+
# Redis.
|
8
|
+
#
|
9
|
+
# If you take a little time to grok the internals of Ohm and Redis, the more
|
10
|
+
# effective you will be in weilding it efficiently.
|
11
|
+
|
12
|
+
#### Keys
|
13
|
+
#
|
14
|
+
# Most key-value stores have standardized on a structured design pattern for
|
15
|
+
# organizing data. Let's fire up a console:
|
16
|
+
#
|
17
|
+
# >> irb -r ohm -r logger
|
18
|
+
#
|
19
|
+
# Ohm.redis.client.logger = Logger.new(STDOUT)
|
20
|
+
#
|
21
|
+
# class Post < Ohm::Model
|
22
|
+
# attribute :title
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# Post.create(:title => "Grokking Ohm")
|
26
|
+
#
|
27
|
+
# After executing `Post.create(...)`, you'll see a lot of output from the
|
28
|
+
# logger. Let's go through every line in detail.
|
29
|
+
|
30
|
+
#### Post.create
|
31
|
+
|
32
|
+
# *Generate a new `Post:id`.*
|
33
|
+
INCR Post:id
|
34
|
+
|
35
|
+
# *Lock `Post:2`
|
36
|
+
# (see [SETNX](http://code.google.com/p/redis/wiki/SetnxCommand)
|
37
|
+
# for an explanation of the locking design pattern).
|
38
|
+
SETNX Post:2:_lock 1285060009.409451
|
39
|
+
|
40
|
+
# *Add the newly generated ID 2 to the `Post:all` SET.*
|
41
|
+
SADD Post:all 2
|
42
|
+
|
43
|
+
# *Start transaction.*
|
44
|
+
MULTI
|
45
|
+
|
46
|
+
# *Delete HASH `Post:2`.*
|
47
|
+
DEL Post:2
|
48
|
+
|
49
|
+
# *Set the attributes.*
|
50
|
+
HMSET Post:2 title "Grokking Ohm"
|
51
|
+
|
52
|
+
# *End transaction.*
|
53
|
+
EXEC
|
54
|
+
|
55
|
+
# *Release the lock.*
|
56
|
+
DEL Post:2:_lock
|
57
|
+
|
58
|
+
#### Sets and Lists
|
59
|
+
|
60
|
+
# Let's do a little `SET` and `LIST` manipulation and see what Ohm does.
|
61
|
+
# Here's the code for this section:
|
62
|
+
#
|
63
|
+
# class User < Ohm::Model
|
64
|
+
# collection :posts, Post
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# class Post < Ohm::Model
|
68
|
+
# attribute :title
|
69
|
+
# reference :user, User
|
70
|
+
#
|
71
|
+
# list :comments, Comment
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# class Comment < Ohm::Model
|
75
|
+
# end
|
76
|
+
|
77
|
+
#### User.create
|
78
|
+
|
79
|
+
# *Generate a new ID for the `User`.*
|
80
|
+
INCR User:id
|
81
|
+
|
82
|
+
# *Lock User:9.*
|
83
|
+
SETNX User:9:_lock 1285061032.174834
|
84
|
+
|
85
|
+
# *Add this new user to the SET User:all.*
|
86
|
+
SADD User:all 9
|
87
|
+
|
88
|
+
# *Release the lock.*
|
89
|
+
DEL User:9:_lock
|
90
|
+
|
91
|
+
#### Post.create(:title => "Foo", :user => User.create)
|
92
|
+
|
93
|
+
# *Generate an ID for this Post*
|
94
|
+
INCR Post:id
|
95
|
+
|
96
|
+
# *Lock Post:3*
|
97
|
+
SETNX Post:3:_lock 1285061032.180314
|
98
|
+
|
99
|
+
# *Add the ID 3 to the SET Post:all*
|
100
|
+
SADD Post:all 3
|
101
|
+
|
102
|
+
# *Start transaction*
|
103
|
+
MULTI
|
104
|
+
|
105
|
+
# *Remove existing Post:3 HASH*
|
106
|
+
DEL Post:3
|
107
|
+
|
108
|
+
# *Assign the attributes to Post:3*
|
109
|
+
HMSET Post:3 title Foo user_id 9
|
110
|
+
|
111
|
+
# *End transaction*
|
112
|
+
EXEC
|
113
|
+
|
114
|
+
# *Add the ID of this post to the SET index (more on this below)*
|
115
|
+
SADD Post:user_id:OQ== 3
|
116
|
+
|
117
|
+
# *Book keeping for Post:3 indices*
|
118
|
+
SADD Post:3:_indices Post:user_id:OQ==
|
119
|
+
|
120
|
+
# *Release lock*
|
121
|
+
DEL Post:3:_lock
|
122
|
+
|
123
|
+
#### post.comments << Comment.create
|
124
|
+
|
125
|
+
# *Generate an ID for this comment*
|
126
|
+
INCR Comment:id
|
127
|
+
|
128
|
+
# *Lock Comment:1*
|
129
|
+
SETNX Comment:1:_lock 1285061034.335855
|
130
|
+
|
131
|
+
# *Add this comment to the SET Comment:all*
|
132
|
+
SADD Comment:all 1
|
133
|
+
|
134
|
+
# *Release lock*
|
135
|
+
DEL Comment:1:_lock
|
136
|
+
|
137
|
+
# *Append the comment to the LIST Post:3:comments*
|
138
|
+
RPUSH Post:3:comments 1
|
139
|
+
|
140
|
+
#### Understanding indexes
|
141
|
+
|
142
|
+
# reference :user, User
|
143
|
+
#
|
144
|
+
# is actually more or less the same as:
|
145
|
+
#
|
146
|
+
# attribute :user_id
|
147
|
+
# index :user_id
|
148
|
+
#
|
149
|
+
# def user
|
150
|
+
# User[user_id]
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# def user_id=(user_id)
|
154
|
+
# self.user_id = user_id
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
# To further explain the [example above](#section-29), let's
|
158
|
+
# run through the commands that was issued. Remember that
|
159
|
+
# we had a *user_id* of *9* and a *post_id* of *3*.
|
160
|
+
|
161
|
+
# *OQ== is Base64 of *9*, with newlines removed. The post_id 3 is added
|
162
|
+
# effectively to Post:user_id:9, if we ignore the encoding.*
|
163
|
+
SADD Post:user_id:OQ== 3
|
164
|
+
|
165
|
+
# *Just keep track that Post:user_id:OQ== was created*
|
166
|
+
SADD Post:3:_indices Post:user_id:OQ==
|
167
|
+
|
168
|
+
# *Get all *posts* with user_id *9**
|
169
|
+
SMEMBERS Post:user_id:OQ==
|
170
|
+
|
171
|
+
#### What's next?
|
172
|
+
|
173
|
+
# We tackled a fair bit amount regarding Ohm internals. More or less this is
|
174
|
+
# the foundation of everything else found in Ohm, and other code are just
|
175
|
+
# built around the same fundamental concepts.
|
176
|
+
#
|
177
|
+
# If there's anything else you want to know, you can hang out on #ohm at
|
178
|
+
# irc.freenode.net, or you can visit the
|
179
|
+
# [Ohm google group](http://groups.google.com/group/ohm-ruby).
|