sohm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,4 @@
1
+ msgpack -v 0.5.11
2
+ nido -v 0.0.1
3
+ redic -v 1.4.1
4
+ cutest -v 1.2.2
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ /.yardoc
2
+ /doc
3
+ /.gs
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#&lt;&lt;` 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,13 @@
1
+ require_relative "common"
2
+
3
+ 1000.times do |i|
4
+ Event.create(:name => "Redis Meetup #{i}", :location => "At my place")
5
+ end
6
+
7
+ benchmark "Delete event" do
8
+ Event.all.each do |event|
9
+ event.delete
10
+ end
11
+ end
12
+
13
+ run 1
@@ -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*.