easyredis 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ Manifest
2
+ README.md
3
+ Rakefile
4
+ lib/easyredis.rb
5
+ tests/benchmark.rb
6
+ tests/test.rb
@@ -0,0 +1,73 @@
1
+ # EasyRedis
2
+
3
+ EasyRedis is a simple ruby framework designed to make using Redis as a database simpler.
4
+
5
+ Redis is a very fast key-value store that supports data structures like lists, (sorted) sets, and hashes, but because of its simplicity, using Redis to store traditional database data can be somewhat tedious. EasyRedis streamlines this process.
6
+
7
+ ## Installation
8
+
9
+ You can get the source with git:
10
+
11
+ $ git clone git://github.com/alecbenzer/easyredis.git
12
+
13
+ or just download and extract a tar archive with the Downloads button.
14
+
15
+ Once you have the source, run:
16
+
17
+ $ rake
18
+ $ gem install pkg/easyredis-x.x.x.gem
19
+
20
+ Where x.x.x is the current version number.
21
+
22
+ ## Basics
23
+
24
+ First, create a simple model:
25
+
26
+ require 'easyredis'
27
+
28
+ class Post < EasyRedis::Model
29
+ field :title
30
+ field :body
31
+ end
32
+
33
+ EasyRedis.connect
34
+
35
+ This creates a Post model and connects to a redis server running on localhost on the default port (you can pass options to EasyRedis.connect like you would to Redis.new)
36
+
37
+ We can now make post objects:
38
+
39
+ p = Post.new
40
+ p.title = "My First Post"
41
+ p.body = "This is my very first post!"
42
+
43
+ Posts are automatically given ids that we can then use to retrive them:
44
+
45
+ id = p.id
46
+ p = Post[id] # or Post.find(id)
47
+ p.title # => "My First Post"
48
+
49
+ We also get a created_at field for free that we can sort by.
50
+
51
+ p.created_at # a ruby Time object
52
+ Post.all # get all posts, ordered by creation time
53
+ Post.all :order => :desc # specifying an order option
54
+
55
+ ## Searching and Sorting
56
+
57
+ We can also tell EasyRedis to optimize sorting and searching on certain fields. If we had defined Post as:
58
+
59
+ class Post < EasyRedis::Model
60
+ field :title
61
+ field :body
62
+
63
+ sort_on :title
64
+ end
65
+
66
+ We can now sort our posts by title:
67
+
68
+ Post.sort_by :title, :order => :desc
69
+
70
+ And also search:
71
+
72
+ Post.search_by(:title,"A common title") # all posts with this title
73
+ Post.find_by(:title,"My First Post") # just one post
@@ -0,0 +1,100 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ # spec = Gem::Specification.new do |s|
6
+ # s.platform = Gem::Platform::RUBY
7
+ # s.name = "easyredis"
8
+ # s.version = "0.0.1"
9
+ # s.author = "Alec Benzer"
10
+ # s.email = "alecbenzer@gmail.com"
11
+ # s.homepage = "https://github.com/alecbenzer/easyredis"
12
+ # s.summary = "simple framework designed to make using redis as a database simpler"
13
+ # s.files = FileList['lib/*.rb','test/*'].to_a
14
+ # s.require_path = "lib"
15
+ # s.test_files = Dir.glob('tests/*.rb')
16
+ # s.has_rdoc = true
17
+ # s.add_dependency("redis")
18
+ # s.add_dependency("activesupport")
19
+ # end
20
+ #
21
+ # Rake::GemPackageTask.new(spec) do |pkg|
22
+ # pkg.need_tar = true
23
+ # end
24
+ #
25
+ # task :default => "pkg/#{spec.name}-#{spec.version}.gem" do
26
+ # puts "generated latest version"
27
+ # end
28
+
29
+ Echoe.new('easyredis','0.0.1') do |p|
30
+ p.description = "simple framework designed to make using redis as a database simpler"
31
+ p.url = "https://github.com/alecbenzer/easyredis"
32
+ p.author = "Alec Benzer"
33
+ p.email = "alecbezer @nospam@ gmail.com"
34
+ p.ignore_pattern = ["*.rdb"]
35
+ p.development_dependencies = ["redis","activesupport"]
36
+ end
37
+
38
+
39
+ require 'benchmark'
40
+ require './tests/test'
41
+
42
+ $names = ["Bill","Bob","John","Jack","Alec","Mark","Nick","Evan","Eamon","Joe","Vikram"]
43
+
44
+ def rand_name
45
+ $names[rand*$names.size]
46
+ end
47
+
48
+ namespace :bm do
49
+ task :clear do
50
+ count = Man.count
51
+
52
+ puts "destroying #{$count} previous entries"
53
+ Benchmark.bm do |bm|
54
+ bm.report { Man.destroy_all }
55
+ end
56
+ end
57
+
58
+ task :add do
59
+ count = ENV["count"] ? ENV["count"].to_i : 25000
60
+ puts "adding #{count} new entries"
61
+ Benchmark.bm do |bm|
62
+ bm.report { count.times { m = Man.new ; m.name = rand_name } }
63
+ end
64
+ end
65
+
66
+ task :populate => [:clear, :add]
67
+
68
+ task :sort do
69
+ puts "sorting #{Man.count} entries by name"
70
+ Benchmark.bm do |bm|
71
+ #bm.report("ruby:") { Man.all.sort_by { |m| m.name } }
72
+ #bm.report("redis:") { Man.sort_by(:name) }
73
+ bm.report { Man.sort_by(:name) }
74
+ end
75
+ end
76
+
77
+ task :search do
78
+ puts "searching #{Man.count} entries by a particular name"
79
+ Benchmark.bm do |bm|
80
+ name = rand_name
81
+ #bm.report("ruby:") { Man.all.select {|m| m.name == name} }
82
+ #bm.report("redis:") { Man.search_by(:name,name) }
83
+ bm.report { Man.search_by(:name,name) }
84
+ end
85
+ end
86
+
87
+ task :find do
88
+ puts "finding one of #{Man.count} entry by name"
89
+ Benchmark.bm do |bm|
90
+ name = rand_name
91
+ #bm.report("redis:") { Man.find_by(:name,name) }
92
+ bm.report { Man.find_by(:name,name) }
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ task :doc do
99
+ puts `rdoc lib/ --title "EasyRedis"`
100
+ end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{easyredis}
5
+ s.version = "0.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Alec Benzer"]
9
+ s.date = %q{2011-02-05}
10
+ s.description = %q{simple framework designed to make using redis as a database simpler}
11
+ s.email = %q{alecbezer @nospam@ gmail.com}
12
+ s.extra_rdoc_files = ["README.md", "lib/easyredis.rb"]
13
+ s.files = ["Manifest", "README.md", "Rakefile", "lib/easyredis.rb", "tests/benchmark.rb", "tests/test.rb", "easyredis.gemspec"]
14
+ s.homepage = %q{https://github.com/alecbenzer/easyredis}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Easyredis", "--main", "README.md"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{easyredis}
18
+ s.rubygems_version = %q{1.5.0}
19
+ s.summary = %q{simple framework designed to make using redis as a database simpler}
20
+
21
+ if s.respond_to? :specification_version then
22
+ s.specification_version = 3
23
+
24
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
25
+ s.add_development_dependency(%q<redis>, [">= 0"])
26
+ s.add_development_dependency(%q<activesupport>, [">= 0"])
27
+ else
28
+ s.add_dependency(%q<redis>, [">= 0"])
29
+ s.add_dependency(%q<activesupport>, [">= 0"])
30
+ end
31
+ else
32
+ s.add_dependency(%q<redis>, [">= 0"])
33
+ s.add_dependency(%q<activesupport>, [">= 0"])
34
+ end
35
+ end
@@ -0,0 +1,348 @@
1
+ # EasyRedis is a simple ruby framework designed to make using Redis as a database simpler.
2
+ #
3
+ # Redis is a very fast key-value store that supports data structures like lists, (sorted) sets, and hashes, but because of its simplicity, using Redis to store traditional database data can be somewhat tedious. EasyRedis streamlines this process.
4
+ #
5
+ # Author:: Alec Benzer (mailto:alecbenzer@gmail.com)
6
+
7
+
8
+ require 'redis'
9
+ require 'set'
10
+ require 'active_support/inflector'
11
+
12
+
13
+ # main EasyRedis module which
14
+ # holds all classes and helper methods
15
+ module EasyRedis
16
+
17
+ # generate a 'score' for a string
18
+ # used for storing it in a sorted set
19
+ def self.string_score(str)
20
+ str = str.downcase
21
+ mult = 1.0
22
+ scr = 0.0
23
+ str.each_byte do |b|
24
+ mult /= 27
25
+ scr += (b-'a'.ord+1)*mult
26
+ end
27
+ scr
28
+ end
29
+
30
+ # gets a score for a generic object
31
+ #
32
+ # Uses EasyRedis#string_score if the object is a string,
33
+ # and just returns the object otherwise (presumably its a number)
34
+ def self.score(obj)
35
+ if obj.is_a? String
36
+ string_score(obj)
37
+ else
38
+ obj
39
+ end
40
+ end
41
+
42
+ # access the redis object
43
+ def self.redis
44
+ @redis
45
+ end
46
+
47
+ # connect to a redis server
48
+ def self.connect(options = {})
49
+ @redis = Redis.new(options)
50
+ end
51
+
52
+ # exception that indicates that the given field has not been indexed for sorting/searching
53
+ class FieldNotSortable < RuntimeError
54
+ def initialize(field)
55
+ @message = "field '#{field.to_s}' not sortable"
56
+ end
57
+
58
+ def to_s
59
+ @message
60
+ end
61
+ end
62
+
63
+ # exception that indicated an unknown ordering option was encountered
64
+ class UnknownOrderOption < RuntimeError
65
+ def initialize(opt)
66
+ @message = "unknown order option '#{opt}'"
67
+ end
68
+
69
+ def to_s
70
+ @message
71
+ end
72
+ end
73
+
74
+ # class representing a sort
75
+ class Sort
76
+ include Enumerable
77
+
78
+ # initialize the sort with a specific field, ordering option, and model
79
+ def initialize(field,order,klass)
80
+ raise EasyRedis::FieldNotSortable, field unless klass.sortable?(field)
81
+ raise EasyRedis::UnknownOrderOption, order unless [:asc,:desc].member? order
82
+ @field = field
83
+ @order = order
84
+ @klass = klass
85
+ end
86
+
87
+ # access elements in this sort
88
+ #
89
+ # Work's like ruby's Array#[]. It can take a specific index, a range, or an offset, amount pair.
90
+ # Calling this method will actually query the redis server for ids
91
+ def [](index,limit=nil)
92
+ if limit
93
+ offset = index
94
+ self[offset...(offset+limit)]
95
+ elsif index.is_a? Range
96
+ a = index.begin
97
+ b = index.end
98
+ b -= 1 if index.exclude_end?
99
+ ids = []
100
+ if @order == :asc
101
+ ids = EasyRedis.redis.zrange(@klass.sort_prefix(@field),a,b)
102
+ elsif @order == :desc
103
+ ids = EasyRedis.redis.zrevrange(@klass.sort_prefix(@field),a,b)
104
+ end
105
+ ids.map{|i|@klass.new(i)}
106
+ elsif index.is_a? Integer
107
+ self[index..index].first
108
+ end
109
+ end
110
+
111
+ # iterate through all members of this sort
112
+ def each
113
+ self[0..-1].each { |o| yield o }
114
+ end
115
+
116
+ # return the number of elements in this sort
117
+ #
118
+ # As of now, idential to the Model's #count method.
119
+ # This method is explicility defined here to overwrite the default one in Enumerable, which iterates through all the entries to count them
120
+ def count
121
+ EasyRedis.zcard(@klass.sort_prefix(@field))
122
+ end
123
+
124
+ # return the fist element of this sort, or the first n elements, if n is given
125
+ def first(n = nil)
126
+ if n
127
+ self[0,n]
128
+ else
129
+ self[0]
130
+ end
131
+ end
132
+
133
+ def inspect
134
+ "#<EasyRedis::Sort model=#{@klass.name}, field=#{@field.to_s}, order=#{@order.to_s}>"
135
+ end
136
+
137
+ end
138
+
139
+
140
+ # class representing a data model
141
+ # you want to store in redis
142
+ class Model
143
+
144
+ # add a field to the model
145
+ def self.field(name)
146
+ name = name.to_s
147
+ getter = name
148
+ setter = name + "="
149
+ instance_var = '@' + name
150
+
151
+ define_method getter.to_sym do
152
+ prev = instance_variable_get(instance_var)
153
+ if prev
154
+ prev
155
+ else
156
+ instance_variable_set(instance_var,Marshal.load(EasyRedis.redis.hget(key_name,name)))
157
+ end
158
+ end
159
+
160
+ define_method setter.to_sym do |val|
161
+ EasyRedis.redis.hset(key_name,name,Marshal.dump(val))
162
+ instance_variable_set(instance_var,val)
163
+
164
+ if @@sorts.member? name.to_sym
165
+ # score = val.is_a?(String) ? EasyRedis.string_score(val) : val
166
+ EasyRedis.redis.zadd(sort_prefix(name),EasyRedis.score(val),@id)
167
+ end
168
+ end
169
+ end
170
+
171
+ # index a field to be sorted/searched
172
+ def self.sort_on(field)
173
+ @@sorts ||= []
174
+ @@sorts << field.to_sym
175
+ end
176
+
177
+ # returns number of instances of this model
178
+ def self.count
179
+ EasyRedis.redis.zcard(prefix.pluralize)
180
+ end
181
+
182
+ # get all instances of this model
183
+ # ordered by creation time
184
+ def self.all(options = {:order => :asc})
185
+ self.sort_by :created_at, options
186
+ end
187
+
188
+ # find an instance of this model based on its id
189
+ def self.find(id)
190
+ if EasyRedis.redis.zscore(prefix.pluralize,id)
191
+ new(id)
192
+ else
193
+ nil
194
+ end
195
+ end
196
+
197
+ # alias for Model#find
198
+ def self.[](id)
199
+ find(id)
200
+ end
201
+
202
+ # get all entries where field matches val
203
+ def self.search_by(field, val, options = {})
204
+ raise EasyRedis::FieldNotSortable, field unless @@sorts.member? field.to_sym
205
+ scr = EasyRedis.score(val)
206
+ # options[:limit] = [0,options[:limit]] if options[:limit]
207
+ ids = EasyRedis.redis.zrangebyscore(sort_prefix(field),scr,scr,proc_options(options))
208
+ ids.map{|i| new(i) }
209
+ end
210
+
211
+ # get the first entry where field matches val
212
+ def self.find_by(field,val)
213
+ search_by(field,val,:limit => 1).first
214
+ end
215
+
216
+ # get all entries, sorted by the given field
217
+ def self.sort_by(field,options = {:order => :asc})
218
+ EasyRedis::Sort.new(field,options[:order],self)
219
+ end
220
+
221
+ # gives all values for the given field that begins with str
222
+ #
223
+ # This method is currently iterates through all existing entries. It is therefore very slow and should probably not be used at this time.
224
+ def self.matches(field,str)
225
+ scr = EasyRedis.score(str)
226
+ a,b = scr, scr+1/(27.0**str.size)
227
+ ids = EasyRedis.redis.zrangebyscore(sort_prefix(field), "#{a}", "(#{b}")
228
+ s = Set.new
229
+ ids.each{|i| s << new(i).send(field.to_s) }
230
+ s.to_a
231
+ end
232
+
233
+ # searches for all entries where field begins with str
234
+ #
235
+ # works with string fields that have been indexed with sort_on
236
+ def self.match(field,str, options = {})
237
+ raise EasyRedis::FieldNotSortable, filename unless @@sorts.member? field
238
+ scr = EasyRedis.score(str)
239
+ a,b = scr, scr+1/(27.0**str.size)
240
+ ids = EasyRedis.redis.zrangebyscore(sort_prefix(field), "#{a}", "(#{b}", proc_options(options))
241
+ ids.map{|i| new(i)}
242
+ end
243
+
244
+ # indicates whether field has been indexed with sort_on
245
+ def self.sortable?(field)
246
+ @@sorts.member? field or field.to_sym == :created_at
247
+ end
248
+
249
+ # destroy all instances of this model
250
+ def self.destroy_all
251
+ all.each {|x| x.destroy}
252
+ @@sorts.each {|field| EasyRedis.redis.del(sort_prefix(field)) }
253
+ EasyRedis.redis.del(prefix.pluralize)
254
+ EasyRedis.redis.del(prefix + ":next_id")
255
+ end
256
+
257
+
258
+ # the id of this entry
259
+ attr_reader :id
260
+
261
+ # create a new instance of this model
262
+ #
263
+ # If no id is passed, one is generated for you.
264
+ # Otherwise, sets the id field to the passed id, but does not check to see if it is a valid id for this model.
265
+ # Users should use Model#find or Model#[] when retiving models by id, as these check for valid ids.
266
+ def initialize(id=nil)
267
+ if id
268
+ @id = id
269
+ else
270
+ @id = EasyRedis.redis.incr(prefix + ':next_id')
271
+ EasyRedis.redis.zadd(prefix.pluralize,Time.now.to_i,@id)
272
+ @id
273
+ end
274
+ end
275
+
276
+ # get the creation time of an entry
277
+ def created_at
278
+ Time.at(EasyRedis.redis.zscore(prefix.pluralize,@id).to_i)
279
+ end
280
+
281
+ # directly access a field of this entry's redis hash
282
+ #
283
+ # note that you cannot access created_at or id with these methods
284
+ def [](field)
285
+ EasyRedis.redis.hget(key_name,field)
286
+ end
287
+
288
+ # directly change a field of this entry's redis hash
289
+ #
290
+ # note that you cannot access created_at or id with these methods
291
+ def []=(field,val)
292
+ if val
293
+ EasyRedis.redis.hset(key_name,field,val)
294
+ else
295
+ EasyRedis.redis.hdel(key_name,field)
296
+ end
297
+ end
298
+
299
+ # remove the entry
300
+ def destroy
301
+ EasyRedis.redis.zrem(prefix.pluralize,@id)
302
+ EasyRedis.redis.del(key_name)
303
+ end
304
+
305
+ # returns the key name of this entry's redis hash
306
+ def key_name
307
+ prefix + ':' + @id.to_s
308
+ end
309
+
310
+ def inspect
311
+ "#<#{self.class.name}:#{@id}>"
312
+ end
313
+
314
+
315
+ private
316
+
317
+ def self.get_temp_key
318
+ i = EasyRedis.redis.incr prefix.pluralize + ':next_tmp_id'
319
+ "#{name}:tmp_#{i}"
320
+ end
321
+
322
+ def self.proc_options(options)
323
+ opts = {}
324
+ opts[:limit] = [0,options[:limit]] if options[:limit]
325
+ opts
326
+ end
327
+
328
+ def self.prefix
329
+ self.name.downcase
330
+ end
331
+
332
+ def self.sort_prefix(field)
333
+ if field == :created_at
334
+ prefix.pluralize
335
+ else
336
+ prefix.pluralize + ':sort_' + field.to_s
337
+ end
338
+ end
339
+
340
+ def prefix
341
+ self.class.prefix
342
+ end
343
+
344
+ def sort_prefix(field)
345
+ self.class.sort_prefix(field)
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,44 @@
1
+ require './tests/test'
2
+ require 'benchmark'
3
+
4
+ $names = ["Bill","Bob","John","Jack","Alec","Mark","Nick","Evan","Eamon","Joe","Vikram"]
5
+
6
+ def rand_name
7
+ $names[rand*$names.size]
8
+ end
9
+
10
+ $count = 0
11
+ $num_add = ARGV[0] ? ARGV[0].to_i : 50000
12
+
13
+ puts "counting entries"
14
+ Benchmark.bm(7) do |bm|
15
+ bm.report { $count = Man.count }
16
+ end
17
+ puts
18
+
19
+ puts "destroying #{$count} previous entries"
20
+ Benchmark.bm(7) do |bm|
21
+ bm.report { Man.destroy_all }
22
+ end
23
+ puts
24
+
25
+ puts "adding #{$num_add} new entries"
26
+ Benchmark.bm(7) do |bm|
27
+ bm.report { $num_add.times { m = Man.new ; m.name = rand_name } }
28
+ end
29
+ puts
30
+
31
+ puts "sorting by name"
32
+ Benchmark.bm(7) do |bm|
33
+ bm.report("ruby:") { Man.all.sort_by { |m| m.name } }
34
+ bm.report("redis:") { Man.sort_by(:name) }
35
+ end
36
+ puts
37
+
38
+ puts "finding all entries by a particular name"
39
+ Benchmark.bm(7) do |bm|
40
+ name = rand_name
41
+ bm.report("ruby:") { Man.all.select {|m| m.name == name} }
42
+ bm.report("redis:") { Man.search_by(:name,name) }
43
+ end
44
+ puts
@@ -0,0 +1,10 @@
1
+ require './lib/easyredis'
2
+
3
+ class Man < EasyRedis::Model
4
+ field :name
5
+ field :friend
6
+
7
+ sort_on :name
8
+ end
9
+
10
+ EasyRedis.connect
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easyredis
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Alec Benzer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-02-05 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: redis
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :development
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :development
37
+ version_requirements: *id002
38
+ description: simple framework designed to make using redis as a database simpler
39
+ email: alecbezer @nospam@ gmail.com
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ extra_rdoc_files:
45
+ - README.md
46
+ - lib/easyredis.rb
47
+ files:
48
+ - Manifest
49
+ - README.md
50
+ - Rakefile
51
+ - lib/easyredis.rb
52
+ - tests/benchmark.rb
53
+ - tests/test.rb
54
+ - easyredis.gemspec
55
+ has_rdoc: true
56
+ homepage: https://github.com/alecbenzer/easyredis
57
+ licenses: []
58
+
59
+ post_install_message:
60
+ rdoc_options:
61
+ - --line-numbers
62
+ - --inline-source
63
+ - --title
64
+ - Easyredis
65
+ - --main
66
+ - README.md
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "1.2"
81
+ requirements: []
82
+
83
+ rubyforge_project: easyredis
84
+ rubygems_version: 1.5.0
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: simple framework designed to make using redis as a database simpler
88
+ test_files: []
89
+