silver 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.2"
12
+ gem "rcov", ">= 0"
13
+ gem "redis", "~> 2.1.1"
14
+ gem "yajl-ruby", ">= 0.7.7"
15
+ gem "text", "~> 0.2.0"
16
+ gem "dm-core", "~> 1.0.0"
17
+ gem "dm-sqlite-adapter", "~> 1.0.0"
18
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.2.3)
5
+ data_objects (0.10.2)
6
+ addressable (~> 2.1)
7
+ diff-lcs (1.1.2)
8
+ dm-core (1.0.2)
9
+ addressable (~> 2.2)
10
+ extlib (~> 0.9.15)
11
+ dm-do-adapter (1.0.2)
12
+ data_objects (~> 0.10.2)
13
+ dm-core (~> 1.0.2)
14
+ dm-sqlite-adapter (1.0.2)
15
+ dm-do-adapter (~> 1.0.2)
16
+ do_sqlite3 (~> 0.10.2)
17
+ do_sqlite3 (0.10.2)
18
+ data_objects (= 0.10.2)
19
+ extlib (0.9.15)
20
+ git (1.2.5)
21
+ jeweler (1.5.2)
22
+ bundler (~> 1.0.0)
23
+ git (>= 1.2.5)
24
+ rake
25
+ rake (0.8.7)
26
+ rcov (0.9.9)
27
+ redis (2.1.1)
28
+ rspec (2.3.0)
29
+ rspec-core (~> 2.3.0)
30
+ rspec-expectations (~> 2.3.0)
31
+ rspec-mocks (~> 2.3.0)
32
+ rspec-core (2.3.1)
33
+ rspec-expectations (2.3.0)
34
+ diff-lcs (~> 1.1.2)
35
+ rspec-mocks (2.3.0)
36
+ text (0.2.0)
37
+ yajl-ruby (0.7.9)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ bundler (~> 1.0.0)
44
+ dm-core (~> 1.0.0)
45
+ dm-sqlite-adapter (~> 1.0.0)
46
+ jeweler (~> 1.5.2)
47
+ rcov
48
+ redis (~> 2.1.1)
49
+ rspec (~> 2.3.0)
50
+ text (~> 0.2.0)
51
+ yajl-ruby (>= 0.7.7)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Erik Hin-tone
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,92 @@
1
+ # Silver
2
+
3
+ Silver is a lightweight, redis-backed database cacher and indexer.
4
+
5
+ ## Getting Started
6
+
7
+ As it says on the tin, Silver is going to make your database queries much faster. Now it is no secret that Redis is fantastic to use as a cache/index. However, you have to write the same boilerplate to use it as cache over and over: find most recent cached entry, look for newer entries in the database, cache it to redis, combine with old results. Rinse, repeat.
8
+ The goal of Silver is so that you never have to do that again. Rather than connecting to the database/service for you, you simply wrap your calls in Silver and it does the rest. This means you can use silver to speed up calls to databases, calls APIs, CURLS, really whatever you want.
9
+
10
+ ### A Simple Caching Example
11
+
12
+ First make sure you have Silver installed.
13
+ gem install silver
14
+ Now, let's pretend you have an app that queries your database for entries frequently. Entries are added frequently. Furthermore, you only want Entries that come from a specific blog, blog #12. Also you want to grab something from an association of the Entry row in the database. Let's say the author's name.
15
+ First, instantiate a new cache object.
16
+ cache = Silver::Cache.new("12_entries","created_time") do |date|
17
+ Entry.all(:order => :created_time.desc, :created_time.gt => date, :blog_id => 12)
18
+ end
19
+ The first paramater passed to the constructor is the name you want to give to this cache in Redis as Silver allows to creates as many caches for different queries as you would like. The second paramater is the name of the field that you will be using to determine if there are new entries. Finally, you pass the constructor a block that will receive the date of the newest cached entry from Redis. You must return the entries in reverse chronological order for Silver to be able to keep them in order. Silver will then query the database/service for newer entries when the instance's find method is called.
20
+ results = cache.find do |entry|
21
+ attrs = entry.attributes
22
+ author = {:author_name => entry.author[:name]}
23
+ attrs.merge author
24
+ end
25
+ The find method of a cache instance takes a block that will be called for every new entry. The results of the block call should be a hash that will be stored in the cache. The whole thing will be converted into JSON and stashed in the Redis cache. From now on the database will never have to be hit again to return this value. The find method returns an array of all the results old and new from the Redis cache.
26
+ If you just want to read from the cache without hitting the database, simply call find without a block and with a single param: false
27
+ results = cache.find(false)
28
+ Currently, the cache does not support the changing of cached entries and is, thus, intended for data that is unlikely to change once it has been written to the database. This feature will be included in future releases of Silver.
29
+ Finally, Silver provides a cull method.
30
+ cache.cull(30)
31
+ This will cut the Redis cache down to the 30 most recent items.
32
+
33
+ ### A Simple Indexing Example
34
+
35
+ However, Silver is not just a simple cache. It can also be used to index a database. It is optimized to index based on short text, such as names, captions, tag lists, excerpts, tweets etc. There is nothing stopping you from using on longer fields such as body text except the size of your memory alloted to Redis. Silver uses a stupidly simple fuzzy text search. The search will likely be augmented in the future.
36
+ Here's how you would index a mess of photos by their captions, falling back on their filename if no caption is given.
37
+ First, instantiate a new index object.
38
+ index = Silver::Index.new("blog_pictures","created_time") do |date|
39
+ Picture.all(:order => :created_time.desc, :created_time.gt => date)
40
+ end
41
+ This is the same deal as before with Silver::Cache: redis key name, time field, ordering block.
42
+ Next, call the find_and_update method of the instance.
43
+ index.find_and_update do |result|
44
+ output = result.label || result.filename || ""
45
+ id = result.id
46
+ [id,output]
47
+ end
48
+ Find_and_update takes a block that will be called for each db-fetched result. This block should return a two item array of the row's id, first, and the value we are using for indexing second. As you can see in the example, Silver allows you to mix fields to use to index. It let's you do anything you want actually as long as an id and a corresponding value are returned. After calling find_and_update, your database is indexed and ready to be searched. Say, we wanted to search for photos of "Barack Obama":
49
+ search = Silver::Search.new("Barack Obama","blog_pictures")
50
+ The constructor takes a string to search for and the name of Redis key storing the index. To actually perform the search:
51
+ search.perform{|id| Picture.get(id)}
52
+ The perform method takes a block that will be passed the ids of all the id's whose indexes match the query. Perform will return an array of database/service objects for you to then interact with as you please.
53
+
54
+ ### A note about the shortcoming of the search.
55
+
56
+ As Silver is currently in beta, it's search could use some work (feel free to contribute code). Currently, it does a fuzzy search based on the double-metaphone of each word in the search and then interects the results. This means several things. First, a search for "barack Obama" will only return those entries indexed with "Barack Obama"(case-insensitive) in their indexed field. It will excludes those with just, say, "obama". Second, no levensteining is done yet. It should be.
57
+
58
+ ### Non-standard configurations
59
+
60
+ Every initializer in Silver takes, in addition to the parameters shown above, an optional options hash for Redis as the third parameter.
61
+ cache = Silver::Cache.new("12_entries","created_time",{:host => "127.0.0.1",:port => "6969"})
62
+ Also, the search initializer for Silver's indexing takes optional number and offset paramater for pagination. Default is no offest and 30 results returned.
63
+ search = Silver::Search.new("Barack Obama","blog_pictures",{:host => "127.0.0.1",:port => "6969"},50,10)
64
+ This will return results 10-60.
65
+
66
+ ### Rocco Annotated Source
67
+
68
+ * [Cacher](http://tpm.github.com/Silver/cache.html)
69
+ * [Indexer](http://tpm.github.com/Silver/indexer.html)
70
+ * [Searcher](http://tpm.github.com/Silver/search.html)
71
+
72
+ ## Improvements to be made
73
+
74
+ * Better Search
75
+ * Allow for changes to be made in the database for already cached values. (Make an uncache function that fetches that ID again and replaces it in the cache)
76
+ * Etc.
77
+
78
+ ## Contributing to silver
79
+
80
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
81
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
82
+ * Fork the project
83
+ * Start a feature/bugfix branch
84
+ * Commit and push until you are happy with your contribution
85
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
86
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
87
+
88
+ ## Copyright
89
+
90
+ Copyright (c) 2011 Erik Hin-tone. See LICENSE.txt for
91
+ further details.
92
+
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ gem.name = "silver"
15
+ gem.homepage = "http://github.com/tpm/silver"
16
+ gem.license = "MIT"
17
+ gem.summary = %Q{Makes your queries faster with the power of Redis.}
18
+ gem.description = %Q{A lightweight, Redis-backed cacher and indexer for databases, REST API's, really anything you can query.}
19
+ gem.email = "hinton.erik@gmail.com"
20
+ gem.authors = ["Erik Hin-tone"]
21
+ gem.add_runtime_dependency "redis", "~> 2.1.1"
22
+ gem.add_runtime_dependency "yajl-ruby", ">= 0.7.7"
23
+ gem.add_runtime_dependency "text", "~> 0.2.0"
24
+ gem.add_development_dependency "dm-core", "~> 1.0.0"
25
+ gem.add_development_dependency "dm-sqlite-adapter", "~> 1.0.0"
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "silver #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,152 @@
1
+ #### Hash extensions
2
+
3
+ # Creates a new class, BareHash, that is alike a Hash in every way except that
4
+ # it may be accessed by a symbol or a string for every key. Really the same thing
5
+ # as HashWithIndifferentAccess but without ActiveSupport
6
+
7
+ class BareHash < Hash
8
+
9
+ def [](key)
10
+
11
+ if self.include? key
12
+ self.fetch(key)
13
+ else
14
+ key.class == String ? self.fetch(key.to_sym,nil) : self.fetch(key.to_s, nil)
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+
21
+ # Monkey patches Hash to allow for conversion to a BareHash where all values are strings. This
22
+ # is good for mixing results with Redis results which are always stored as strings.
23
+
24
+ class Hash
25
+
26
+ def to_bare
27
+
28
+ bhash = BareHash.new
29
+ self.each{|k,v| bhash[k] = v}
30
+ bhash
31
+
32
+ end
33
+
34
+ end
35
+
36
+ #### Caching
37
+
38
+ module Silver
39
+
40
+ class Cache
41
+
42
+ attr_reader :key, :time_field, :query
43
+
44
+ # Creates a new cached search object.
45
+ #
46
+ # Silver does not connect to a database, the query is passed as a block. This means you can use
47
+ # Silver to cache databases, REST APIs, or anything else that can be queried.
48
+ #
49
+ # key is string to identify this and successive queries to Redis
50
+ # time_field is the name of a field used to determine whether or not there are new entries that
51
+ # are not yet cached.
52
+ # query is a block that take a date and queries the database for all entries after that date. The results
53
+ # must be returned in descending order.
54
+ #
55
+ # Example to prepare a cache and query for the database of new stories in blog #2:
56
+ #
57
+ # cache = Silver::Cache.new("news_stories",
58
+ # "created_time") do |date|
59
+ #
60
+ # Stories.all(:order => :created_time.desc,
61
+ # :created_time.gt => date
62
+ # :blog_id => 2)
63
+ #
64
+ # end
65
+
66
+
67
+ def initialize(key,time_field,redis_options={},&query)
68
+
69
+ @key = key
70
+ @time_field = time_field
71
+ @query = query
72
+ @r = Redis.new(redis_options)
73
+ @r.select 12
74
+
75
+ end
76
+
77
+ # Queries Redis, returns new entries and inserts them into Redis.
78
+ #
79
+ # update is an optional parameter that, if false, will just return cached results and not look for new ones.
80
+ # callback is block that gets called for every new results, receives the result
81
+ # and returns the hash to be cached. This can used to query associations.
82
+ #
83
+ # Example to cache and the query the database and include any categories the entry might have:
84
+ #
85
+ # cache.find do |entry|
86
+ # attrs = entry.attributes
87
+ # cats = {:categories => entry.categories}
88
+ # attrs.merge cats
89
+ # end
90
+
91
+ def find(update=true,&callback)
92
+
93
+ old_results = @r.lrange(@key,0,-1).map{|q| JSON.parse(q)}
94
+
95
+ if update
96
+ last_date = @r.get("#{@key}:last") || "1970-01-01"
97
+ new_results = @query.call(DateTime.parse(last_date))
98
+ results = new_results.map do |result|
99
+ callback.call(result)
100
+ end
101
+
102
+ if results.empty?
103
+ final_results = old_results
104
+ else
105
+ write_new(results)
106
+
107
+ # Why do we go back to Redis here instead of just merging old and new? Because it's faster and cleaner than
108
+ # selectively determining which types are changed by the to_json (like Dates) and which are preservered (like
109
+ # Hashes).
110
+
111
+ final_results = @r.lrange(@key,0,-1).map{|q| JSON.parse(q)}
112
+ end
113
+ else
114
+ final_results = old_results
115
+ end
116
+
117
+ final_results = final_results.map do |result|
118
+ result.to_bare
119
+ end
120
+ end
121
+
122
+ # A helper method to keep the redis list at a reasonable size.
123
+ #
124
+ # length is the number of entries to reduce the redis to
125
+
126
+ def cull(length)
127
+
128
+ @r.ltrim(@key,0,length-1)
129
+
130
+ end
131
+
132
+ private
133
+
134
+ # Writes the results to redis by pushing them in reverse order on the head
135
+ # of the redis list. This ensures that the first result will always be the newest.
136
+ # Also turns every result hash into JSON before writing because Redis is string based.
137
+ # Find will automatically parse these JSON strings upon retrieval.
138
+
139
+ def write_new(results)
140
+
141
+ new_date = results[0][@time_field].to_s
142
+ @r.set("#{@key}:last",new_date)
143
+
144
+ results.reverse.each do |result|
145
+ @r.lpush(@key,result.to_json)
146
+ end
147
+
148
+ end
149
+
150
+ end
151
+
152
+ end
@@ -0,0 +1 @@
1
+ COMMON_WORDS = %w[about, after, again, air, all, along, also, an, and, another, any, are, around, as, at, away, back, be, because, been, before, below, between, both, but, by, came, can, come, could, day, did, different, do, does, don't, down, each, end, even, every, few, find, first, for, found, from, get, give, go, good, great, had, has, have, he, help, her, here, him, his, home, house, how, I, if, in, into, is, it, its, just, know, large, last, left, like, line, little, long, look, made, make, man, many, may, me, men, might, more, most, Mr., must, my, name, never, new, next, no, not, now, number, of, off, old, on, one, only, or, other, our, out, over, own, part, people, place, put, read, right, said, same, saw, say, see, she, should, show, small, so, some, something, sound, still, such, take, tell, than, that, the, them, then, there, these, they, thing, think, this, those, thought, three, through, time, to, together, too, two, under, up, us, use, very, want, water, way, we, well, went, were, what, when, where, which, while, who, why, will, with, word, work, world, would, write, year, you, your, was]
@@ -0,0 +1,126 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/common_words.rb')
2
+
3
+ #### Boring new error
4
+
5
+ # Just makes a new sublcass of RuntimeError so we can raise database
6
+ # errors later
7
+
8
+ class AttrError < RuntimeError
9
+ end
10
+
11
+ #### Indexing
12
+
13
+ module Silver
14
+
15
+ class Index
16
+
17
+ # Create a new index for some given database or web service. Silver
18
+ # doesn't do any connecting to a database, it requires you to pass
19
+ # a query to it as a block. Use whatever ORM you like best.
20
+ #
21
+ # key is the name this index will be stored with in Redis.
22
+ # time_field is the name of the field used for determining whether or not
23
+ # there are new entries.
24
+ # query is a block that takes the date of the most recently indexed item
25
+ # as a parameter. Query should always sort entries in descending order.
26
+ #
27
+ # Example to index all large pictures based on their captions:
28
+ #
29
+ # index = Silver::Index.new("picturecaptions",
30
+ # "created_time") do |time|
31
+ #
32
+ # Pictures.all(:order => :created_time.desc,
33
+ # :created_time.gt => time,
34
+ # :size => "large")
35
+ # end
36
+
37
+ def initialize(key,time_field,redis_options={},&query)
38
+ @r = Redis.new(redis_options)
39
+ @r.select 12
40
+ @key = key
41
+ @time_field = time_field
42
+ @query = query
43
+ @new_results = []
44
+ @indexed_items = []
45
+ end
46
+
47
+ # Looks for the most recent date indexed and only fetch
48
+ # entries newer than that date from the database.
49
+ #
50
+ # Takes a block that takes a results from the database and returns the value by which to index.
51
+ # accessor should be a block that takes an individual results and returns and array containing, first,
52
+ # the item's id and, second, the value by which to index.
53
+ #
54
+ # Ex:
55
+ #
56
+ # index.find_and_update do |result|
57
+ # output = result.caption || result.label || ""
58
+ # id = result.id
59
+ # [id,output]
60
+ # end
61
+
62
+ def find_and_update(&accessor)
63
+
64
+ last_date = @r.get("#{@key}:last") || "Jan. 1, 1970"
65
+ new_results = @query.call(DateTime.parse(last_date))
66
+ if new_results.empty?
67
+ false
68
+ else
69
+ new_date = new_results[0][@time_field].to_s
70
+ @r.set("#{@key}:last",new_date)
71
+ @new_results = new_results
72
+ parse(accessor)
73
+ true
74
+ end
75
+ end
76
+
77
+
78
+ private
79
+
80
+ # Sends results off to a double metaphone method to allow for fuzzy searching.
81
+
82
+ def parse(accessor)
83
+
84
+ @new_results.each do |result|
85
+ begin
86
+ value = accessor.call(result)
87
+ time = Time.parse(result[@time_field].to_s).to_i
88
+ raise AttrError, "Specified attribute not found in item #{value[0]}" if !value
89
+ morphed_words = morph(value[1])
90
+ morphed_words.each{|word| write(word,value[0],time)}
91
+ rescue AttrError => e
92
+ puts e
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ # Takes a metaphoned string, the id of the row that contains it, and the time created/modified of that row
100
+ # and writes to the database to phonemes by which that row is now indexed.
101
+
102
+ def write(word,id,time)
103
+
104
+ text = word[0]
105
+ phonemes = word[1].compact
106
+ phonemes.each do |phoneme|
107
+ @r.zadd "#{@key}:#{phoneme}", time, id
108
+ end
109
+ end
110
+
111
+ # Note: this method needs some work/ is not perfect.
112
+ # Takes a string, removes any file extension, splits it on non-alpha characters, throws out empty words
113
+ # words smaller than 5 characters that are not capitalized and all common words. Finally it converts each
114
+ # word to its metaphoned equivalents and returns an array of the original words in its metaphones.
115
+
116
+ def morph(string)
117
+ string.gsub!(/\.[a-zA-Z]{1,4}$/,"")
118
+ words = string.split(/[^a-zA-Z]/).reject{|q| q == "" || (q.length < 5 && q.capitalize != q)} - COMMON_WORDS
119
+ morphed_words = words.map{|q| [q,Text::Metaphone.double_metaphone(q)]}
120
+ morphed_words
121
+ end
122
+
123
+ end
124
+
125
+ end
126
+
@@ -0,0 +1,71 @@
1
+ module Silver
2
+
3
+ #### Search
4
+
5
+ # Searches an indexed database. What else would it do?
6
+
7
+ class Search
8
+
9
+ # Takes a query and a redis key from a previous indexing. There is an optional offset
10
+ # that can be used to paginate. It defaults to returning 30 results.
11
+ #
12
+ # Example, query picture captions for "Barack Obama":
13
+ # search = Silver::Search.new("barack obama","picturecaption")
14
+
15
+ def initialize(query,key,redis_options={},number=30,offset=0)
16
+ @query = query
17
+ @key = key
18
+ @number = number
19
+ @offset = offset
20
+ @r = Redis.new(redis_options)
21
+ @r.select 12
22
+ end
23
+
24
+ # Send a query to be metaphoned, finds the matching ids for the query and then
25
+ # returns the results. Finally it only returns entries that are shared by both words.
26
+ #
27
+ # Takes a block that takes an id and then queries the database for that row. Again, Silver
28
+ # can be used for services that "row" is a bad metaphor like REST apis. However, it is easy
29
+ # to write.
30
+ #
31
+ # Ex:
32
+ # search.perform{|id| Picture.get(id) }
33
+
34
+ def perform(&accessor)
35
+ morphed_words = morph_words
36
+ morphed_words.map! do |word|
37
+ phones = word[1]
38
+ phones = self.find_matching_phones(phones)
39
+ phones
40
+ end
41
+ results = morphed_words.reduce{|memo,obj| memo & obj}.slice(@offset,@offset+@number)
42
+ results.map{|result| accessor.call(result)}
43
+ end
44
+
45
+ # Takes the instance's query, splits it into words, metaphones each word and returns the array of metaphoned words.
46
+
47
+ def morph_words
48
+ words = @query.split(/[^a-zA-Z0-9]/)
49
+ morphed_words = words.map{|word| [word,Text::Metaphone.double_metaphone(word)]}
50
+ morphed_words
51
+ end
52
+
53
+ # Takes an array of metaphones and returns the matching keys in the index. Since we are using double metaphone,
54
+ # it unions the results for the two possible metaphones.i
55
+
56
+ def find_matching_phones(phones)
57
+ phones.map! do |phone|
58
+ if phone
59
+ "#{@key}:#{phone}"
60
+ else
61
+ nil
62
+ end
63
+ end
64
+ phones = @r.zunionstore "temp", phones.compact
65
+ phones = @r.zrevrange "temp", 0, -1
66
+ phones
67
+ end
68
+
69
+ end
70
+
71
+ end
data/lib/silver.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'silver/indexer'
2
+ require 'silver/search'
3
+ require 'silver/cache'
4
+ require 'rubygems'
5
+ require 'redis'
6
+ require 'text'
7
+ require 'yajl/json_gem'
data/silver.gemspec ADDED
@@ -0,0 +1,102 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{silver}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Erik Hin-tone"]
12
+ s.date = %q{2011-01-31}
13
+ s.description = %q{A lightweight, Redis-backed cacher and indexer for databases, REST API's, really anything you can query.}
14
+ s.email = %q{hinton.erik@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.markdown",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "lib/silver.rb",
29
+ "lib/silver/cache.rb",
30
+ "lib/silver/common_words.rb",
31
+ "lib/silver/indexer.rb",
32
+ "lib/silver/search.rb",
33
+ "silver.gemspec",
34
+ "spec/silver_spec.rb",
35
+ "spec/spec_helper.rb",
36
+ "spec/support/db.rb",
37
+ "spec/support/spec.db"
38
+ ]
39
+ s.homepage = %q{http://github.com/tpm/silver}
40
+ s.licenses = ["MIT"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = %q{1.3.7}
43
+ s.summary = %q{Makes your queries faster with the power of Redis.}
44
+ s.test_files = [
45
+ "spec/silver_spec.rb",
46
+ "spec/spec_helper.rb",
47
+ "spec/support/db.rb"
48
+ ]
49
+
50
+ if s.respond_to? :specification_version then
51
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
52
+ s.specification_version = 3
53
+
54
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
55
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
56
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
57
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
58
+ s.add_development_dependency(%q<rcov>, [">= 0"])
59
+ s.add_development_dependency(%q<redis>, ["~> 2.1.1"])
60
+ s.add_development_dependency(%q<yajl-ruby>, [">= 0.7.7"])
61
+ s.add_development_dependency(%q<text>, ["~> 0.2.0"])
62
+ s.add_development_dependency(%q<dm-core>, ["~> 1.0.0"])
63
+ s.add_development_dependency(%q<dm-sqlite-adapter>, ["~> 1.0.0"])
64
+ s.add_runtime_dependency(%q<redis>, ["~> 2.1.1"])
65
+ s.add_runtime_dependency(%q<yajl-ruby>, [">= 0.7.7"])
66
+ s.add_runtime_dependency(%q<text>, ["~> 0.2.0"])
67
+ s.add_development_dependency(%q<dm-core>, ["~> 1.0.0"])
68
+ s.add_development_dependency(%q<dm-sqlite-adapter>, ["~> 1.0.0"])
69
+ else
70
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
71
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
72
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
73
+ s.add_dependency(%q<rcov>, [">= 0"])
74
+ s.add_dependency(%q<redis>, ["~> 2.1.1"])
75
+ s.add_dependency(%q<yajl-ruby>, [">= 0.7.7"])
76
+ s.add_dependency(%q<text>, ["~> 0.2.0"])
77
+ s.add_dependency(%q<dm-core>, ["~> 1.0.0"])
78
+ s.add_dependency(%q<dm-sqlite-adapter>, ["~> 1.0.0"])
79
+ s.add_dependency(%q<redis>, ["~> 2.1.1"])
80
+ s.add_dependency(%q<yajl-ruby>, [">= 0.7.7"])
81
+ s.add_dependency(%q<text>, ["~> 0.2.0"])
82
+ s.add_dependency(%q<dm-core>, ["~> 1.0.0"])
83
+ s.add_dependency(%q<dm-sqlite-adapter>, ["~> 1.0.0"])
84
+ end
85
+ else
86
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
87
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
88
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
89
+ s.add_dependency(%q<rcov>, [">= 0"])
90
+ s.add_dependency(%q<redis>, ["~> 2.1.1"])
91
+ s.add_dependency(%q<yajl-ruby>, [">= 0.7.7"])
92
+ s.add_dependency(%q<text>, ["~> 0.2.0"])
93
+ s.add_dependency(%q<dm-core>, ["~> 1.0.0"])
94
+ s.add_dependency(%q<dm-sqlite-adapter>, ["~> 1.0.0"])
95
+ s.add_dependency(%q<redis>, ["~> 2.1.1"])
96
+ s.add_dependency(%q<yajl-ruby>, [">= 0.7.7"])
97
+ s.add_dependency(%q<text>, ["~> 0.2.0"])
98
+ s.add_dependency(%q<dm-core>, ["~> 1.0.0"])
99
+ s.add_dependency(%q<dm-sqlite-adapter>, ["~> 1.0.0"])
100
+ end
101
+ end
102
+
@@ -0,0 +1,263 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/support/db.rb')
3
+
4
+ describe "indexer" do
5
+
6
+ it "correctly metaphones searches" do
7
+
8
+ search = Silver::Search.new("Barack Obama","specparents")
9
+ morph = search.morph_words
10
+ morph.should eq([["Barack",["PRK",nil]],["Obama",["APM",nil]]])
11
+
12
+ end
13
+
14
+ it "correctly metaphones items" do
15
+
16
+ index = Silver::Index.new("specparents","date") do |date|
17
+ Parent.all(:order => :date.desc, :date.gt => date)
18
+ end
19
+ morph = index.send("morph","barack-obama.JpEg")
20
+ morph.should eq([["barack",["PRK",nil]],["obama",["APM",nil]]])
21
+
22
+ end
23
+
24
+ it "indexes the database" do
25
+ r = Redis.new
26
+ r.select 12
27
+ keys = r.keys("specparents:*")
28
+ keys.each{|key| r.del key}
29
+ r.keys("specparents:*").should eq([])
30
+
31
+ index = Silver::Index.new("specparents","date") do |date|
32
+ Parent.all(:order => :date.desc, :date.gt => date)
33
+ end
34
+
35
+ output = index.find_and_update do |result|
36
+ output = result.name
37
+ id = result.id
38
+ [id,output]
39
+ end
40
+
41
+ r.keys("specparents:*").should include("specparents:PS")
42
+ r.keys("specparents:*").should include("specparents:ARK")
43
+
44
+ end
45
+
46
+
47
+ it "returns false when there are no new updates" do
48
+
49
+ index = Silver::Index.new("specparents","date") do |date|
50
+ Parent.all(:order => :date.desc, :date.gt => date)
51
+ end
52
+
53
+ output = index.find_and_update do |result|
54
+ output = result.name
55
+ id = result.id
56
+ [id,output]
57
+ end
58
+
59
+ output.should eq(false)
60
+
61
+ end
62
+
63
+ it "searches the index" do
64
+
65
+ search = Silver::Search.new("Erik","specparents")
66
+ results = search.perform{|id| Parent.get(id)}
67
+ results[0]["name"].should eq("Erik")
68
+ results[0]["age"].should eq(24)
69
+ search = Silver::Search.new("Erik Hinton","specparents")
70
+ results = search.perform{|id| Parent.get(id)}
71
+ results[0]["name"].should eq("Erik Hinton")
72
+ results[0]["age"].should eq(2)
73
+
74
+ end
75
+
76
+ it "updates the index" do
77
+
78
+ r = Redis.new
79
+ r.select 12
80
+
81
+ index = Silver::Index.new("specparents","date") do |date|
82
+ Parent.all(:order => :date.desc, :date.gt => date)
83
+ end
84
+
85
+ p = Parent.create(:name => "Update",
86
+ :age => 1,
87
+ :date => DateTime.now)
88
+
89
+ output = index.find_and_update do |result|
90
+ output = result.name
91
+ id = result.id
92
+ [id,output]
93
+ end
94
+
95
+ output.should eq(true)
96
+ r.keys("specparents:*").should include("specparents:APTT")
97
+ p.destroy
98
+
99
+ end
100
+
101
+ end
102
+
103
+ describe "cacher" do
104
+
105
+ it "caches new additions to the database" do
106
+ r = Redis.new
107
+ r.select 12
108
+ r.del "parents"
109
+ r.del "parents:last"
110
+
111
+ cache = Silver::Cache.new("parents","date") do |date|
112
+ Parent.all(:order => :date.desc, :date.gt => date)
113
+ end
114
+
115
+ results = cache.find do |result|
116
+ result.attributes
117
+ end
118
+ results.should eq([{"name"=>"Baz", "id"=>2, "age"=>33, "date"=>"2011-01-28T10:40:45-05:00"}, {"name"=>"Erik", "id"=>1, "age"=>24, "date"=>"2011-01-28T10:34:10-05:00"}, {"name"=>"Erik Hinton", "id"=>11, "age"=>2, "date"=>"1980-01-02T00:00:00+00:00"}])
119
+ cached_results = r.lrange "parents",0,-1
120
+ cached_results.map{|result| JSON.parse(result)}.should eq([{"name"=>"Baz", "id"=>2, "age"=>33, "date"=>"2011-01-28T10:40:45-05:00"}, {"name"=>"Erik", "id"=>1, "age"=>24, "date"=>"2011-01-28T10:34:10-05:00"}, {"name"=>"Erik Hinton", "id"=>11, "age"=>2, "date"=>"1980-01-02T00:00:00+00:00"}])
121
+ end
122
+
123
+ it "caches associations" do
124
+ r = Redis.new
125
+ r.select 12
126
+ r.del "parents"
127
+ r.del "parents:last"
128
+
129
+ cache = Silver::Cache.new("parents","date") do |date|
130
+ Parent.all(:order => :date.desc, :date.gt => date)
131
+ end
132
+
133
+ results = cache.find do |parent|
134
+ children = parent.children
135
+ names = children.map{|child| child["name"]}
136
+ attrs = parent.attributes
137
+ c = {:children => names}
138
+ attrs.merge c
139
+ end
140
+
141
+ results.should eq([{"name"=>"Baz", "id"=>2, "age"=>33, "date"=>"2011-01-28T10:40:45-05:00", "children"=>["Bar"]},
142
+ {"name"=>"Erik", "id"=>1, "age"=>24, "date"=>"2011-01-28T10:34:10-05:00", "children"=>["Foo"]},
143
+ {"name"=>"Erik Hinton", "id"=>11, "age"=>2, "date"=>"1980-01-02T00:00:00+00:00", "children"=>[]}])
144
+ cached_results = r.lrange "parents",0,-1
145
+ cached_results.map{|result| JSON.parse(result)}.should eq([{"name"=>"Baz", "id"=>2, "age"=>33, "date"=>"2011-01-28T10:40:45-05:00", "children"=>["Bar"]},
146
+ {"name"=>"Erik", "id"=>1, "age"=>24, "date"=>"2011-01-28T10:34:10-05:00", "children"=>["Foo"]},
147
+ {"name"=>"Erik Hinton", "id"=>11, "age"=>2, "date"=>"1980-01-02T00:00:00+00:00", "children"=>[]}])
148
+
149
+ end
150
+
151
+ it "retrieves just old results" do
152
+
153
+ cache = Silver::Cache.new("parents","date") do |date|
154
+ Parent.all(:order => :date.desc, :date.gt => date)
155
+ end
156
+ results = cache.find(false)
157
+ results.should eq([{"name"=>"Baz", "id"=>2, "age"=>33, "date"=>"2011-01-28T10:40:45-05:00", "children"=>["Bar"]},
158
+ {"name"=>"Erik", "id"=>1, "age"=>24, "date"=>"2011-01-28T10:34:10-05:00", "children"=>["Foo"]},
159
+ {"name"=>"Erik Hinton", "id"=>11, "age"=>2, "date"=>"1980-01-02T00:00:00+00:00", "children"=>[]}])
160
+
161
+ end
162
+
163
+ end
164
+
165
+ describe "barehash" do
166
+
167
+ it "makes an indifferent hash" do
168
+ a = BareHash.new
169
+ a[:name] = "Erik"
170
+ a["age"] = 14
171
+ a["name"].should eq("Erik")
172
+ a[:age].should eq(14)
173
+ end
174
+
175
+ it "converts a hash to a barehash" do
176
+
177
+ a = {:name => "Erik", "age" => 24, "DOB" => DateTime.parse("Jan. 3, 1987")}
178
+ b = a.to_bare
179
+ b["name"].should eq("Erik")
180
+ b["age"].should eq(24)
181
+ b["DOB"].should eq(DateTime.parse("Jan. 3, 1987"))
182
+
183
+ end
184
+
185
+ end
186
+
187
+ #describe "reader" do
188
+
189
+ #it "reads the database" do
190
+ #index = Silver::Index.new("Asset","label",{:id => 15})
191
+ #items = index.read
192
+ #items[0].label.should eq("petraeus-testify-med.jpg")
193
+ #end
194
+
195
+ #end
196
+
197
+ #describe "parser" do
198
+ #it "registers the correct time" do
199
+ #index = Silver::Index.new("Asset","label",{:id => 15})
200
+ #items = index.read
201
+ #parsed_item = index.parse(items[0])
202
+ #parsed_item[0].should eq(1207685164)
203
+ #end
204
+
205
+ #it "parses the string" do
206
+ #index = Silver::Index.new("Asset","label",{:id => 15})
207
+ #items = index.read
208
+ #parsed_item = index.parse(items[0])
209
+ #parsed_item[1].should eq([["petraeus", ["PTRS", nil]], ["testify", ["TSTF", nil]], ["med", ["MT", nil]]])
210
+ #end
211
+ #end
212
+
213
+ #describe "morpher" do
214
+ #it "breaks strings into words" do
215
+ #string = "barack-obama-happy.jpg"
216
+ #parsed_string = Silver::Index.morph(string)
217
+ #parsed_string[0][0].should eq("barack")
218
+ #parsed_string[2][0].should eq("happy")
219
+ #end
220
+ #it "correctly sounds out words" do
221
+ #string = "cat-hard-tack.jpg"
222
+ #parsed_string = Silver::Index.morph(string)
223
+ #parsed_string[0][1].should eq(["KT",nil])
224
+ #parsed_string[2][1].should eq(["TK",nil])
225
+ #end
226
+ #end
227
+
228
+ #describe "writer" do
229
+
230
+ #it "writes to redis" do
231
+ #index = Silver::Index.new("Asset","caption/label",{:parent => nil, :limit => 2})
232
+ #r = Redis.new
233
+ #r.select 4
234
+ #items = index.read
235
+ #if items == []
236
+ #puts "no new items to test"
237
+ #else
238
+ #parsed_item = index.parse(items[1])
239
+ #index.process
240
+ #r.keys.should include("captionlabel:#{parsed_item[1][0][1][0]}")
241
+ #end
242
+ #end
243
+
244
+ #end
245
+
246
+ #describe "searcher" do
247
+
248
+ #it "finds matching phones" do
249
+ #search = Silver::Search.new("obama","Asset","captionlabel",0)
250
+ #results = search.find_matching_phones(["SMT","XMT"])
251
+ #results.should include("36662")
252
+ #end
253
+
254
+ #it "perform searches" do
255
+ #search = Silver::Search.new("barack obama","Asset","captionlabel",0)
256
+ #results = search.perform
257
+ #results.length.should eq(30)
258
+ #words = (results[0].caption || "")+" "+(results[0].label || "")
259
+ #results[0].class.should eq(Asset)
260
+ #words.should =~ /obama/i
261
+ #end
262
+
263
+ #end
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rubygems'
4
+ require 'rspec'
5
+ require 'silver'
6
+
7
+ # Requires supporting files with custom matchers and macros, etc,
8
+ # in ./support/ and its subdirectories.
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
10
+
11
+ RSpec.configure do |config|
12
+
13
+ end
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'dm-core'
3
+
4
+ DB = "sqlite://#{File.expand_path(File.dirname(__FILE__))}/spec.db"
5
+
6
+ DataMapper.setup(:default,DB)
7
+ DataMapper.finalize
8
+
9
+ class Parent
10
+ include DataMapper::Resource
11
+
12
+ property :id, Serial
13
+ property :name, String
14
+ property :date, DateTime
15
+ property :age, Integer
16
+
17
+ has n, :children
18
+ end
19
+
20
+ class Child
21
+ include DataMapper::Resource
22
+
23
+ property :id, Serial
24
+ property :name, String
25
+ property :wow_factor, Integer
26
+
27
+ belongs_to :parent
28
+ end
29
+
Binary file
metadata ADDED
@@ -0,0 +1,308 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: silver
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
+ platform: ruby
12
+ authors:
13
+ - Erik Hin-tone
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-31 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 2
32
+ - 3
33
+ - 0
34
+ version: 2.3.0
35
+ name: rspec
36
+ requirement: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 23
46
+ segments:
47
+ - 1
48
+ - 0
49
+ - 0
50
+ version: 1.0.0
51
+ name: bundler
52
+ requirement: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 7
62
+ segments:
63
+ - 1
64
+ - 5
65
+ - 2
66
+ version: 1.5.2
67
+ name: jeweler
68
+ requirement: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 3
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ name: rcov
82
+ requirement: *id004
83
+ - !ruby/object:Gem::Dependency
84
+ type: :development
85
+ prerelease: false
86
+ version_requirements: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ~>
90
+ - !ruby/object:Gem::Version
91
+ hash: 9
92
+ segments:
93
+ - 2
94
+ - 1
95
+ - 1
96
+ version: 2.1.1
97
+ name: redis
98
+ requirement: *id005
99
+ - !ruby/object:Gem::Dependency
100
+ type: :development
101
+ prerelease: false
102
+ version_requirements: &id006 !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 13
108
+ segments:
109
+ - 0
110
+ - 7
111
+ - 7
112
+ version: 0.7.7
113
+ name: yajl-ruby
114
+ requirement: *id006
115
+ - !ruby/object:Gem::Dependency
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: &id007 !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ~>
122
+ - !ruby/object:Gem::Version
123
+ hash: 23
124
+ segments:
125
+ - 0
126
+ - 2
127
+ - 0
128
+ version: 0.2.0
129
+ name: text
130
+ requirement: *id007
131
+ - !ruby/object:Gem::Dependency
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: &id008 !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ~>
138
+ - !ruby/object:Gem::Version
139
+ hash: 23
140
+ segments:
141
+ - 1
142
+ - 0
143
+ - 0
144
+ version: 1.0.0
145
+ name: dm-core
146
+ requirement: *id008
147
+ - !ruby/object:Gem::Dependency
148
+ type: :development
149
+ prerelease: false
150
+ version_requirements: &id009 !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ~>
154
+ - !ruby/object:Gem::Version
155
+ hash: 23
156
+ segments:
157
+ - 1
158
+ - 0
159
+ - 0
160
+ version: 1.0.0
161
+ name: dm-sqlite-adapter
162
+ requirement: *id009
163
+ - !ruby/object:Gem::Dependency
164
+ type: :runtime
165
+ prerelease: false
166
+ version_requirements: &id010 !ruby/object:Gem::Requirement
167
+ none: false
168
+ requirements:
169
+ - - ~>
170
+ - !ruby/object:Gem::Version
171
+ hash: 9
172
+ segments:
173
+ - 2
174
+ - 1
175
+ - 1
176
+ version: 2.1.1
177
+ name: redis
178
+ requirement: *id010
179
+ - !ruby/object:Gem::Dependency
180
+ type: :runtime
181
+ prerelease: false
182
+ version_requirements: &id011 !ruby/object:Gem::Requirement
183
+ none: false
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ hash: 13
188
+ segments:
189
+ - 0
190
+ - 7
191
+ - 7
192
+ version: 0.7.7
193
+ name: yajl-ruby
194
+ requirement: *id011
195
+ - !ruby/object:Gem::Dependency
196
+ type: :runtime
197
+ prerelease: false
198
+ version_requirements: &id012 !ruby/object:Gem::Requirement
199
+ none: false
200
+ requirements:
201
+ - - ~>
202
+ - !ruby/object:Gem::Version
203
+ hash: 23
204
+ segments:
205
+ - 0
206
+ - 2
207
+ - 0
208
+ version: 0.2.0
209
+ name: text
210
+ requirement: *id012
211
+ - !ruby/object:Gem::Dependency
212
+ type: :development
213
+ prerelease: false
214
+ version_requirements: &id013 !ruby/object:Gem::Requirement
215
+ none: false
216
+ requirements:
217
+ - - ~>
218
+ - !ruby/object:Gem::Version
219
+ hash: 23
220
+ segments:
221
+ - 1
222
+ - 0
223
+ - 0
224
+ version: 1.0.0
225
+ name: dm-core
226
+ requirement: *id013
227
+ - !ruby/object:Gem::Dependency
228
+ type: :development
229
+ prerelease: false
230
+ version_requirements: &id014 !ruby/object:Gem::Requirement
231
+ none: false
232
+ requirements:
233
+ - - ~>
234
+ - !ruby/object:Gem::Version
235
+ hash: 23
236
+ segments:
237
+ - 1
238
+ - 0
239
+ - 0
240
+ version: 1.0.0
241
+ name: dm-sqlite-adapter
242
+ requirement: *id014
243
+ description: A lightweight, Redis-backed cacher and indexer for databases, REST API's, really anything you can query.
244
+ email: hinton.erik@gmail.com
245
+ executables: []
246
+
247
+ extensions: []
248
+
249
+ extra_rdoc_files:
250
+ - LICENSE.txt
251
+ - README.markdown
252
+ files:
253
+ - .document
254
+ - .rspec
255
+ - Gemfile
256
+ - Gemfile.lock
257
+ - LICENSE.txt
258
+ - README.markdown
259
+ - Rakefile
260
+ - VERSION
261
+ - lib/silver.rb
262
+ - lib/silver/cache.rb
263
+ - lib/silver/common_words.rb
264
+ - lib/silver/indexer.rb
265
+ - lib/silver/search.rb
266
+ - silver.gemspec
267
+ - spec/silver_spec.rb
268
+ - spec/spec_helper.rb
269
+ - spec/support/db.rb
270
+ - spec/support/spec.db
271
+ has_rdoc: true
272
+ homepage: http://github.com/tpm/silver
273
+ licenses:
274
+ - MIT
275
+ post_install_message:
276
+ rdoc_options: []
277
+
278
+ require_paths:
279
+ - lib
280
+ required_ruby_version: !ruby/object:Gem::Requirement
281
+ none: false
282
+ requirements:
283
+ - - ">="
284
+ - !ruby/object:Gem::Version
285
+ hash: 3
286
+ segments:
287
+ - 0
288
+ version: "0"
289
+ required_rubygems_version: !ruby/object:Gem::Requirement
290
+ none: false
291
+ requirements:
292
+ - - ">="
293
+ - !ruby/object:Gem::Version
294
+ hash: 3
295
+ segments:
296
+ - 0
297
+ version: "0"
298
+ requirements: []
299
+
300
+ rubyforge_project:
301
+ rubygems_version: 1.3.7
302
+ signing_key:
303
+ specification_version: 3
304
+ summary: Makes your queries faster with the power of Redis.
305
+ test_files:
306
+ - spec/silver_spec.rb
307
+ - spec/spec_helper.rb
308
+ - spec/support/db.rb