sohm 0.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 +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*.
|