airblade-Sphincter 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +15 -0
- data/LICENSE.txt +27 -0
- data/Manifest.txt +16 -0
- data/README.txt +153 -0
- data/Rakefile +21 -0
- data/lib/sphincter.rb +107 -0
- data/lib/sphincter/association_searcher.rb +22 -0
- data/lib/sphincter/configure.rb +499 -0
- data/lib/sphincter/search.rb +199 -0
- data/lib/sphincter/search_stub.rb +60 -0
- data/lib/sphincter/tasks.rb +114 -0
- data/test/sphincter_test_case.rb +220 -0
- data/test/test_sphincter_association_searcher.rb +39 -0
- data/test/test_sphincter_configure.rb +386 -0
- data/test/test_sphincter_search.rb +100 -0
- data/test/test_sphincter_search_stub.rb +50 -0
- metadata +101 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
require 'date'
|
|
2
|
+
require 'time'
|
|
3
|
+
require 'sphincter'
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Search extension for ActiveRecord::Base that automatically extends
|
|
7
|
+
# ActiveRecord::Base.
|
|
8
|
+
|
|
9
|
+
module Sphincter::Search
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# Struct to hold search results.
|
|
13
|
+
#
|
|
14
|
+
# records:: ActiveRecord objects returned by sphinx
|
|
15
|
+
# total:: Total records searched
|
|
16
|
+
# per_page:: Size of each chunk of records
|
|
17
|
+
|
|
18
|
+
Results = Struct.new :records, :total, :per_page
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Indexes registered with add_index.
|
|
22
|
+
|
|
23
|
+
@@indexes ||= Hash.new { |h, model| h[model] = [] }
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Accessor for indexes registered with add_index.
|
|
27
|
+
|
|
28
|
+
def self.indexes
|
|
29
|
+
@@indexes
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Adds an index with +options+.
|
|
34
|
+
#
|
|
35
|
+
# add_index will automatically add Sphincter::AssociationSearcher to any
|
|
36
|
+
# has_many associations referenced by this model's belongs_to associations.
|
|
37
|
+
# If this model belongs_to another model, and that model has_many of this
|
|
38
|
+
# model, then you will be able to <tt>other.models.search</tt> and recieve
|
|
39
|
+
# only records in the association.
|
|
40
|
+
#
|
|
41
|
+
# Currently, only has_many associations without conditions will have
|
|
42
|
+
# AssociationSearcher appended.
|
|
43
|
+
#
|
|
44
|
+
# Options are:
|
|
45
|
+
#
|
|
46
|
+
# :name:: Name of index. Defaults to ActiveRecord::Base::table_name.
|
|
47
|
+
# :fields:: Array of fields to index. Foreign key columns for belongs_to
|
|
48
|
+
# associations are automatically added. Fields from associations
|
|
49
|
+
# may be included by using "association.field".
|
|
50
|
+
# :conditions:: Array of SQL conditions that will be ANDed together to
|
|
51
|
+
# predicate inclusion in the search index.
|
|
52
|
+
#
|
|
53
|
+
# Example:
|
|
54
|
+
#
|
|
55
|
+
# class Post < ActiveRecord::Base
|
|
56
|
+
# belongs_to :user
|
|
57
|
+
# belongs_to :blog
|
|
58
|
+
# has_many :comments
|
|
59
|
+
#
|
|
60
|
+
# add_index :fields => %w[title body user.name, comments.body],
|
|
61
|
+
# :conditions => ['published = 1']
|
|
62
|
+
# end
|
|
63
|
+
#
|
|
64
|
+
# When including fields from associations, MySQL's GROUP_CONCAT() function
|
|
65
|
+
# is used. By default this will create a string up to 1024 characters long.
|
|
66
|
+
# A larger string can be used by changing the value of MySQL's
|
|
67
|
+
# group_concat_max_len variable. To do this, add the following to your
|
|
68
|
+
# sphincter.RAILS_ENV.yml files:
|
|
69
|
+
#
|
|
70
|
+
# mysql:
|
|
71
|
+
# sql_query_pre:
|
|
72
|
+
# - SET NAMES utf8
|
|
73
|
+
# - SET SESSION group_concat_max_len = VALUE
|
|
74
|
+
|
|
75
|
+
def add_index(options = {})
|
|
76
|
+
options[:fields] ||= []
|
|
77
|
+
|
|
78
|
+
reflect_on_all_associations.each do |my_assoc|
|
|
79
|
+
next unless my_assoc.macro == :belongs_to
|
|
80
|
+
|
|
81
|
+
options[:fields] << my_assoc.primary_key_name.to_s
|
|
82
|
+
|
|
83
|
+
has_many_klass = my_assoc.class_name.constantize
|
|
84
|
+
|
|
85
|
+
has_many_klass.reflect_on_all_associations.each do |opp_assoc|
|
|
86
|
+
next if opp_assoc.class_name != name or
|
|
87
|
+
opp_assoc.macro != :has_many or
|
|
88
|
+
opp_assoc.options[:conditions]
|
|
89
|
+
|
|
90
|
+
extends = Array(opp_assoc.options[:extend])
|
|
91
|
+
extends << Sphincter::AssociationSearcher
|
|
92
|
+
opp_assoc.options[:extend] = extends
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
options[:fields].uniq!
|
|
97
|
+
|
|
98
|
+
# Use class name rather than object identity to test for hash membership.
|
|
99
|
+
unless Sphincter::Search.indexes.keys.any? {|k| k.class_name == self.class_name}
|
|
100
|
+
Sphincter::Search.indexes[self] << options
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
##
|
|
105
|
+
# Converts +values+ into an Array of values SetFilter can digest.
|
|
106
|
+
#
|
|
107
|
+
# true/false becomes 1/0, Time/Date/DateTime becomes a time in epoch
|
|
108
|
+
# seconds. Everything else is passed straight through.
|
|
109
|
+
|
|
110
|
+
def sphincter_convert_values(values)
|
|
111
|
+
values.map do |value|
|
|
112
|
+
case value
|
|
113
|
+
when Date, DateTime then Time.parse(value.to_s).to_i
|
|
114
|
+
when FalseClass then 0
|
|
115
|
+
when Time then value.to_i
|
|
116
|
+
when TrueClass then 1
|
|
117
|
+
else value
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
##
|
|
123
|
+
# Searches for +query+ with +options+.
|
|
124
|
+
#
|
|
125
|
+
# Allowed options are:
|
|
126
|
+
#
|
|
127
|
+
# :between:: Hash of Sphinx range filter conditions. Hash keys are sphinx
|
|
128
|
+
# group_column or date_column names. Values can be
|
|
129
|
+
# Date/Time/DateTime or Integers.
|
|
130
|
+
# :conditions:: Hash of Sphinx value filter conditions. Hash keys are
|
|
131
|
+
# sphinx group_column or date_column names. Values can be a
|
|
132
|
+
# single value or an Array of values.
|
|
133
|
+
# :index:: Name of Sphinx index to search. Defaults to
|
|
134
|
+
# ActiveRecord::Base::table_name.
|
|
135
|
+
# :page:: Page offset of records to return, for easy use with paginators.
|
|
136
|
+
# :per_page:: Size of a page. Default page size is controlled by the
|
|
137
|
+
# configuration.
|
|
138
|
+
#
|
|
139
|
+
# Returns a Sphincter::Search::Results object.
|
|
140
|
+
|
|
141
|
+
def search(query, options = {})
|
|
142
|
+
sphinx = Sphinx::Client.new
|
|
143
|
+
|
|
144
|
+
@host ||= Sphincter::Configure.get_conf['sphincter']['host']
|
|
145
|
+
@port ||= Sphincter::Configure.get_conf['sphincter']['port']
|
|
146
|
+
|
|
147
|
+
sphinx.SetServer @host, @port
|
|
148
|
+
|
|
149
|
+
options[:conditions] ||= {}
|
|
150
|
+
options[:conditions].each do |column, values|
|
|
151
|
+
values = sphincter_convert_values Array(values)
|
|
152
|
+
sphinx.SetFilter column.to_s, values
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
options[:between] ||= {}
|
|
156
|
+
options[:between].each do |column, between|
|
|
157
|
+
min, max = sphincter_convert_values between
|
|
158
|
+
|
|
159
|
+
sphinx.SetFilterRange column.to_s, min, max
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
@default_per_page ||= Sphincter::Configure.get_conf['sphincter']['per_page']
|
|
163
|
+
|
|
164
|
+
per_page = options[:per_page] || @default_per_page
|
|
165
|
+
page_offset = options.key?(:page) ? options[:page] - 1 : 0
|
|
166
|
+
offset = page_offset * per_page
|
|
167
|
+
|
|
168
|
+
sphinx.SetLimits offset, per_page
|
|
169
|
+
|
|
170
|
+
index_name = options[:index] || table_name
|
|
171
|
+
|
|
172
|
+
sphinx_result = sphinx.Query query, index_name
|
|
173
|
+
|
|
174
|
+
matches = sphinx_result['matches'].sort_by do |id, match|
|
|
175
|
+
-match['index'] # #find reverses, lame!
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
ids = matches.map do |id, match|
|
|
179
|
+
(id - match['attrs']['sphincter_index_id']) /
|
|
180
|
+
Sphincter::Configure.index_count
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
results = Results.new
|
|
184
|
+
|
|
185
|
+
results.records = find ids
|
|
186
|
+
results.total = sphinx_result['total_found']
|
|
187
|
+
results.per_page = per_page
|
|
188
|
+
|
|
189
|
+
results
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# :stopdoc:
|
|
195
|
+
module ActiveRecord; end
|
|
196
|
+
class ActiveRecord::Base; end
|
|
197
|
+
ActiveRecord::Base.extend Sphincter::Search
|
|
198
|
+
# :startdoc:
|
|
199
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'sphincter'
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Stub for Sphincter searching. Extend ActiveRecord::Base with this module in
|
|
5
|
+
# your tests so you won't have to run a Sphinx searchd to test your searching.
|
|
6
|
+
#
|
|
7
|
+
# In test/testHelper.rb:
|
|
8
|
+
#
|
|
9
|
+
# require 'sphincter/search_stub'
|
|
10
|
+
# ActiveRecord::Base.extend Sphincter::SearchStub
|
|
11
|
+
#
|
|
12
|
+
# Before running a search, you'll need to populate the stub's accessors:
|
|
13
|
+
#
|
|
14
|
+
# def test_search
|
|
15
|
+
# Model.search_args = []
|
|
16
|
+
# Model.search_results = [Model.find(1, 2, 3)]
|
|
17
|
+
#
|
|
18
|
+
# records = Model.search 'query'
|
|
19
|
+
#
|
|
20
|
+
# assert_equal 1, Model.search_args.length
|
|
21
|
+
# assert_equal [...], Model.search_args
|
|
22
|
+
# assert_equal 0, Model.search_results.length
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# Since both search_args and search_results are an Array you can call #search
|
|
26
|
+
# multiple times and get back different results per call. #search will raise
|
|
27
|
+
# an exception if you don't supply enough results.
|
|
28
|
+
|
|
29
|
+
module Sphincter::SearchStub
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# An Array that records arguments #search was called with. search_args
|
|
33
|
+
# isn't set to anything by default, so do that in your test setup.
|
|
34
|
+
|
|
35
|
+
attr_accessor :search_args
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# A pre-populated Array of search results for queries. search_results isn't
|
|
39
|
+
# set to anything by default, so do that in your test setup.
|
|
40
|
+
|
|
41
|
+
attr_accessor :search_results
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# Overrides Sphincter::Search#search to use the search_args and
|
|
45
|
+
# search_results values instead of connecting to Sphinx.
|
|
46
|
+
|
|
47
|
+
def search(query, options = {})
|
|
48
|
+
unless @search_args and @search_results then
|
|
49
|
+
raise 'need to set up Sphincter::SearchStub#search_results and/or Sphincter::SearchStub#search_args in test setup'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@search_args << [query, options]
|
|
53
|
+
|
|
54
|
+
raise 'no more search results in search stub' if @search_results.empty?
|
|
55
|
+
|
|
56
|
+
@search_results.shift
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'sphincter/configure'
|
|
3
|
+
|
|
4
|
+
namespace :sphincter do
|
|
5
|
+
|
|
6
|
+
desc 'Creates sphinx.conf if it doesn\'t exist'
|
|
7
|
+
task :configure => :environment do
|
|
8
|
+
sphinx_conf = Sphincter::Configure.sphinx_conf
|
|
9
|
+
Rake::Task['sphincter:reconfigure'].invoke unless File.exist? sphinx_conf
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
desc 'Creates sphinx.conf'
|
|
13
|
+
task :reconfigure => :environment do
|
|
14
|
+
Sphincter::Configure.configure
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
desc 'Runs the sphinx indexer if the indexes don\'t exist'
|
|
18
|
+
task :index => :configure do
|
|
19
|
+
indexes_defined = Sphincter::Configure.index_count
|
|
20
|
+
sphinx_dir = Sphincter::Configure.get_conf['sphincter']['path']
|
|
21
|
+
|
|
22
|
+
indexes_found = Dir[File.join(sphinx_dir, '*.spd')].length
|
|
23
|
+
|
|
24
|
+
Rake::Task['sphincter:reindex'].invoke if indexes_found > indexes_defined
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
desc 'Runs the sphinx indexer'
|
|
28
|
+
task :reindex => :configure do
|
|
29
|
+
sphinx_conf = Sphincter::Configure.sphinx_conf
|
|
30
|
+
cmd = %W[indexer --all --config #{sphinx_conf}]
|
|
31
|
+
cmd << "--quiet" unless Rake.application.options.trace
|
|
32
|
+
cmd << "--rotate" if Sphincter::Configure.searchd_running?
|
|
33
|
+
system(*cmd)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc 'Stops searchd, reconfigures and reindexes'
|
|
37
|
+
task :reset => :configure do
|
|
38
|
+
Rake::Task['sphincter:stop_searchd'].invoke
|
|
39
|
+
FileUtils.rm_rf Sphincter::Configure.sphinx_dir,
|
|
40
|
+
:verbose => Rake.application.options.trace
|
|
41
|
+
Rake::Task['sphincter:reconfigure'].execute # force reindex
|
|
42
|
+
Rake::Task['sphincter:reindex'].invoke
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc 'Restarts the searchd sphinx daemon'
|
|
46
|
+
task :restart_searchd => %w[sphincter:stop_searchd sphincter:start_searchd]
|
|
47
|
+
|
|
48
|
+
desc 'Sets up the sphinx client'
|
|
49
|
+
task :setup_sphinx do
|
|
50
|
+
require 'fileutils'
|
|
51
|
+
require 'tmpdir'
|
|
52
|
+
require 'open-uri'
|
|
53
|
+
|
|
54
|
+
verbose = Rake.application.options.trace
|
|
55
|
+
|
|
56
|
+
begin
|
|
57
|
+
tmpdir = File.join Dir.tmpdir, "Sphincter_setup_#{$$}"
|
|
58
|
+
|
|
59
|
+
mkdir tmpdir, :verbose => true
|
|
60
|
+
|
|
61
|
+
chdir tmpdir
|
|
62
|
+
|
|
63
|
+
src = open "http://rubyforge.org/frs/download.php/19571/sphinx-0.3.0.zip"
|
|
64
|
+
File.open('sphinx-0.3.0.zip', 'wb') { |dst| dst.write src.read }
|
|
65
|
+
|
|
66
|
+
quiet = verbose ? '' : ' -q'
|
|
67
|
+
sh "unzip#{quiet} sphinx-0.3.0.zip" or
|
|
68
|
+
raise "couldn't unzip sphinx-0.3.0.zip"
|
|
69
|
+
|
|
70
|
+
File.open 'sphinx.patch', 'wb' do |patch|
|
|
71
|
+
patch.puts <<-EOF
|
|
72
|
+
--- sphinx/lib/client.rb.orig 2007-04-05 06:38:14.000000000 -0700
|
|
73
|
+
+++ sphinx/lib/client.rb 2007-07-29 20:23:18.000000000 -0700
|
|
74
|
+
@@ -398,6 +398,7 @@
|
|
75
|
+
\r
|
|
76
|
+
result['matches'][doc] ||= {}\r
|
|
77
|
+
result['matches'][doc]['weight'] = weight\r
|
|
78
|
+
+ result['matches'][doc]['index'] = count\r
|
|
79
|
+
attrs_names_in_order.each do |attr|\r
|
|
80
|
+
val = response[p, 4].unpack('N*').first; p += 4\r
|
|
81
|
+
result['matches'][doc]['attrs'] ||= {}\r
|
|
82
|
+
EOF
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
quiet = verbose ? ' --verbose' : ''
|
|
86
|
+
sh "patch#{quiet} -p0 sphinx/lib/client.rb sphinx.patch" or
|
|
87
|
+
raise "couldn't patch sphinx"
|
|
88
|
+
|
|
89
|
+
sphinx_plugin_dir = File.join RAILS_ROOT, 'vendor', 'plugins', 'sphinx'
|
|
90
|
+
rm_rf sphinx_plugin_dir, :verbose => true
|
|
91
|
+
|
|
92
|
+
mv 'sphinx', sphinx_plugin_dir, :verbose => true
|
|
93
|
+
ensure
|
|
94
|
+
rm_rf tmpdir, :verbose => true
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
desc 'Starts the searchd sphinx daemon'
|
|
99
|
+
task :start_searchd => :index do
|
|
100
|
+
unless Sphincter::Configure.searchd_running? then
|
|
101
|
+
cmd = "searchd --config #{Sphincter::Configure.sphinx_conf}"
|
|
102
|
+
cmd << " > /dev/null" unless Rake.application.options.trace
|
|
103
|
+
system cmd
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
desc 'Stops the searchd daemon'
|
|
108
|
+
task :stop_searchd => :configure do
|
|
109
|
+
pid = Sphincter::Configure.searchd_running?
|
|
110
|
+
system 'kill', pid if pid
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
|
114
|
+
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
|
|
5
|
+
$TESTING = true
|
|
6
|
+
|
|
7
|
+
class String
|
|
8
|
+
def constantize
|
|
9
|
+
case self
|
|
10
|
+
when /belongs_to/i then SphincterTestCase::BelongsTo
|
|
11
|
+
when /many/i then SphincterTestCase::HasMany
|
|
12
|
+
when /poly/i then SphincterTestCase::Poly
|
|
13
|
+
else raise "missing klass for #{self} in #constantize"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
require 'sphincter'
|
|
19
|
+
|
|
20
|
+
class ActiveRecord::Base
|
|
21
|
+
def self.configurations
|
|
22
|
+
{
|
|
23
|
+
'development' => {
|
|
24
|
+
'adapter' => 'mysql',
|
|
25
|
+
'host' => 'host',
|
|
26
|
+
'username' => 'username',
|
|
27
|
+
'password' => 'password',
|
|
28
|
+
'database' => 'database',
|
|
29
|
+
'socket' => 'socket',
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module Sphinx; end
|
|
36
|
+
|
|
37
|
+
class Sphinx::Client
|
|
38
|
+
|
|
39
|
+
@@last_client = nil
|
|
40
|
+
|
|
41
|
+
attr_reader :host, :port, :query, :index, :filters, :offset, :limit
|
|
42
|
+
|
|
43
|
+
def self.last_client
|
|
44
|
+
@@last_client
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initialize
|
|
48
|
+
@filters = {}
|
|
49
|
+
|
|
50
|
+
@@last_client = self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def Query(query, index)
|
|
54
|
+
@query, @index = query, index
|
|
55
|
+
|
|
56
|
+
{
|
|
57
|
+
'matches' => {
|
|
58
|
+
12 => { 'attrs' => { 'sphincter_index_id' => 1 }, 'index' => 3 },
|
|
59
|
+
13 => { 'attrs' => { 'sphincter_index_id' => 1 }, 'index' => 1 },
|
|
60
|
+
14 => { 'attrs' => { 'sphincter_index_id' => 1 }, 'index' => 2 },
|
|
61
|
+
},
|
|
62
|
+
'total_found' => 3
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def SetFilter(column, values)
|
|
67
|
+
@filters[column] = values
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def SetFilterRange(column, min, max)
|
|
71
|
+
@filters[column] = [:range, min, max]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def SetLimits(offset, limit)
|
|
75
|
+
@offset, @limit = offset, limit
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def SetServer(host, port)
|
|
79
|
+
@host, @port = host, port
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class SphincterTestCase < Test::Unit::TestCase
|
|
85
|
+
|
|
86
|
+
undef_method :default_test
|
|
87
|
+
|
|
88
|
+
class Column
|
|
89
|
+
attr_accessor :type
|
|
90
|
+
|
|
91
|
+
def initialize(type)
|
|
92
|
+
@type = type
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class Connection
|
|
97
|
+
def quote(name) "'#{name}'" end
|
|
98
|
+
def quote_column_name(name) "`#{name}`" end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class Reflection
|
|
102
|
+
attr_accessor :klass
|
|
103
|
+
attr_reader :macro, :options, :name
|
|
104
|
+
|
|
105
|
+
def initialize(macro, name, options = {})
|
|
106
|
+
@klass = Model
|
|
107
|
+
@macro = macro
|
|
108
|
+
@name = name.intern
|
|
109
|
+
@options = options
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def class_name() @name.to_s.sub(/s$/, '').capitalize end
|
|
113
|
+
def primary_key_name() "#{@name}_id" end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
class Model
|
|
117
|
+
|
|
118
|
+
@reflections = [Reflection.new(:belongs_to, 'belongs_to'),
|
|
119
|
+
Reflection.new(:has_many, 'manys'),
|
|
120
|
+
Reflection.new(:has_many, 'polys', :as => :polyable)]
|
|
121
|
+
|
|
122
|
+
class << self; attr_accessor :reflections; end
|
|
123
|
+
|
|
124
|
+
def self.connection() Connection.new end
|
|
125
|
+
|
|
126
|
+
def self.columns_hash
|
|
127
|
+
{
|
|
128
|
+
'boolean' => Column.new(:boolean),
|
|
129
|
+
'date' => Column.new(:date),
|
|
130
|
+
'datetime' => Column.new(:datetime),
|
|
131
|
+
'float' => Column.new(:float),
|
|
132
|
+
'integer' => Column.new(:integer),
|
|
133
|
+
'string' => Column.new(:string),
|
|
134
|
+
'text' => Column.new(:text),
|
|
135
|
+
'time' => Column.new(:time),
|
|
136
|
+
'timestamp' => Column.new(:timestamp),
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def self.find(ids) ids end
|
|
141
|
+
|
|
142
|
+
def self.name() 'Model' end
|
|
143
|
+
|
|
144
|
+
def self.primary_key() 'id' end
|
|
145
|
+
|
|
146
|
+
def self.reflect_on_all_associations
|
|
147
|
+
@reflections
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def self.table_name() 'models' end
|
|
151
|
+
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
class BelongsTo < Model
|
|
155
|
+
@reflections = [Reflection.new(:belongs_to, 'something'),
|
|
156
|
+
Reflection.new(:has_many, 'models')]
|
|
157
|
+
|
|
158
|
+
def self.table_name() 'belongs_tos' end
|
|
159
|
+
|
|
160
|
+
def id() 42 end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
class HasMany < Model
|
|
164
|
+
@reflections = [Reflection.new(:belongs_to, 'models')]
|
|
165
|
+
|
|
166
|
+
def self.table_name() 'has_manys' end
|
|
167
|
+
|
|
168
|
+
def id() 84 end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
class Poly < Model
|
|
172
|
+
@reflections = [Reflection.new(:belongs_to, 'polyable',
|
|
173
|
+
:polymorphic => true)]
|
|
174
|
+
|
|
175
|
+
def self.table_name() 'polys' end
|
|
176
|
+
|
|
177
|
+
def id() 126 end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
class Model
|
|
181
|
+
extend Sphincter::Search
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def setup
|
|
185
|
+
@temp_dir = File.join Dir.tmpdir, "sphincter_test_case_#{$$}"
|
|
186
|
+
FileUtils.mkdir_p @temp_dir
|
|
187
|
+
@orig_dir = Dir.pwd
|
|
188
|
+
Dir.chdir @temp_dir
|
|
189
|
+
|
|
190
|
+
@old_RAILS_ROOT = (Object.const_get :RAILS_ROOT rescue nil)
|
|
191
|
+
Object.send :remove_const, :RAILS_ROOT rescue nil
|
|
192
|
+
Object.const_set :RAILS_ROOT, @temp_dir
|
|
193
|
+
|
|
194
|
+
@old_RAILS_ENV = (Object.const_get :RAILS_ENV rescue nil)
|
|
195
|
+
Object.send :remove_const, :RAILS_ENV rescue nil
|
|
196
|
+
Object.const_set :RAILS_ENV, 'development'
|
|
197
|
+
|
|
198
|
+
Sphincter::Search.indexes.replace Hash.new { |h,k| h[k] = [] }
|
|
199
|
+
|
|
200
|
+
Sphincter::Configure.instance_variable_set '@env_conf', nil if
|
|
201
|
+
Sphincter::Configure.instance_variables.include? '@env_conf'
|
|
202
|
+
Sphincter::Configure.instance_variable_set '@index_count', nil if
|
|
203
|
+
Sphincter::Configure.instance_variables.include? '@index_count'
|
|
204
|
+
|
|
205
|
+
BelongsTo.reflections.last.options.delete :extend
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def teardown
|
|
209
|
+
FileUtils.rm_rf @temp_dir
|
|
210
|
+
Dir.chdir @orig_dir
|
|
211
|
+
|
|
212
|
+
Object.send :remove_const, :RAILS_ROOT
|
|
213
|
+
Object.const_set :RAILS_ROOT, @old_RAILS_ROOT
|
|
214
|
+
|
|
215
|
+
Object.send :remove_const, :RAILS_ENV
|
|
216
|
+
Object.const_set :RAILS_ENV, @old_RAILS_ENV
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
end
|
|
220
|
+
|