thebes 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +84 -0
- data/LICENSE +19 -0
- data/Rakefile +7 -0
- data/Readme.mkd +140 -0
- data/lib/thebes.rb +19 -0
- data/lib/thebes/config_writer.rb +29 -0
- data/lib/thebes/query.rb +32 -0
- data/lib/thebes/railtie.rb +33 -0
- data/lib/thebes/sphinx_search.rb +152 -0
- data/lib/thebes/sphinxql/client.rb +19 -0
- data/lib/thebes/sphinxql/query.rb +35 -0
- data/lib/thebes/version.rb +3 -0
- data/rails/init.rb +1 -0
- data/railties/sphinx_config_generator.rb +13 -0
- data/railties/thebes.rake +18 -0
- data/spec/spec_helper.rb +83 -0
- data/spec/support/active_record.rb +22 -0
- data/spec/support/database.yml.example +9 -0
- data/spec/support/test.sphinx.conf.erb +70 -0
- data/spec/thebes/query_spec.rb +98 -0
- data/spec/thebes/sphinxql/client_spec.rb +15 -0
- data/spec/thebes/sphinxql/query_spec.rb +57 -0
- data/spec/thebes/version_spec.rb +7 -0
- data/templates/sphinx.conf.erb +12 -0
- data/templates/sphinx.yml +16 -0
- data/templates/sphinx_servers.yml +16 -0
- data/thebes.gemspec +30 -0
- metadata +219 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
thebes (0.0.3)
|
5
|
+
actionpack (>= 3.0.3)
|
6
|
+
activerecord (>= 3.0.3)
|
7
|
+
mysql2
|
8
|
+
riddle
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: http://rubygems.org/
|
12
|
+
specs:
|
13
|
+
abstract (1.0.0)
|
14
|
+
actionpack (3.0.3)
|
15
|
+
activemodel (= 3.0.3)
|
16
|
+
activesupport (= 3.0.3)
|
17
|
+
builder (~> 2.1.2)
|
18
|
+
erubis (~> 2.6.6)
|
19
|
+
i18n (~> 0.4)
|
20
|
+
rack (~> 1.2.1)
|
21
|
+
rack-mount (~> 0.6.13)
|
22
|
+
rack-test (~> 0.5.6)
|
23
|
+
tzinfo (~> 0.3.23)
|
24
|
+
activemodel (3.0.3)
|
25
|
+
activesupport (= 3.0.3)
|
26
|
+
builder (~> 2.1.2)
|
27
|
+
i18n (~> 0.4)
|
28
|
+
activerecord (3.0.3)
|
29
|
+
activemodel (= 3.0.3)
|
30
|
+
activesupport (= 3.0.3)
|
31
|
+
arel (~> 2.0.2)
|
32
|
+
tzinfo (~> 0.3.23)
|
33
|
+
activesupport (3.0.3)
|
34
|
+
arel (2.0.8)
|
35
|
+
builder (2.1.2)
|
36
|
+
diff-lcs (1.1.2)
|
37
|
+
erubis (2.6.6)
|
38
|
+
abstract (>= 1.0.0)
|
39
|
+
genspec (0.1.1)
|
40
|
+
rspec
|
41
|
+
sc-core-ext (>= 1.2.0)
|
42
|
+
i18n (0.5.0)
|
43
|
+
mocha (0.9.10)
|
44
|
+
rake
|
45
|
+
mysql2 (0.2.6)
|
46
|
+
rack (1.2.1)
|
47
|
+
rack-mount (0.6.13)
|
48
|
+
rack (>= 1.0.0)
|
49
|
+
rack-test (0.5.7)
|
50
|
+
rack (>= 1.0)
|
51
|
+
railties (3.0.3)
|
52
|
+
actionpack (= 3.0.3)
|
53
|
+
activesupport (= 3.0.3)
|
54
|
+
rake (>= 0.8.7)
|
55
|
+
thor (~> 0.14.4)
|
56
|
+
rake (0.8.7)
|
57
|
+
riddle (1.2.2)
|
58
|
+
rspec (2.4.0)
|
59
|
+
rspec-core (~> 2.4.0)
|
60
|
+
rspec-expectations (~> 2.4.0)
|
61
|
+
rspec-mocks (~> 2.4.0)
|
62
|
+
rspec-core (2.4.0)
|
63
|
+
rspec-expectations (2.4.0)
|
64
|
+
diff-lcs (~> 1.1.2)
|
65
|
+
rspec-mocks (2.4.0)
|
66
|
+
rspec-rails (2.4.1)
|
67
|
+
actionpack (~> 3.0)
|
68
|
+
activesupport (~> 3.0)
|
69
|
+
railties (~> 3.0)
|
70
|
+
rspec (~> 2.4.0)
|
71
|
+
sc-core-ext (1.2.1)
|
72
|
+
activesupport (>= 2.3.5)
|
73
|
+
thor (0.14.6)
|
74
|
+
tzinfo (0.3.24)
|
75
|
+
|
76
|
+
PLATFORMS
|
77
|
+
ruby
|
78
|
+
|
79
|
+
DEPENDENCIES
|
80
|
+
genspec
|
81
|
+
mocha
|
82
|
+
rspec
|
83
|
+
rspec-rails
|
84
|
+
thebes!
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Iridesco LLC (support@harvestapp.com)
|
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/Rakefile
ADDED
data/Readme.mkd
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
Thebes
|
2
|
+
======
|
3
|
+
|
4
|
+
Thebes is a thin binding layer for [Rails](http://rubyonrails.org/) and
|
5
|
+
[Sphinx](http://sphinxsearch.com/) via [Riddle](https://github.com/freelancing-god/riddle)
|
6
|
+
and [Mysql2](https://github.com/brianmario/mysql2). Thebes expects you to write
|
7
|
+
Sphinx configuration files by hand and have a rich understanding of Sphinx, but
|
8
|
+
provides configuration files and templates to ease the process.
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
|
13
|
+
To use Thebes, just add it to your `Gemfile`:
|
14
|
+
|
15
|
+
gem 'thebes'
|
16
|
+
|
17
|
+
# Or pin Thebes to git
|
18
|
+
# gem 'thebes',
|
19
|
+
# :git => 'git@github.com:harvesthq/thebes.git'
|
20
|
+
|
21
|
+
Then use `bundle install` to update your `Gemfile.lock`. If your project is using
|
22
|
+
Rails, you can take advantage of the generator to create your starting config files:
|
23
|
+
|
24
|
+
script/rails g sphinx_config
|
25
|
+
|
26
|
+
This will create the three config files that Thebes requires for easy setup:
|
27
|
+
|
28
|
+
* `sphinx.yml` - Configuration values for your sphinx.conf files.
|
29
|
+
* `sphinx.conf.erb` - The template for your sphinx.conf files.
|
30
|
+
* `sphinx_servers.yml` - Configuration of Rails, which Sphinx servers to connect to.
|
31
|
+
|
32
|
+
Flavor to taste, and run `rake thebes:build` to generate your sphinx configuation.
|
33
|
+
|
34
|
+
Querying via Sphinx's custom protocol
|
35
|
+
-------------------------------------
|
36
|
+
|
37
|
+
Generate a query against Sphinx via Riddle:
|
38
|
+
|
39
|
+
result = Thebes::Query.run do |a|
|
40
|
+
# a here is an instance of Riddle::Client:
|
41
|
+
# http://rdoc.info/github/freelancing-god/riddle/master/Riddle/Client
|
42
|
+
a.apply_filter_like_in_riddle
|
43
|
+
end
|
44
|
+
|
45
|
+
`result` will be an instance of [Riddle::Response](http://rdoc.info/github/freelancing-god/riddle/master/Riddle/Client/Response).
|
46
|
+
Usually you will want to call `.next` on it to get your returned values from
|
47
|
+
Sphinx. A more complex usage example:
|
48
|
+
|
49
|
+
# sphinx_res is a raw result from Riddle.
|
50
|
+
#
|
51
|
+
sphinx_res = Thebes::Query.run do |q|
|
52
|
+
|
53
|
+
# In Riddle, filters need to be added to a query before the
|
54
|
+
# actual query.
|
55
|
+
#
|
56
|
+
q.filters << Riddle::Client::Filter.new('active', [1])
|
57
|
+
|
58
|
+
# Pull search terms of a query.
|
59
|
+
#
|
60
|
+
query = params[:search_terms].split(' ').collect {|word|
|
61
|
+
"( =#{word} | #{word} | #{word}* )"
|
62
|
+
}.join(' ')
|
63
|
+
|
64
|
+
q.append_query query, 'documents' # Search index 'documents'.
|
65
|
+
|
66
|
+
# You can change the match_mode or alter the query in other
|
67
|
+
# ways.
|
68
|
+
#
|
69
|
+
# q.match_mode = :expr2
|
70
|
+
end
|
71
|
+
|
72
|
+
ids, weights = [], []
|
73
|
+
sphinx_res[0][:matches].each do |match|
|
74
|
+
ids << match[:attributes]['_id']
|
75
|
+
weights << match[:weight]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Fetch documents from the database.
|
79
|
+
#
|
80
|
+
@documents = Document.find(ids)
|
81
|
+
|
82
|
+
# Sort documents by weight.
|
83
|
+
#
|
84
|
+
@documents.sort! {|a,b| weights[ids.index(b.id)] <=> weights[ids.index(a.id)] }
|
85
|
+
|
86
|
+
Additionally, you can set procs to be run before the query block or right
|
87
|
+
before the query is run:
|
88
|
+
|
89
|
+
# Usually found in config/initializers/thebes.rb
|
90
|
+
#
|
91
|
+
|
92
|
+
# Before the Thebes::Query.run block.
|
93
|
+
#
|
94
|
+
Thebes::Query.before_query = Proc.new do |q|
|
95
|
+
q.match_mode = :any
|
96
|
+
q.max_matches = 10
|
97
|
+
end
|
98
|
+
|
99
|
+
# Just before the query is run.
|
100
|
+
#
|
101
|
+
Thebes::Query.before_running = Proc.new do |q|
|
102
|
+
end
|
103
|
+
|
104
|
+
Querying via Sphinxql
|
105
|
+
---------------------
|
106
|
+
|
107
|
+
Thebes has minimal support for querying Sphinx via the new [Sphinxql syntax](http://sphinxsearch.com/docs/manual-0.9.9.html#sphinxql).
|
108
|
+
Running a query returns a [Mysql2::Result](http://rdoc.info/github/brianmario/mysql2/master/Mysql2/Result)
|
109
|
+
object.
|
110
|
+
|
111
|
+
# Do a search with SphinxQL.
|
112
|
+
#
|
113
|
+
@results = Thebes::Sphinxql::Query.run "SELECT * FROM items WHERE MATCH('Horwitz')"
|
114
|
+
|
115
|
+
@ids = @results.collect {|r| r['_id'] }
|
116
|
+
|
117
|
+
Deployment Strategy
|
118
|
+
-------------------
|
119
|
+
|
120
|
+
Always check `config/sphinx.conf.erb` into your SCM. The `config/sphinx.yml` and
|
121
|
+
`config/sphinx_servers.yml` files probably should not be checked in, and any generated
|
122
|
+
`config/sphinx.conf` files should likely not be checked in.
|
123
|
+
|
124
|
+
Steps in a capistrano-style deployment:
|
125
|
+
|
126
|
+
* Copy server-side `config/sphinx_servers.yml` and `config/sphinx.yml` files into place.
|
127
|
+
* Run `rake thebes:build` to generate `config/sphinx.conf` or any other sphinx configuration files.
|
128
|
+
* Restart searchd, run the indexer.
|
129
|
+
|
130
|
+
If you want to use a cluster of Sphinx servers, several configuration files can
|
131
|
+
be generated by modifying `config/sphinx.conf` to generate the proper configurations.
|
132
|
+
Then update `config/sphinx_servers.yml` to tell your running Rails app where to
|
133
|
+
find any new servers.
|
134
|
+
|
135
|
+
Contributing
|
136
|
+
------------
|
137
|
+
|
138
|
+
Fork, clone, write a test, write some code, commit, push, send a pull request. Github FTW!
|
139
|
+
|
140
|
+
This project was open-sourced by [Harvest](http://getharvest.com/). [We're hiring!](http://www.getharvest.com/careers)
|
data/lib/thebes.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require File.dirname(__FILE__)+'/thebes/config_writer'
|
3
|
+
require File.dirname(__FILE__)+'/thebes/query'
|
4
|
+
require File.dirname(__FILE__)+'/thebes/sphinx_search'
|
5
|
+
require File.dirname(__FILE__)+'/thebes/sphinxql/client'
|
6
|
+
require File.dirname(__FILE__)+'/thebes/sphinxql/query'
|
7
|
+
require File.dirname(__FILE__)+'/thebes/railtie' if defined?(Rails::Railtie)
|
8
|
+
|
9
|
+
module Thebes
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'zlib'
|
13
|
+
ActiveRecord::Base.class_eval do
|
14
|
+
|
15
|
+
def self.to_crc32
|
16
|
+
Zlib.crc32(self.name)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
|
3
|
+
module Thebes
|
4
|
+
|
5
|
+
class ConfigWriter
|
6
|
+
|
7
|
+
def initialize template_path=nil, template_file=nil
|
8
|
+
@template_path = template_path || File.join(File.dirname(__FILE__), '..', '..', 'templates')
|
9
|
+
@template_file = template_file || 'sphinx.conf'
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_config_file
|
13
|
+
"./#{@@config_template}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def build(outfile=nil, locals={})
|
17
|
+
outfile ||= default_config_file
|
18
|
+
view = ActionView::Base.new(
|
19
|
+
@template_path,
|
20
|
+
locals
|
21
|
+
)
|
22
|
+
File.open(outfile, 'w') do |file|
|
23
|
+
file.write view.render(:file => @template_file)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/thebes/query.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'riddle'
|
2
|
+
|
3
|
+
module Thebes
|
4
|
+
|
5
|
+
class Query < Riddle::Client
|
6
|
+
cattr_accessor :before_query,
|
7
|
+
:before_running,
|
8
|
+
:servers
|
9
|
+
|
10
|
+
def initialize *args
|
11
|
+
if !args.empty? || self.class.servers.empty?
|
12
|
+
super *args
|
13
|
+
else
|
14
|
+
super *self.class.servers[rand(self.class.servers.size)]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
|
20
|
+
def run &block
|
21
|
+
client = new # would take server and port
|
22
|
+
before_query.call(client) if before_query
|
23
|
+
block.call client
|
24
|
+
before_running.call(client) if before_running
|
25
|
+
client.run
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'thebes'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module Thebes
|
5
|
+
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
|
8
|
+
rake_tasks do
|
9
|
+
load "#{File.dirname(__FILE__)}/../../railties/thebes.rake"
|
10
|
+
end
|
11
|
+
|
12
|
+
generators do
|
13
|
+
load "#{File.dirname(__FILE__)}/../../railties/sphinx_config_generator.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
initializer "thebes.initialize" do |app|
|
17
|
+
|
18
|
+
config_file = File.join(Rails.root, 'config', 'sphinx_servers.yml')
|
19
|
+
if File.exists?(config_file)
|
20
|
+
config = YAML.load(ERB.new(IO.read(config_file)).result)[Rails.env.to_s]
|
21
|
+
if config['sphinx_api']
|
22
|
+
Thebes::Query.servers = config['sphinx_api']
|
23
|
+
end
|
24
|
+
if config['sphinxql']
|
25
|
+
Thebes::Sphinxql::Client.servers = config['sphinxql']
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
#Due credit: large parts ripped out of thinking_sphinx - http://freelancing-god.github.com/ts/en/.
|
2
|
+
|
3
|
+
module Thebes
|
4
|
+
|
5
|
+
class SphinxSearch
|
6
|
+
|
7
|
+
attr_reader :results
|
8
|
+
|
9
|
+
def initialize options
|
10
|
+
@options = options
|
11
|
+
@client = Riddle::Client.new(*server)
|
12
|
+
@client.limit = options[:per_page] || 20
|
13
|
+
@client.offset = offset
|
14
|
+
@client.filters = filters
|
15
|
+
@client.field_weights = options[:field_weights] if options[:field_weights]
|
16
|
+
@client.index_weights = options[:index_weights] || {}
|
17
|
+
@client.sort_by = options[:sort_by] if options[:sort_by]
|
18
|
+
@client.match_mode = :extended
|
19
|
+
end
|
20
|
+
|
21
|
+
def search query
|
22
|
+
logger.info "Querying: '#{query}'"
|
23
|
+
@total_pages = nil
|
24
|
+
@results = { }
|
25
|
+
runtime = Benchmark.realtime {
|
26
|
+
@results = @client.query(format_query(query), indexes, '')
|
27
|
+
}
|
28
|
+
logger.error("Sphinx returned an error: #{@results[:error]}") if !@results[:error].blank?
|
29
|
+
logger.warn("Sphinx returned a warning: #{@results[:error]}") if !@results[:warning].blank?
|
30
|
+
logger.info "Found #{@results[:total_found]} results out of #{total_pages}"
|
31
|
+
logger.debug "Sphinx (#{sprintf("%f", runtime)}s)"
|
32
|
+
if @options[:ids_only]
|
33
|
+
@results[:matches].map{ |match|
|
34
|
+
match[:attributes]["sphinx_internal_id"]
|
35
|
+
}
|
36
|
+
else
|
37
|
+
instances_from_matches
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def total_pages
|
42
|
+
return 0 if @results[:total].nil?
|
43
|
+
@total_pages ||= (@results[:total] / per_page.to_f).ceil
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def server
|
49
|
+
Thebes::Query.servers[rand(Thebes::Query.servers.size)]
|
50
|
+
end
|
51
|
+
|
52
|
+
def format_query query
|
53
|
+
return query unless @options[:star]
|
54
|
+
@query ||= (@options[:star] ? star_query(query) : query).strip
|
55
|
+
end
|
56
|
+
|
57
|
+
def star_query(query)
|
58
|
+
token = @options[:star].is_a?(Regexp) ? @options[:star] : /\w+/u
|
59
|
+
query.gsub(/("#{token}(.*?#{token})?"|(?![!-])#{token})/u) do
|
60
|
+
pre, proper, post = $`, $&, $'
|
61
|
+
# E.g. "@foo", "/2", "~3", but not as part of a token
|
62
|
+
is_operator = pre.match(%r{(\W|^)[@~/]\Z})
|
63
|
+
# E.g. "foo bar", with quotes
|
64
|
+
is_quote = proper.starts_with?('"') && proper.ends_with?('"')
|
65
|
+
has_star = pre.ends_with?("*") || post.starts_with?("*")
|
66
|
+
if is_operator || is_quote || has_star
|
67
|
+
proper
|
68
|
+
else
|
69
|
+
"*#{proper}*"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def instances_from_matches
|
75
|
+
groups = results[:matches].group_by { |match| match[:attributes]["class_crc"] }
|
76
|
+
groups.each do |crc, group|
|
77
|
+
group.replace(instances_from_class(class_from_crc(crc), group))
|
78
|
+
end
|
79
|
+
results[:matches].collect do |match|
|
80
|
+
groups.detect { |crc, group|
|
81
|
+
crc == match[:attributes]["class_crc"]
|
82
|
+
}[1].compact.detect { |obj|
|
83
|
+
obj.id == match[:attributes]["sphinx_internal_id"]
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def instances_from_class(klass, matches)
|
89
|
+
ids = matches.map { |match| match[:attributes]["sphinx_internal_id"] }
|
90
|
+
instances = ids.length > 0 ? klass.where(:id => ids) : []
|
91
|
+
ids.map { |obj_id| instances.detect { |obj| obj.id == obj_id } }
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def indexes
|
96
|
+
if @options[:index]
|
97
|
+
return @options[:index]
|
98
|
+
end
|
99
|
+
@options[:classes].map { |t| ["_core", "_delta"].map { |sufix| "#{t.name.underscore}#{sufix}" }}.flatten.uniq.join(',')
|
100
|
+
end
|
101
|
+
|
102
|
+
def logger
|
103
|
+
Rails.logger
|
104
|
+
end
|
105
|
+
|
106
|
+
def current_page
|
107
|
+
@options[:page].blank? ? 1 : @options[:page].to_i
|
108
|
+
end
|
109
|
+
|
110
|
+
def offset
|
111
|
+
@options[:offset] || ((current_page - 1) * per_page)
|
112
|
+
end
|
113
|
+
|
114
|
+
def per_page
|
115
|
+
@options[:per_page] || 100
|
116
|
+
end
|
117
|
+
|
118
|
+
def filters
|
119
|
+
chain = [Riddle::Client::Filter.new('sphinx_deleted', [0])]
|
120
|
+
if @options[:with]
|
121
|
+
chain << @options[:with].map { |k, v| Riddle::Client::Filter.new(k, filter_value(v))}
|
122
|
+
end
|
123
|
+
unless @options[:classes]
|
124
|
+
raise "You'll need to explicitly pass in a list of AR classes to search as :classes"
|
125
|
+
end
|
126
|
+
chain << Riddle::Client::Filter.new('class_crc', @options[:classes].map(&:to_crc32))
|
127
|
+
chain.flatten
|
128
|
+
end
|
129
|
+
|
130
|
+
def class_from_crc(crc)
|
131
|
+
@options[:classes].detect { |klass| klass.to_crc32 == crc }
|
132
|
+
end
|
133
|
+
|
134
|
+
def filter_value(value)
|
135
|
+
case value
|
136
|
+
when Range
|
137
|
+
filter_value(value.first).first..filter_value(value.last).first
|
138
|
+
when Array
|
139
|
+
value.map { |v| filter_value(v) }.flatten
|
140
|
+
when Time
|
141
|
+
[value.to_i]
|
142
|
+
when NilClass
|
143
|
+
0
|
144
|
+
else
|
145
|
+
Array(value)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
|
3
|
+
module Thebes::Sphinxql
|
4
|
+
|
5
|
+
class Client < ::Mysql2::Client
|
6
|
+
|
7
|
+
cattr_accessor :servers
|
8
|
+
|
9
|
+
def initialize *args
|
10
|
+
if !args.empty? || (!self.class.servers || self.class.servers.empty?)
|
11
|
+
super *args
|
12
|
+
else
|
13
|
+
super self.class.servers[rand(self.class.servers.size)]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Thebes::Sphinxql
|
2
|
+
|
3
|
+
class Query
|
4
|
+
|
5
|
+
def initialize query
|
6
|
+
@query = query
|
7
|
+
@client = Client.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
@client.query self.to_sql
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_sql
|
15
|
+
case @query
|
16
|
+
when String
|
17
|
+
@query
|
18
|
+
when Array
|
19
|
+
@query.shift % (@query.collect { |q|
|
20
|
+
@client.escape(q)
|
21
|
+
})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
|
27
|
+
def run query
|
28
|
+
self.new(query).run
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'thebes/railtie'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class SphinxConfigGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
source_root "#{File.dirname(__FILE__)}/../templates/"
|
6
|
+
|
7
|
+
def copy_config_defaults
|
8
|
+
copy_file 'sphinx.conf.erb', 'config/sphinx.conf.erb'
|
9
|
+
copy_file 'sphinx.yml', 'config/sphinx.yml'
|
10
|
+
copy_file 'sphinx_servers.yml', 'config/sphinx_servers.yml'
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
namespace :thebes do
|
2
|
+
|
3
|
+
desc "Build the sphinx config files"
|
4
|
+
task :build do
|
5
|
+
unless File.exists?(File.join(Rails.root, 'config', 'sphinx.yml'))
|
6
|
+
raise 'No config file present, please create a config/sphinx.yml'
|
7
|
+
end
|
8
|
+
config = YAML.load(ERB.new(IO.read(File.join(Rails.root, 'config', 'sphinx.yml'))).result)[Rails.env.to_s]
|
9
|
+
generator = Thebes::ConfigWriter.new(File.join(Rails.root, 'config'))
|
10
|
+
config.each do |file, conf|
|
11
|
+
if file[0].chr != '/'
|
12
|
+
file = File.join(Rails.root, 'config', file)
|
13
|
+
end
|
14
|
+
generator.build file, (conf || {})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'thebes' # and any other gems you need
|
5
|
+
|
6
|
+
FileUtils.mkdir_p 'tmp'
|
7
|
+
|
8
|
+
Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.mock_with :mocha
|
12
|
+
config.use_transactional_fixtures = false
|
13
|
+
config.before :suite do
|
14
|
+
|
15
|
+
unless defined?(SPHINX)
|
16
|
+
|
17
|
+
# Fetch a database configuration.
|
18
|
+
#
|
19
|
+
db_config = YAML.load( ERB.new( IO.read(
|
20
|
+
File.join(File.dirname(__FILE__), 'support/database.yml')
|
21
|
+
) ).result )['test']
|
22
|
+
|
23
|
+
# Write a sphinx configuation for test mode.
|
24
|
+
#
|
25
|
+
generator = Thebes::ConfigWriter.new \
|
26
|
+
File.join(File.dirname(__FILE__), 'support'),
|
27
|
+
'test.sphinx.conf'
|
28
|
+
generator.build \
|
29
|
+
File.join(File.dirname(__FILE__), 'support/test.sphinx.conf'),
|
30
|
+
db_config
|
31
|
+
|
32
|
+
# Give Riddle a dummy configuration so we can use it to
|
33
|
+
# control searchd.
|
34
|
+
#
|
35
|
+
r_config = Riddle::Configuration.new
|
36
|
+
r_config.searchd.pid_file = 'tmp/searchd.pid'
|
37
|
+
r_config.searchd.log = 'tmp/searchd.log'
|
38
|
+
|
39
|
+
# Create a Riddle controller.
|
40
|
+
#
|
41
|
+
SPHINX = Riddle::Controller.new \
|
42
|
+
r_config,
|
43
|
+
File.join(File.dirname(__FILE__), 'support/test.sphinx.conf')
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
# Build an initial index.
|
48
|
+
#
|
49
|
+
SPHINX.index
|
50
|
+
|
51
|
+
# Start searchd.
|
52
|
+
#
|
53
|
+
SPHINX.start
|
54
|
+
|
55
|
+
until SPHINX.running? do
|
56
|
+
sleep 0.1
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
config.after :each do
|
61
|
+
ActiveRecord::Base.connection.execute('SHOW TABLES;').each do |table|
|
62
|
+
# Or whatever you think is appropriate.
|
63
|
+
next if table.index('schema_migrations') or table.index('roles')
|
64
|
+
ActiveRecord::Base.connection.execute("TRUNCATE #{table}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
config.after :suite do
|
68
|
+
|
69
|
+
# Stop searchd after specs run.
|
70
|
+
#
|
71
|
+
SPHINX.stop
|
72
|
+
|
73
|
+
while SPHINX.running? do
|
74
|
+
sleep 0.1
|
75
|
+
SPHINX.stop
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
RSpec::Matchers.define :define_constant do |expected|
|
82
|
+
match { |actual| actual.const_defined?(expected) }
|
83
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'mysql2'
|
3
|
+
require 'logger'
|
4
|
+
require 'rspec/rails/adapters'
|
5
|
+
require 'rspec/rails/fixture_support'
|
6
|
+
|
7
|
+
ROOT = File.join(File.dirname(__FILE__), '..')
|
8
|
+
|
9
|
+
ActiveRecord::Base.logger = Logger.new('tmp/ar_debug.log')
|
10
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read('spec/support/database.yml'))
|
11
|
+
ActiveRecord::Base.establish_connection('test')
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define :version => 0 do
|
14
|
+
create_table :items, :force => true do |t|
|
15
|
+
t.string :name
|
16
|
+
t.boolean :active
|
17
|
+
t.string :body
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Item < ActiveRecord::Base
|
22
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
indexer
|
2
|
+
{
|
3
|
+
mem_limit = 32M
|
4
|
+
}
|
5
|
+
|
6
|
+
searchd
|
7
|
+
{
|
8
|
+
listen = 0.0.0.0:9333
|
9
|
+
listen = 0.0.0.0:9334:mysql41
|
10
|
+
log = tmp/searchd.log
|
11
|
+
query_log = tmp/searchd.query.log
|
12
|
+
pid_file = tmp/searchd.pid
|
13
|
+
}
|
14
|
+
|
15
|
+
source items_core_0
|
16
|
+
{
|
17
|
+
type = mysql
|
18
|
+
sql_host = <%= @host %>
|
19
|
+
sql_user = <%= @username %>
|
20
|
+
sql_pass = <%= @password %>
|
21
|
+
sql_db = <%= @database %>
|
22
|
+
sql_sock = <%= @socket %>
|
23
|
+
sql_query_pre = SET NAMES utf8
|
24
|
+
sql_query_pre = SET TIME_ZONE = '+0:00'
|
25
|
+
sql_query = SELECT SQL_NO_CACHE \
|
26
|
+
( `items`.`id` * 66 + 0 ) AS `id`, \
|
27
|
+
`items`.`id` AS `_id`, \
|
28
|
+
`items`.`name` as `name`, \
|
29
|
+
`items`.`active` AS `active`, \
|
30
|
+
`items`.`body` AS `body` \
|
31
|
+
FROM `items` \
|
32
|
+
WHERE \
|
33
|
+
`items`.`id` >= $start AND \
|
34
|
+
`items`.`id` <= $end \
|
35
|
+
ORDER BY NULL
|
36
|
+
sql_query_range = SELECT IFNULL(MIN(`id`), 1), IFNULL(MAX(`id`), 1) FROM `items`
|
37
|
+
|
38
|
+
sql_attr_uint = _id
|
39
|
+
sql_attr_uint = active
|
40
|
+
|
41
|
+
sql_query_info = SELECT * FROM `items` WHERE `id` = (($id - 0) / 66)
|
42
|
+
}
|
43
|
+
|
44
|
+
index items_core
|
45
|
+
{
|
46
|
+
source = items_core_0
|
47
|
+
path = tmp/items_core
|
48
|
+
charset_type = utf-8
|
49
|
+
|
50
|
+
enable_star = 1
|
51
|
+
|
52
|
+
min_prefix_len = 3
|
53
|
+
index_exact_words = 1
|
54
|
+
|
55
|
+
morphology = stem_en
|
56
|
+
# , libstemmer_sv
|
57
|
+
|
58
|
+
min_stemming_len = 3
|
59
|
+
min_word_len = 2
|
60
|
+
|
61
|
+
# This expand_keywords declaration is not working.
|
62
|
+
#
|
63
|
+
# expand_keywords = 1
|
64
|
+
}
|
65
|
+
|
66
|
+
index items
|
67
|
+
{
|
68
|
+
type = distributed
|
69
|
+
local = items_core
|
70
|
+
}
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Thebes::Query, "after configuration" do
|
4
|
+
|
5
|
+
before(:all) {
|
6
|
+
Thebes::Query.servers = [['127.0.0.2', 111]]
|
7
|
+
}
|
8
|
+
|
9
|
+
subject { Thebes::Query.new }
|
10
|
+
|
11
|
+
its(:servers) { should == [['127.0.0.2', 111]] }
|
12
|
+
|
13
|
+
context "running query" do
|
14
|
+
|
15
|
+
before(:each) {
|
16
|
+
Thebes::Query.any_instance.stubs(:run)
|
17
|
+
}
|
18
|
+
|
19
|
+
after(:each) {
|
20
|
+
Thebes::Query.run {|q| }
|
21
|
+
}
|
22
|
+
|
23
|
+
it "should run the query on an instance" do
|
24
|
+
Thebes::Query.any_instance.expects(:run)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should call the before_query callback" do
|
28
|
+
Thebes::Query.before_query = Proc.new {|q| }
|
29
|
+
Thebes::Query.before_query.expects(:call)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should call the before_running callback" do
|
33
|
+
Thebes::Query.before_running = Proc.new {|q| }
|
34
|
+
Thebes::Query.before_running.expects(:call)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
describe Thebes::Query, "against live data" do
|
42
|
+
|
43
|
+
before {
|
44
|
+
Thebes::Query.servers = [['127.0.0.1', 9333]]
|
45
|
+
Item.create \
|
46
|
+
:name => "Larry",
|
47
|
+
:active => true,
|
48
|
+
:body => "Fine was born to a Jewish family as Louis Feinberg[1] in Philadelphia, Pennsylvania, at the corner of 3rd and South Streets. The building there is now a restaurant which is called Jon's Bar & Grill."
|
49
|
+
Item.create \
|
50
|
+
:name => "Moe",
|
51
|
+
:active => true,
|
52
|
+
:body => "Moses Horwitz was born in Brooklyn, New York, neighborhood of Brownsville, to Solomon Horwitz and Jennie Gorovitz. He was the fourth of the five Horwitz brothers and of Levite and Lithuanian Jewish ancestry."
|
53
|
+
Item.create \
|
54
|
+
:name => "Curly",
|
55
|
+
:active => false,
|
56
|
+
:body => "Curly Howard was born Jerome Lester Horwitz in Brownsville, a section of Brooklyn, New York. He was the fifth of the five Horwitz brothers and of Lithuanian Jewish ancestry."
|
57
|
+
Item.create \
|
58
|
+
:name => "Shemp",
|
59
|
+
:active => true,
|
60
|
+
:body => "Shemp, like his brothers Moe and Curly, was born in Brownsville, Brooklyn. He was the third of the five Horwitz brothers and of Levite[citation needed] and Lithuanian Jewish ancestry."
|
61
|
+
SPHINX.index # :verbose => true
|
62
|
+
}
|
63
|
+
|
64
|
+
context "searching for 'Horwitz'" do
|
65
|
+
|
66
|
+
before {
|
67
|
+
@result = Thebes::Query.run do |q|
|
68
|
+
q.append_query 'Horwitz', 'items'
|
69
|
+
end
|
70
|
+
}
|
71
|
+
|
72
|
+
subject { @result.first[:matches] }
|
73
|
+
|
74
|
+
its(:length) { should == 3 }
|
75
|
+
its(:first) { subject[:attributes]['_id'] == 2 }
|
76
|
+
its(:last) { subject[:attributes]['_id'] == 4 }
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
context "searching for 'Horwitz' with filter" do
|
81
|
+
|
82
|
+
before {
|
83
|
+
@result = Thebes::Query.run do |q|
|
84
|
+
q.filters << Riddle::Client::Filter.new('active', [1])
|
85
|
+
q.append_query 'Horwitz', 'items'
|
86
|
+
end
|
87
|
+
}
|
88
|
+
|
89
|
+
subject { @result.first[:matches] }
|
90
|
+
|
91
|
+
its(:length) { should == 2 }
|
92
|
+
its(:first) { subject[:attributes]['_id'] == 2 }
|
93
|
+
its(:last) { subject[:attributes]['_id'] == 4 }
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Thebes::Sphinxql::Client, "after configuration" do
|
4
|
+
|
5
|
+
before(:all) {
|
6
|
+
Thebes::Sphinxql::Client.servers = [
|
7
|
+
{ :host => 'localhost', :port => 9009 }
|
8
|
+
]
|
9
|
+
}
|
10
|
+
|
11
|
+
subject { Thebes::Sphinxql::Client.new }
|
12
|
+
|
13
|
+
its(:servers) { should == [{ :host => "localhost", :port => 9009 }] }
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Thebes::Query, "against live data" do
|
4
|
+
|
5
|
+
before {
|
6
|
+
Thebes::Sphinxql::Client.servers = [
|
7
|
+
{ :host => '127.0.0.1', :port => 9334 }
|
8
|
+
]
|
9
|
+
Item.create \
|
10
|
+
:name => "Larry",
|
11
|
+
:active => true,
|
12
|
+
:body => "Fine was born to a Jewish family as Louis Feinberg[1] in Philadelphia, Pennsylvania, at the corner of 3rd and South Streets. The building there is now a restaurant which is called Jon's Bar & Grill."
|
13
|
+
Item.create \
|
14
|
+
:name => "Moe",
|
15
|
+
:active => true,
|
16
|
+
:body => "Moses Horwitz was born in Brooklyn, New York, neighborhood of Brownsville, to Solomon Horwitz and Jennie Gorovitz. He was the fourth of the five Horwitz brothers and of Levite and Lithuanian Jewish ancestry."
|
17
|
+
Item.create \
|
18
|
+
:name => "Curly",
|
19
|
+
:active => false,
|
20
|
+
:body => "Curly Howard was born Jerome Lester Horwitz in Brownsville, a section of Brooklyn, New York. He was the fifth of the five Horwitz brothers and of Lithuanian Jewish ancestry."
|
21
|
+
Item.create \
|
22
|
+
:name => "Shemp",
|
23
|
+
:active => true,
|
24
|
+
:body => "Shemp, like his brothers Moe and Curly, was born in Brownsville, Brooklyn. He was the third of the five Horwitz brothers and of Levite[citation needed] and Lithuanian Jewish ancestry."
|
25
|
+
SPHINX.index # :verbose => true
|
26
|
+
}
|
27
|
+
|
28
|
+
context "searching for 'Horwitz'" do
|
29
|
+
|
30
|
+
before {
|
31
|
+
@result = Thebes::Sphinxql::Query.run "SELECT * FROM items WHERE MATCH('Horwitz')"
|
32
|
+
}
|
33
|
+
|
34
|
+
subject { @result.collect {|r| r } }
|
35
|
+
|
36
|
+
its(:length) { should == 3 }
|
37
|
+
its(:first) { subject['_id'] == 2 }
|
38
|
+
its(:last) { subject['_id'] == 4 }
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
context "searching for 'Horwitz' with filter" do
|
43
|
+
|
44
|
+
before {
|
45
|
+
@result = Thebes::Sphinxql::Query.run "SELECT * FROM items WHERE MATCH('Horwitz') AND active = 1"
|
46
|
+
}
|
47
|
+
|
48
|
+
subject { @result.collect {|r| r } }
|
49
|
+
|
50
|
+
its(:length) { should == 2 }
|
51
|
+
its(:first) { subject['_id'] == 2 }
|
52
|
+
its(:last) { subject['_id'] == 4 }
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
end
|
data/thebes.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- Mode:Ruby; encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "thebes/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "thebes"
|
7
|
+
s.version = Thebes::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Matthew Beale"]
|
10
|
+
s.email = ["matt.beale@madhatted.com"]
|
11
|
+
s.homepage = "https://github.com/harvesthq/thebes"
|
12
|
+
s.summary = %q{Thebes is a thin binding layer for Rails and Sphinx via Riddle and Mysql2.}
|
13
|
+
s.description = %q{Thebes is a thin binding layer for Rails and Sphinx via Riddle and Mysql2. Thebes expects you to write Sphinx configuration files by hand and have a rich understanding of Sphinx, but provides configuration files and templates to ease the process.}
|
14
|
+
|
15
|
+
s.add_dependency "riddle"
|
16
|
+
s.add_dependency "mysql2"
|
17
|
+
s.add_dependency "actionpack", ">= 3.0.3"
|
18
|
+
s.add_dependency "activerecord", ">= 3.0.3"
|
19
|
+
s.add_development_dependency "rspec"
|
20
|
+
s.add_development_dependency "rspec-rails"
|
21
|
+
s.add_development_dependency "genspec"
|
22
|
+
s.add_development_dependency "mocha"
|
23
|
+
|
24
|
+
# s.rubyforge_project = "thebes"
|
25
|
+
|
26
|
+
s.files = `git ls-files`.split("\n")
|
27
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
28
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
29
|
+
s.require_paths = ["lib"]
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thebes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Matthew Beale
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-03-07 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: riddle
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: mysql2
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: actionpack
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 1
|
58
|
+
segments:
|
59
|
+
- 3
|
60
|
+
- 0
|
61
|
+
- 3
|
62
|
+
version: 3.0.3
|
63
|
+
type: :runtime
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: activerecord
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 1
|
74
|
+
segments:
|
75
|
+
- 3
|
76
|
+
- 0
|
77
|
+
- 3
|
78
|
+
version: 3.0.3
|
79
|
+
type: :runtime
|
80
|
+
version_requirements: *id004
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rspec
|
83
|
+
prerelease: false
|
84
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
hash: 3
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
type: :development
|
94
|
+
version_requirements: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: rspec-rails
|
97
|
+
prerelease: false
|
98
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
hash: 3
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
version: "0"
|
107
|
+
type: :development
|
108
|
+
version_requirements: *id006
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: genspec
|
111
|
+
prerelease: false
|
112
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
hash: 3
|
118
|
+
segments:
|
119
|
+
- 0
|
120
|
+
version: "0"
|
121
|
+
type: :development
|
122
|
+
version_requirements: *id007
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: mocha
|
125
|
+
prerelease: false
|
126
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
hash: 3
|
132
|
+
segments:
|
133
|
+
- 0
|
134
|
+
version: "0"
|
135
|
+
type: :development
|
136
|
+
version_requirements: *id008
|
137
|
+
description: Thebes is a thin binding layer for Rails and Sphinx via Riddle and Mysql2. Thebes expects you to write Sphinx configuration files by hand and have a rich understanding of Sphinx, but provides configuration files and templates to ease the process.
|
138
|
+
email:
|
139
|
+
- matt.beale@madhatted.com
|
140
|
+
executables: []
|
141
|
+
|
142
|
+
extensions: []
|
143
|
+
|
144
|
+
extra_rdoc_files: []
|
145
|
+
|
146
|
+
files:
|
147
|
+
- .gitignore
|
148
|
+
- .rspec
|
149
|
+
- Gemfile
|
150
|
+
- Gemfile.lock
|
151
|
+
- LICENSE
|
152
|
+
- Rakefile
|
153
|
+
- Readme.mkd
|
154
|
+
- lib/thebes.rb
|
155
|
+
- lib/thebes/config_writer.rb
|
156
|
+
- lib/thebes/query.rb
|
157
|
+
- lib/thebes/railtie.rb
|
158
|
+
- lib/thebes/sphinx_search.rb
|
159
|
+
- lib/thebes/sphinxql/client.rb
|
160
|
+
- lib/thebes/sphinxql/query.rb
|
161
|
+
- lib/thebes/version.rb
|
162
|
+
- rails/init.rb
|
163
|
+
- railties/sphinx_config_generator.rb
|
164
|
+
- railties/thebes.rake
|
165
|
+
- spec/spec_helper.rb
|
166
|
+
- spec/support/active_record.rb
|
167
|
+
- spec/support/database.yml.example
|
168
|
+
- spec/support/test.sphinx.conf.erb
|
169
|
+
- spec/thebes/query_spec.rb
|
170
|
+
- spec/thebes/sphinxql/client_spec.rb
|
171
|
+
- spec/thebes/sphinxql/query_spec.rb
|
172
|
+
- spec/thebes/version_spec.rb
|
173
|
+
- templates/sphinx.conf.erb
|
174
|
+
- templates/sphinx.yml
|
175
|
+
- templates/sphinx_servers.yml
|
176
|
+
- thebes.gemspec
|
177
|
+
has_rdoc: true
|
178
|
+
homepage: https://github.com/harvesthq/thebes
|
179
|
+
licenses: []
|
180
|
+
|
181
|
+
post_install_message:
|
182
|
+
rdoc_options: []
|
183
|
+
|
184
|
+
require_paths:
|
185
|
+
- lib
|
186
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
187
|
+
none: false
|
188
|
+
requirements:
|
189
|
+
- - ">="
|
190
|
+
- !ruby/object:Gem::Version
|
191
|
+
hash: 3
|
192
|
+
segments:
|
193
|
+
- 0
|
194
|
+
version: "0"
|
195
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
196
|
+
none: false
|
197
|
+
requirements:
|
198
|
+
- - ">="
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
hash: 3
|
201
|
+
segments:
|
202
|
+
- 0
|
203
|
+
version: "0"
|
204
|
+
requirements: []
|
205
|
+
|
206
|
+
rubyforge_project:
|
207
|
+
rubygems_version: 1.5.0
|
208
|
+
signing_key:
|
209
|
+
specification_version: 3
|
210
|
+
summary: Thebes is a thin binding layer for Rails and Sphinx via Riddle and Mysql2.
|
211
|
+
test_files:
|
212
|
+
- spec/spec_helper.rb
|
213
|
+
- spec/support/active_record.rb
|
214
|
+
- spec/support/database.yml.example
|
215
|
+
- spec/support/test.sphinx.conf.erb
|
216
|
+
- spec/thebes/query_spec.rb
|
217
|
+
- spec/thebes/sphinxql/client_spec.rb
|
218
|
+
- spec/thebes/sphinxql/query_spec.rb
|
219
|
+
- spec/thebes/version_spec.rb
|