sohm 0.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 +312 -0
- data/LICENSE +19 -0
- data/README.md +10 -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 +162 -0
- data/examples/json-hash.rb +75 -0
- data/examples/one-to-many.rb +124 -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/sample.rb +14 -0
- data/lib/sohm/command.rb +51 -0
- data/lib/sohm/json.rb +17 -0
- data/lib/sohm/lua/delete.lua +72 -0
- data/lib/sohm/lua/save.lua +13 -0
- data/lib/sohm.rb +1576 -0
- data/makefile +4 -0
- data/sohm.gemspec +18 -0
- data/test/association.rb +33 -0
- data/test/command.rb +55 -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 +133 -0
- data/test/json.rb +62 -0
- data/test/list.rb +83 -0
- data/test/model.rb +789 -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 +98 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 69c6cd0607dd5504902222e7b1f4ba805eed185d
|
4
|
+
data.tar.gz: 58c03e9e6c90b847489688ec014cad18ada039b6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 50f648882a984944b87026d46e368c529fa9434d8df48db087e58e9f240dec6e9c048bb19b6625b60f8d55782feca4859f0319d53ec233e47d277012020db8f8
|
7
|
+
data.tar.gz: 49f26999c6d7519f7d2b80ef891a179a61f1f24360e3a2cd274d515d6697624a2990daacef21cfd135a454e5bdf96c61d00a0eb5309d7a0c94029a4ed5b4f4df
|
data/.gems
ADDED
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,312 @@
|
|
1
|
+
## 2.0.0
|
2
|
+
|
3
|
+
- Lists now respond to range.
|
4
|
+
|
5
|
+
Example:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class Comment < Ohm::Model
|
9
|
+
end
|
10
|
+
|
11
|
+
class Post < Ohm::Model
|
12
|
+
list :comments, :Comment
|
13
|
+
end
|
14
|
+
|
15
|
+
c1 = Comment.create
|
16
|
+
c2 = Comment.create
|
17
|
+
c3 = Comment.create
|
18
|
+
|
19
|
+
post = Post.create
|
20
|
+
post.comments.push(c1)
|
21
|
+
post.comments.push(c2)
|
22
|
+
post.comments.push(c3)
|
23
|
+
|
24
|
+
post.comments.range(0, 1) == [c1, c2]
|
25
|
+
# => true
|
26
|
+
```
|
27
|
+
|
28
|
+
- When a record is created, `#id` returns a string instead of an integer.
|
29
|
+
This ensures IDs are strings everywhere:
|
30
|
+
|
31
|
+
Example:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
|
35
|
+
# Before
|
36
|
+
Meetup.create(name: "Ruby").id # => 1
|
37
|
+
Meetup.with(:name, "Ruby").id # => "1"
|
38
|
+
|
39
|
+
# Now
|
40
|
+
Meetup.create(name: "Ruby").id # => "1"
|
41
|
+
Meetup.with(:name, "Ruby").id # => "1"
|
42
|
+
```
|
43
|
+
|
44
|
+
- If an attribute is set to an empty string, Ohm won't delete it.
|
45
|
+
|
46
|
+
Example:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# Before
|
50
|
+
event = Meetup.create(location: "")
|
51
|
+
Meetup[event.id].location # => nil
|
52
|
+
|
53
|
+
# Now
|
54
|
+
event = Meetup.create(location: "")
|
55
|
+
Meetup[event.id].location # => ""
|
56
|
+
```
|
57
|
+
|
58
|
+
- Include `Ohm::List#ids` in the public API. It returns an array with all
|
59
|
+
the ID's in the list.
|
60
|
+
|
61
|
+
Example:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
class Comment < Ohm::Model
|
65
|
+
end
|
66
|
+
|
67
|
+
class Post < Ohm::Model
|
68
|
+
list :comments, :Comment
|
69
|
+
end
|
70
|
+
|
71
|
+
post = Post.create
|
72
|
+
post.comments.push(Comment.create)
|
73
|
+
post.comments.push(Comment.create)
|
74
|
+
post.comments.push(Comment.create)
|
75
|
+
|
76
|
+
post.comments.ids
|
77
|
+
# => ["1", "2", "3"]
|
78
|
+
```
|
79
|
+
|
80
|
+
- Include `Ohm::BasicSet#exists?` in the public API. This makes possible
|
81
|
+
to check if an id is included in a set. Check `Ohm::BasicSet#exists?`
|
82
|
+
documentation for more details.
|
83
|
+
|
84
|
+
Example:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
class Post < Ohm::Model
|
88
|
+
end
|
89
|
+
|
90
|
+
class User < Ohm::Model
|
91
|
+
set :posts, :Post
|
92
|
+
end
|
93
|
+
|
94
|
+
user = User.create
|
95
|
+
user.posts.add(post = Post.create)
|
96
|
+
|
97
|
+
user.posts.exists?(post.id) # => true
|
98
|
+
user.posts.exists?("nonexistent") # => false
|
99
|
+
```
|
100
|
+
|
101
|
+
- Change `Ohm::MultiSet#except` to union keys instead of intersect them
|
102
|
+
when passing an array.
|
103
|
+
|
104
|
+
Example:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class User < Ohm::Model
|
108
|
+
attribute :name
|
109
|
+
end
|
110
|
+
|
111
|
+
john = User.create(name: "John")
|
112
|
+
jane = User.create(name: "Jane")
|
113
|
+
|
114
|
+
res = User.all.except(name: [john.name, jane.name])
|
115
|
+
|
116
|
+
# Before
|
117
|
+
res.size # => 2
|
118
|
+
|
119
|
+
# Now
|
120
|
+
res.size # => 0
|
121
|
+
```
|
122
|
+
|
123
|
+
- Move ID generation to Lua. With this change, it's no longer possible
|
124
|
+
to generate custom ids. All ids are autoincremented.
|
125
|
+
|
126
|
+
- Add `Ohm::Model.track` method to allow track of custom keys. This key
|
127
|
+
is removed when the model is deleted.
|
128
|
+
|
129
|
+
Example:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
class Foo < Ohm::Model
|
133
|
+
track :notes
|
134
|
+
end
|
135
|
+
|
136
|
+
foo = Foo.create
|
137
|
+
|
138
|
+
Foo.redis.call("SET", foo.key[:notes], "something")
|
139
|
+
Foo.redis.call("GET", "Foo:1:notes")
|
140
|
+
# => "something"
|
141
|
+
|
142
|
+
foo.delete
|
143
|
+
Foo.redis.call("GET", "Foo:1:notes")
|
144
|
+
# => nil
|
145
|
+
```
|
146
|
+
|
147
|
+
- `Ohm::Model#reference` accepts strings as model references.
|
148
|
+
|
149
|
+
Example:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
class Bar < Ohm::Model
|
153
|
+
reference :foo, "SomeNamespace::Foo"
|
154
|
+
end
|
155
|
+
|
156
|
+
Bar.create.foo.class # => SomeNamespace::Foo
|
157
|
+
```
|
158
|
+
|
159
|
+
- `Ohm::Model#save` sanitizes attributes before sending them to Lua.
|
160
|
+
This complies with the original spec in Ohm v1 where a `to_s`
|
161
|
+
is done on each value.
|
162
|
+
|
163
|
+
Example:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
class Post < Ohm::Model
|
167
|
+
attribute :published
|
168
|
+
end
|
169
|
+
|
170
|
+
post = Post.create(published: true)
|
171
|
+
post = Post[post.id]
|
172
|
+
|
173
|
+
# Before
|
174
|
+
post.published # => "1"
|
175
|
+
|
176
|
+
# Now
|
177
|
+
post.published # => "true"
|
178
|
+
```
|
179
|
+
|
180
|
+
- `Ohm::Model#save` don't save values for attributes set to false.
|
181
|
+
|
182
|
+
Example:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
class Post < Ohm::Model
|
186
|
+
attribute :published
|
187
|
+
end
|
188
|
+
|
189
|
+
post = Post.create(published: false)
|
190
|
+
post = Post[post.id]
|
191
|
+
|
192
|
+
# Before
|
193
|
+
post.published # => "0"
|
194
|
+
|
195
|
+
# Now
|
196
|
+
post.published # => nil
|
197
|
+
```
|
198
|
+
|
199
|
+
- `nest` dependency has been removed. Now, Ohm uses [nido][nido]
|
200
|
+
to generate the keys that hold the data.
|
201
|
+
|
202
|
+
- `scrivener` dependency has been removed. Ohm no longer supports model
|
203
|
+
validations and favors filter validation on the boundary layer. Check
|
204
|
+
[scrivener][scrivener] project for more information.
|
205
|
+
|
206
|
+
- `redis` dependency has been removed. Ohm 2 uses [redic][redic],
|
207
|
+
a lightweight Redis client. Redic uses the `hiredis` gem for the
|
208
|
+
connection and for parsing the replies. Now, it defaults to a
|
209
|
+
Redic connection to "redis://127.0.0.1:6379". To change it, you
|
210
|
+
will need to provide an instance of `Redic` through the `Ohm.redis=`
|
211
|
+
helper.
|
212
|
+
|
213
|
+
Example:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
Ohm.redis = Redic.new("redis://:<passwd>@<host>:<port>/<db>")
|
217
|
+
```
|
218
|
+
|
219
|
+
Check the Redic README for more details.
|
220
|
+
|
221
|
+
- `Ohm::Model#transaction` and `Ohm::Transaction` have been removed.
|
222
|
+
|
223
|
+
- Move `save` and `delete` operations to Lua scripts.
|
224
|
+
|
225
|
+
- Support for Ruby 1.8 has been removed.
|
226
|
+
|
227
|
+
[nido]: https://github.com/soveran/nido
|
228
|
+
[scrivener]: https://github.com/soveran/scrivener
|
229
|
+
[redic]: https://github.com/amakawa/redic
|
230
|
+
|
231
|
+
## 1.3.2
|
232
|
+
|
233
|
+
- Fetching a batch of objects is now done in batches of 1000 objects at
|
234
|
+
a time. If you are iterating over large collections, this change should
|
235
|
+
provide a significant performance boost both in used memory and total
|
236
|
+
execution time.
|
237
|
+
|
238
|
+
- `MutableSet#<<` is now an alias for `#add`.
|
239
|
+
|
240
|
+
## 1.3.1
|
241
|
+
|
242
|
+
- Improve memory consumption when indexing persisted attributes.
|
243
|
+
No migration is needed and old indices will be cleaned up as you save
|
244
|
+
instances.
|
245
|
+
|
246
|
+
## 1.3.0
|
247
|
+
|
248
|
+
- Add Model.attributes.
|
249
|
+
|
250
|
+
## 1.2.0
|
251
|
+
|
252
|
+
- Enumerable fix.
|
253
|
+
|
254
|
+
- Merge Ohm::PipelinedFetch into Ohm::Collection.
|
255
|
+
|
256
|
+
- Fix Set, MultiSet, and List enumerable behavior.
|
257
|
+
|
258
|
+
- Change dependencies to use latest cutest.
|
259
|
+
|
260
|
+
## 1.1.0
|
261
|
+
|
262
|
+
- Compatible with redis-rb 3.
|
263
|
+
|
264
|
+
## 1.0.0
|
265
|
+
|
266
|
+
- Fetching a batch of objects is now done through one pipeline, effectively
|
267
|
+
reducing the IO to just 2 operations (one for SMEMBERS / LRANGE, one for
|
268
|
+
the actual HGET of all the individual HASHes.)
|
269
|
+
|
270
|
+
- write_remote / read_remote have been replaced with set / get respectively.
|
271
|
+
|
272
|
+
- Ohm::Model.unique has been added.
|
273
|
+
|
274
|
+
- Ohm::Model::Set has been renamed to Ohm::Set
|
275
|
+
|
276
|
+
- Ohm::Model::List has been renamed to Ohm::List
|
277
|
+
|
278
|
+
- Ohm::Model::Collection is gone.
|
279
|
+
|
280
|
+
- Ohm::Validations is gone. Ohm now uses Scrivener::Validations.
|
281
|
+
|
282
|
+
- Ohm::Key is gone. Ohm now uses Nest directly.
|
283
|
+
|
284
|
+
- No more concept of volatile keys.
|
285
|
+
|
286
|
+
- Ohm::Model::Wrapper is gone.
|
287
|
+
|
288
|
+
- Use Symbols for constants instead of relying on Ohm::Model.const_missing.
|
289
|
+
|
290
|
+
- `#sort` / `#sort_by` now uses `limit` as it's used in redis-rb, e.g. you
|
291
|
+
have to pass in an array like so: sort(limit: [0, 1]).
|
292
|
+
|
293
|
+
- Set / List have been trimmed to contain only the minimum number
|
294
|
+
of necessary methods.
|
295
|
+
|
296
|
+
- You can no longer mutate a collection / set as before, e.g. doing
|
297
|
+
User.find(...).add(User[1]) will throw an error.
|
298
|
+
|
299
|
+
- The #union operation has been added. You can now chain it with your filters.
|
300
|
+
|
301
|
+
- Temporary keys when doing finds are now automatically cleaned up.
|
302
|
+
|
303
|
+
- Counters are now stored in their own key instead, i.e. in
|
304
|
+
User:<id>:counters.
|
305
|
+
|
306
|
+
- JSON support has to be explicitly required by doing `require
|
307
|
+
"ohm/json"`.
|
308
|
+
|
309
|
+
- All save / delete / update operations are now done using
|
310
|
+
transactions (see http://redis.io/topics/transactions).
|
311
|
+
|
312
|
+
- All indices are now stored without converting the values to base64.
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009 Michel Martens and Damian Janowski
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Sohm
|
2
|
+
====
|
3
|
+
|
4
|
+
This forks [ohm](https://github.com/soveran/ohm) so as to work with [twemproxy](https://github.com/twitter/twemproxy) or similar systems.
|
5
|
+
|
6
|
+
`Sohm` means `Slim ohm`.
|
7
|
+
|
8
|
+
If you are able to use full-featured Redis(for example, if you are using Redis cluster via Sentinel), you should definitely think about using the original ohm.
|
9
|
+
|
10
|
+
However, if you are using twemproxy-like system, and you are okay with limited features, feel free to take a look at this project :)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "bench"
|
2
|
+
require_relative "../lib/ohm"
|
3
|
+
|
4
|
+
Ohm.redis = Redic.new("redis://127.0.0.1:6379/15")
|
5
|
+
Ohm.flush
|
6
|
+
|
7
|
+
class Event < Ohm::Model
|
8
|
+
attribute :name
|
9
|
+
attribute :location
|
10
|
+
|
11
|
+
index :name
|
12
|
+
index :location
|
13
|
+
|
14
|
+
def validate
|
15
|
+
assert_present :name
|
16
|
+
assert_present :location
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Sequence
|
21
|
+
def initialize
|
22
|
+
@value = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def succ!
|
26
|
+
Thread.exclusive { @value += 1 }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.[](name)
|
30
|
+
@@sequences ||= Hash.new { |hash, key| hash[key] = Sequence.new }
|
31
|
+
@@sequences[name]
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative "common"
|
2
|
+
|
3
|
+
benchmark "Create Events" do
|
4
|
+
i = Sequence[:events].succ!
|
5
|
+
|
6
|
+
Event.create(:name => "Redis Meetup #{i}", :location => "London #{i}")
|
7
|
+
end
|
8
|
+
|
9
|
+
benchmark "Find by indexed attribute" do
|
10
|
+
Event.find(:name => "Redis Meetup 1").first
|
11
|
+
end
|
12
|
+
|
13
|
+
benchmark "Mass update" do
|
14
|
+
Event[1].update(:name => "Redis Meetup II")
|
15
|
+
end
|
16
|
+
|
17
|
+
benchmark "Load events" do
|
18
|
+
Event[1].name
|
19
|
+
end
|
20
|
+
|
21
|
+
run 5000
|
@@ -0,0 +1,162 @@
|
|
1
|
+
### Building an activity feed
|
2
|
+
|
3
|
+
#### Common solutions using a relational design
|
4
|
+
|
5
|
+
# When faced with this application requirement, the most common approach by
|
6
|
+
# far have been to create an *activities* table, and rows in this table would
|
7
|
+
# reference a *user*. Activities would typically be generated for each
|
8
|
+
# follower (or friend) when a certain user performs an action, like posting a
|
9
|
+
# new status update.
|
10
|
+
|
11
|
+
#### Problems
|
12
|
+
|
13
|
+
# The biggest issue with this design, is that the *activities* table will
|
14
|
+
# quickly get very huge, at which point you would need to shard it on
|
15
|
+
# *user_id*. Also, inserting thousands of entries per second would quickly
|
16
|
+
# bring your database to its knees.
|
17
|
+
|
18
|
+
#### Ohm Solution
|
19
|
+
|
20
|
+
# As always we need to require `Ohm`.
|
21
|
+
require "ohm"
|
22
|
+
|
23
|
+
# We create a `User` class, with a `set` for all the other users he
|
24
|
+
# would be `following`, and another `set` for all his `followers`.
|
25
|
+
class User < Ohm::Model
|
26
|
+
set :followers, User
|
27
|
+
set :following, User
|
28
|
+
|
29
|
+
# Because a `User` literally has a `list` of activities, using a Redis
|
30
|
+
# `list` to model the activities would be a good choice. We default to
|
31
|
+
# getting the first 100 activities, and use
|
32
|
+
# [lrange](http://code.google.com/p/redis/wiki/LrangeCommand) directly.
|
33
|
+
def activities(start = 0, limit = 100)
|
34
|
+
key[:activities].lrange(start, start + limit)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Broadcasting a message to all the `followers` of a user would simply
|
38
|
+
# be prepending the message for each if his `followers`. We also use
|
39
|
+
# the Redis command
|
40
|
+
# [lpush](http://code.google.com/p/redis/wiki/RpushCommand) directly.
|
41
|
+
def broadcast(str)
|
42
|
+
followers.each do |user|
|
43
|
+
user.key[:activities].lpush(str)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Given that *Jane* wants to follow *John*, we simply do the following
|
48
|
+
# steps:
|
49
|
+
#
|
50
|
+
# 1. *John* is added to *Jane*'s `following` list.
|
51
|
+
# 2. *Jane* is added to *John*'s `followers` list.
|
52
|
+
def follow(other)
|
53
|
+
following << other
|
54
|
+
other.followers << self
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
#### Testing
|
60
|
+
|
61
|
+
# We'll use cutest for our testing framework.
|
62
|
+
require "cutest"
|
63
|
+
|
64
|
+
# The database is flushed before each test.
|
65
|
+
prepare { Ohm.flush }
|
66
|
+
|
67
|
+
# We define two users, `john` and `jane`, and yield them so all
|
68
|
+
# other tests are given access to these 2 users.
|
69
|
+
setup do
|
70
|
+
john = User.create
|
71
|
+
jane = User.create
|
72
|
+
|
73
|
+
[john, jane]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Let's verify our model for `follow`. When `jane` follows `john`,
|
77
|
+
# the following conditions should hold:
|
78
|
+
#
|
79
|
+
# 1. The followers list of `john` is comprised *only* of `jane`.
|
80
|
+
# 2. The list of users `jane` is following is comprised *only* of `john`.
|
81
|
+
test "jane following john" do |john, jane|
|
82
|
+
jane.follow(john)
|
83
|
+
|
84
|
+
assert [john] == jane.following.to_a
|
85
|
+
assert [jane] == john.followers.to_a
|
86
|
+
end
|
87
|
+
|
88
|
+
# Broadcasting a message should simply notify all the followers of the
|
89
|
+
# `broadcaster`.
|
90
|
+
test "john broadcasting a message" do |john, jane|
|
91
|
+
jane.follow(john)
|
92
|
+
john.broadcast("Learning about Redis and Ohm")
|
93
|
+
|
94
|
+
assert jane.activities.include?("Learning about Redis and Ohm")
|
95
|
+
end
|
96
|
+
|
97
|
+
#### Total Denormalization: Adding HTML
|
98
|
+
|
99
|
+
# This may be a real edge case design decision, but for some scenarios this
|
100
|
+
# may work. The beauty of this solution is that you only have to generate the
|
101
|
+
# output once, and successive refreshes of the end user will help you save
|
102
|
+
# some CPU cycles.
|
103
|
+
#
|
104
|
+
# This example of course assumes that the code that generates this does all
|
105
|
+
# the conditional checks (possibly changing the point of view like *Me:*
|
106
|
+
# instead of *John says:*).
|
107
|
+
test "broadcasting the html directly" do |john, jane|
|
108
|
+
jane.follow(john)
|
109
|
+
|
110
|
+
snippet = '<a href="/1">John</a> says: How\'s it going ' +
|
111
|
+
'<a href="/user/2">jane</a>?'
|
112
|
+
|
113
|
+
john.broadcast(snippet)
|
114
|
+
|
115
|
+
assert jane.activities.include?(snippet)
|
116
|
+
end
|
117
|
+
|
118
|
+
#### Saving Space
|
119
|
+
|
120
|
+
# In most cases, users don't really care about keeping their entire activity
|
121
|
+
# history. This application requirement would be fairly trivial to implement.
|
122
|
+
|
123
|
+
# Let's reopen our `User` class and define a new broadcast method.
|
124
|
+
class User
|
125
|
+
# We define a constant where we set the maximum number of activity entries.
|
126
|
+
MAX = 10
|
127
|
+
|
128
|
+
# Using `MAX` as the reference, we check if the number of activities exceeds
|
129
|
+
# `MAX`, and use
|
130
|
+
# [ltrim](http://code.google.com/p/redis/wiki/LtrimCommand) to truncate
|
131
|
+
# the activities.
|
132
|
+
def broadcast(str)
|
133
|
+
followers.each do |user|
|
134
|
+
user.key[:activities].lpush(str)
|
135
|
+
|
136
|
+
if user.key[:activities].llen > MAX
|
137
|
+
user.key[:activities].ltrim(0, MAX - 1)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Now let's verify that this new behavior is enforced.
|
144
|
+
test "pushing 11 activities maintains the list to 10" do |john, jane|
|
145
|
+
jane.follow(john)
|
146
|
+
|
147
|
+
11.times { john.broadcast("Flooding your feed!") }
|
148
|
+
|
149
|
+
assert 10 == jane.activities.size
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
#### Conclusion
|
154
|
+
|
155
|
+
# As you can see, choosing a more straightforward approach (in this case,
|
156
|
+
# actually having a list per user, instead of maintaining a separate
|
157
|
+
# `activities` table) will greatly simplify the design of your system.
|
158
|
+
#
|
159
|
+
# As a final note, keep in mind that the Ohm solution would still need
|
160
|
+
# sharding for large datasets, but that would be again trivial to implement
|
161
|
+
# using [redis-rb](http://github.com/ezmobius/redis-rb)'s distributed support
|
162
|
+
# and sharding it against the *user_id*.
|