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 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*.